diff options
| -rw-r--r-- | docker/crupest-api/CrupestApi/Program.cs | 181 | ||||
| -rw-r--r-- | docker/crupest-nginx/sites/www/src/main.js | 62 | ||||
| -rw-r--r-- | template/docker-compose.yaml.template | 14 | ||||
| -rwxr-xr-x | 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<TodoConfiguration>();
 -            if (todoConfiguration is null)
 -            {
 -                throw new Exception("Fail to get todos configuration.");
 -            }
 +                var todoConfiguration = configuration.GetSection("Todos").Get<TodoConfiguration>();
 +                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<JsonDocument>(responseBody);
 -                if (responseJson is null)
 +                if (response.IsSuccessStatusCode)
                  {
 -                    throw new Exception("Fail to deserialize response body.");
 -                }
 +                    using var responseJson = JsonSerializer.Deserialize<JsonDocument>(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<TodoItem>();
 +                    var result = new List<TodoItem>();
 -                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") | 
