From be03bc9bfd1c5663c606880d678cc864c7cc95bc Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Nov 2022 20:47:11 +0800 Subject: Complete crupest-api. --- docker/crupest-api/CrupestApi/Program.cs | 181 ++++++++++++++--------------- docker/crupest-nginx/sites/www/src/main.js | 62 +++------- template/docker-compose.yaml.template | 14 +++ tool/test-crupest-api.py | 55 ++------- 4 files changed, 123 insertions(+), 189 deletions(-) diff --git a/docker/crupest-api/CrupestApi/Program.cs b/docker/crupest-api/CrupestApi/Program.cs index c62bf4d..7fd6675 100644 --- a/docker/crupest-api/CrupestApi/Program.cs +++ b/docker/crupest-api/CrupestApi/Program.cs @@ -9,47 +9,40 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Console; using Microsoft.AspNetCore.Http; using CrupestApi.Config; -public class TodoItem +namespace CrupestApi { - public string Status { get; set; } = default!; - public string Title { get; set; } = default!; -} + public class TodoItem + { + public string Status { get; set; } = default!; + public string Title { get; set; } = default!; + public bool Closed { get; set; } + public string color { get; set; } = default!; + } -internal class Program -{ - private static void Main(string[] args) + internal class Program { - using var httpClient = new HttpClient(); + private static void Main(string[] args) + { + using var httpClient = new HttpClient(); - var builder = WebApplication.CreateBuilder(args); + var builder = WebApplication.CreateBuilder(args); - string configFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_CONFIG_FILE") ?? "/config.json"; + string configFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_CONFIG_FILE") ?? "/config.json"; - builder.Configuration.AddJsonFile(configFilePath, optional: false, reloadOnChange: true); + builder.Configuration.AddJsonFile(configFilePath, optional: false, reloadOnChange: true); - string? logFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_LOG_FILE"); - if (logFilePath is not null) - { - // TODO: Log to file. - builder.Logging.AddSimpleConsole(logger => - { - logger.ColorBehavior = LoggerColorBehavior.Disabled; - }); - } + var app = builder.Build(); - var app = builder.Build(); - - app.MapGet("/api/todos", async ([FromServices] IConfiguration configuration, [FromServices] ILoggerFactory loggerFactory) => - { - var logger = loggerFactory.CreateLogger("CrupestApi.Todos"); - - static string CreateGraphQLQuery(TodoConfiguration todoConfiguration) + app.MapGet("/api/todos", async ([FromServices] IConfiguration configuration, [FromServices] ILoggerFactory loggerFactory) => { - return $$""" + var logger = loggerFactory.CreateLogger("CrupestApi.Todos"); + + static string CreateGraphQLQuery(TodoConfiguration todoConfiguration) + { + return $$""" { user(login: "{{todoConfiguration.Username}}") { projectV2(number: {{todoConfiguration.ProjectNumber}}) { @@ -76,85 +69,89 @@ internal class Program } } """; - } + } - var todoConfiguration = configuration.GetSection("Todos").Get(); - if (todoConfiguration is null) - { - throw new Exception("Fail to get todos configuration."); - } + var todoConfiguration = configuration.GetSection("Todos").Get(); + if (todoConfiguration is null) + { + throw new Exception("Fail to get todos configuration."); + } - using var requestContent = new StringContent(JsonSerializer.Serialize(new - { - query = CreateGraphQLQuery(todoConfiguration) - })); - requestContent.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Json, Encoding.UTF8.WebName); + using var requestContent = new StringContent(JsonSerializer.Serialize(new + { + query = CreateGraphQLQuery(todoConfiguration) + })); + requestContent.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Json, Encoding.UTF8.WebName); - using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.github.com/graphql"); - request.Content = requestContent; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", todoConfiguration.Token); + using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.github.com/graphql"); + request.Content = requestContent; + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", todoConfiguration.Token); + request.Headers.TryAddWithoutValidation("User-Agent", "crupest"); - using var response = await httpClient.SendAsync(request); - var responseBody = await response.Content.ReadAsStringAsync(); - logger.LogInformation(response.StatusCode.ToString()); - logger.LogInformation(responseBody); + using var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + logger.LogInformation(response.StatusCode.ToString()); + logger.LogInformation(responseBody); - if (response.IsSuccessStatusCode) - { - using var responseJson = JsonSerializer.Deserialize(responseBody); - if (responseJson is null) + if (response.IsSuccessStatusCode) { - throw new Exception("Fail to deserialize response body."); - } + using var responseJson = JsonSerializer.Deserialize(responseBody); + if (responseJson is null) + { + throw new Exception("Fail to deserialize response body."); + } - var nodes = responseJson.RootElement.GetProperty("data").GetProperty("user").GetProperty("projectV2").GetProperty("items").GetProperty("nodes").EnumerateArray(); + var nodes = responseJson.RootElement.GetProperty("data").GetProperty("user").GetProperty("projectV2").GetProperty("items").GetProperty("nodes").EnumerateArray(); - var result = new List(); + var result = new List(); - foreach (var node in nodes) - { - var content = node.GetProperty("content"); - var title = content.GetProperty("title").GetString(); - if (title is null) - { - throw new Exception("Fail to get title."); - } - JsonElement closedElement; - bool closed; - if (content.TryGetProperty("closed", out closedElement)) - { - closed = closedElement.GetBoolean(); - } - else + foreach (var node in nodes) { - closed = false; + var content = node.GetProperty("content"); + var title = content.GetProperty("title").GetString(); + if (title is null) + { + throw new Exception("Fail to get title."); + } + JsonElement closedElement; + bool closed; + if (content.TryGetProperty("closed", out closedElement)) + { + closed = closedElement.GetBoolean(); + } + else + { + closed = false; + } + + result.Add(new TodoItem + { + Title = title, + Status = closed ? "Done" : "Todo", + Closed = closed, + color = closed ? "green" : "blue" + }); } - result.Add(new TodoItem + return Results.Json(result, new JsonSerializerOptions { - Title = title, - Status = closed ? "Done" : "Todo" - }); + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }, statusCode: 200); } - - return Results.Json(result, new JsonSerializerOptions + else { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }, statusCode: 200); - } - else - { - const string message = "Fail to get todos from GitHub."; - logger.LogError(message); + const string message = "Fail to get todos from GitHub."; + logger.LogError(message); - return Results.Json(new - { - message - }, statusCode: StatusCodes.Status503ServiceUnavailable); - } - }); + return Results.Json(new + { + message + }, statusCode: StatusCodes.Status503ServiceUnavailable); + } + }); - app.Run(); + app.Run(); + } } -} \ No newline at end of file +} diff --git a/docker/crupest-nginx/sites/www/src/main.js b/docker/crupest-nginx/sites/www/src/main.js index bbdecab..111b0bd 100644 --- a/docker/crupest-nginx/sites/www/src/main.js +++ b/docker/crupest-nginx/sites/www/src/main.js @@ -1,4 +1,3 @@ -import { Octokit } from "https://cdn.skypack.dev/octokit"; import "./style.css"; const colorStripContainer = document.getElementById("color-strip-container"); @@ -21,63 +20,28 @@ document.addEventListener("DOMContentLoaded", async () => { const todoMessage = document.getElementById("todo-message"); const todoContainer = document.getElementById("todo-container"); - // TODO: we need another way to do this! - const octokit = new Octokit({ - auth: "xxx", - }); + const res = await fetch("/api/todos"); + const body = res.json(); - try { - const res = await octokit.graphql( - ` - { - user(login: "crupest") { - projectV2(number: 2) { - items(last: 100) { - nodes { - __typename - fieldValueByName(name: "Status") - content { - __typename - ... on Issue { - title - closed - } - ... on PullRequest { - title - closed - } - ... on DraftIssue { - title - } - } - } - } - } - } - } - ` + if (res.status !== 200) { + todoMessage.style.color = "red"; + todoMessage.textContent = + "Failed to fetch TODOs. (Maybe due to rate limit. Please try later.)"; + console.log( + `Failed to get GitHub project info. Status: ${res.status}. Body: ${body}` ); - - const items = res.user.projectV2.items.nodes.map((node) => node.content); - - items.forEach((item) => { - if (item.__typename == "DraftIssue") { - item.closed = false; - } - const { title, closed } = item; + } else { + body.forEach((item) => { + const { status, title, color } = item; const li = document.createElement("li"); const span = document.createElement("span"); - span.textContent = closed ? "Done:" : "Todo:"; - span.style.color = closed ? "green" : "blue"; + span.textContent = status; + span.style.color = color; li.appendChild(span); li.append(title); todoContainer.appendChild(li); }); todoMessage.parentElement.removeChild(todoMessage); - } catch (e) { - todoMessage.style.color = "red"; - todoMessage.textContent = "Failed to fetch TODOs."; - console.log("Failed to get GitHub project info.", e); } }); diff --git a/template/docker-compose.yaml.template b/template/docker-compose.yaml.template index 3df0a18..8c854c5 100644 --- a/template/docker-compose.yaml.template +++ b/template/docker-compose.yaml.template @@ -76,6 +76,19 @@ services: - timeline-network - code-server-network - auto-certbot-network + - crupest-api-network + + crupest-api: + pull_policy: build + build: + context: ./docker/crupest-api + dockerfile: Dockerfile + pull: true + container_name: crupest-api + volumes: + - "./crupest-api-config.json:/config.json:ro" + networks: + - crupest-api-network auto-certbot: pull_policy: build @@ -158,3 +171,4 @@ networks: timeline-network: code-server-network: auto-certbot-network: + crupest-api-network: diff --git a/tool/test-crupest-api.py b/tool/test-crupest-api.py index 5cd461d..c89a0f9 100755 --- a/tool/test-crupest-api.py +++ b/tool/test-crupest-api.py @@ -1,45 +1,23 @@ #!/usr/bin/env python3 -import subprocess -import os -import sys -from os.path import * -import signal -from modules.path import * +import json import time -from rich.console import Console +from os.path import * from urllib.request import urlopen from http.client import * -import json +from rich.console import Console +from modules.path import * console = console = Console() -ensure_log_dir() - -dotnet_project = join(project_dir, "docker", "crupest-api", "CrupestApi") -dotnet_log_path = abspath(join(log_dir, "crupest-api-log")) -dotnet_config_path = abspath(join(project_dir, "crupest-api-config.json")) - -os.environ["CRUPEST_API_CONFIG_FILE"] = dotnet_log_path -os.environ["CRUPEST_API_LOG_FILE"] = dotnet_config_path - -popen = subprocess.Popen( - ["dotnet", "run", "--project", dotnet_project, "--launch-profile", "dev"] -) - -console.print("Sleep for 3s to wait for server startup.") -time.sleep(3) - def do_the_test(): res: HTTPResponse = urlopen("http://localhost:5188/api/todos") - console.print(res) body = res.read() - console.print(body) if res.status != 200: raise Exception("Status code is not 200.") - result = json.load(body) + result = json.loads(body) if not isinstance(result, list): raise Exception("Result is not an array.") if len(result) == 0: @@ -52,29 +30,10 @@ def do_the_test(): raise Exception("Result[0].status is not a string.") -for i in range(0, 2): - console.print(f"Test begin with attempt {i + 1}", style="cyan") - try: - do_the_test() - console.print("Test passed.", style="green") - popen.send_signal(signal.SIGTERM) - popen.wait() - exit(0) - except Exception as e: - console.print(e) - console.print( - "Test failed. Try again after sleep for 1s.", style="red") - time.sleep(1) - try: - console.print( - f"Test begin with attempt {i + 2}, also the final one.", style="cyan") do_the_test() - console.print("Test passed.", style="green") - popen.send_signal(signal.SIGTERM) - popen.wait() + console.print("Test passed!", style="green") exit(0) except Exception as e: console.print(e) - console.print("Final test failed.", style="red") - exit(1) + console.print("Test failed!", style="red") -- cgit v1.2.3