From 3c918a6096e77190db83c998e777074493515326 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 30 Nov 2022 18:34:49 +0800 Subject: Prepare for file api. --- docker/crupest-api/CrupestApi/.gitignore | 1 + docker/crupest-api/CrupestApi/Program.cs | 136 +++---------------- .../CrupestApi/Properties/launchSettings.json | 4 +- .../crupest-api/CrupestApi/Services/FileService.cs | 10 ++ .../crupest-api/CrupestApi/Services/TodoService.cs | 146 +++++++++++++++++++++ 5 files changed, 179 insertions(+), 118 deletions(-) create mode 100644 docker/crupest-api/CrupestApi/Services/FileService.cs create mode 100644 docker/crupest-api/CrupestApi/Services/TodoService.cs (limited to 'docker/crupest-api') diff --git a/docker/crupest-api/CrupestApi/.gitignore b/docker/crupest-api/CrupestApi/.gitignore index 7de5508..f3d2489 100644 --- a/docker/crupest-api/CrupestApi/.gitignore +++ b/docker/crupest-api/CrupestApi/.gitignore @@ -1,2 +1,3 @@ obj bin +dev-config.json diff --git a/docker/crupest-api/CrupestApi/Program.cs b/docker/crupest-api/CrupestApi/Program.cs index 7fd6675..9f7e7df 100644 --- a/docker/crupest-api/CrupestApi/Program.cs +++ b/docker/crupest-api/CrupestApi/Program.cs @@ -1,152 +1,54 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Text.Json; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Net.Mime; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.DependencyInjection; using CrupestApi.Config; +using CrupestApi.Services; namespace CrupestApi { - 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) { - using var httpClient = new HttpClient(); var builder = WebApplication.CreateBuilder(args); - string configFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_CONFIG_FILE") ?? "/config.json"; - + string configFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_CONFIG_FILE") ?? "/crupest-api-config.json"; builder.Configuration.AddJsonFile(configFilePath, optional: false, reloadOnChange: true); - var app = builder.Build(); - - app.MapGet("/api/todos", async ([FromServices] IConfiguration configuration, [FromServices] ILoggerFactory loggerFactory) => + builder.Services.AddOptions(); + builder.Services.Configure(builder.Configuration.GetSection("Todos")); + builder.Services.PostConfigure(config => { - var logger = loggerFactory.CreateLogger("CrupestApi.Todos"); - - static string CreateGraphQLQuery(TodoConfiguration todoConfiguration) - { - return $$""" -{ - user(login: "{{todoConfiguration.Username}}") { - projectV2(number: {{todoConfiguration.ProjectNumber}}) { - items(last: {{todoConfiguration.Count ?? 20}}) { - nodes { - __typename - content { - __typename - ... on Issue { - title - closed - } - ... on PullRequest { - title - closed - } - ... on DraftIssue { - title - } - } - } - } - } - } - } -"""; - } - - var todoConfiguration = configuration.GetSection("Todos").Get(); - if (todoConfiguration is null) + if (config.Count is null) { - throw new Exception("Fail to get todos configuration."); + config.Count = 20; } + }); + builder.Services.TryAddScoped(); - 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); - 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); - + var app = builder.Build(); - if (response.IsSuccessStatusCode) + app.MapGet("/api/todos", async ([FromServices] TodoService todoService) => + { + try { - 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 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 - { - closed = false; - } - - result.Add(new TodoItem - { - Title = title, - Status = closed ? "Done" : "Todo", - Closed = closed, - color = closed ? "green" : "blue" - }); - } - - return Results.Json(result, new JsonSerializerOptions + var todos = await todoService.GetTodosAsync(); + return Results.Json(todos, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }, statusCode: 200); } - else + catch (Exception e) { - const string message = "Fail to get todos from GitHub."; - logger.LogError(message); - return Results.Json(new { - message + e.Message }, statusCode: StatusCodes.Status503ServiceUnavailable); } }); diff --git a/docker/crupest-api/CrupestApi/Properties/launchSettings.json b/docker/crupest-api/CrupestApi/Properties/launchSettings.json index 01f2a12..a4a5cbf 100644 --- a/docker/crupest-api/CrupestApi/Properties/launchSettings.json +++ b/docker/crupest-api/CrupestApi/Properties/launchSettings.json @@ -5,8 +5,10 @@ "commandName": "Project", "dotnetRunMessages": true, "applicationUrl": "http://localhost:5188", + "workingDirectory": ".", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "CRUPEST_API_CONFIG_FILE": "dev-config.json" } } } diff --git a/docker/crupest-api/CrupestApi/Services/FileService.cs b/docker/crupest-api/CrupestApi/Services/FileService.cs new file mode 100644 index 0000000..d862702 --- /dev/null +++ b/docker/crupest-api/CrupestApi/Services/FileService.cs @@ -0,0 +1,10 @@ +namespace CrupestApi.Services +{ + public class FileService + { + public FileService() + { + + } + } +} diff --git a/docker/crupest-api/CrupestApi/Services/TodoService.cs b/docker/crupest-api/CrupestApi/Services/TodoService.cs new file mode 100644 index 0000000..eb4fef6 --- /dev/null +++ b/docker/crupest-api/CrupestApi/Services/TodoService.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using CrupestApi.Config; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace CrupestApi.Services +{ + 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!; + } + + public class TodoService + { + private readonly IOptionsSnapshot _options; + private readonly ILogger _logger; + + public TodoService(IOptionsSnapshot options, ILogger logger) + { + _options = options; + _logger = logger; + } + + private static string CreateGraphQLQuery(TodoConfiguration todoConfiguration) + { + return $$""" +{ + user(login: "{{todoConfiguration.Username}}") { + projectV2(number: {{todoConfiguration.ProjectNumber}}) { + items(last: {{todoConfiguration.Count}}) { + nodes { + __typename + content { + __typename + ... on Issue { + title + closed + } + ... on PullRequest { + title + closed + } + ... on DraftIssue { + title + } + } + } + } + } + } +} +"""; + } + + + public async Task> GetTodosAsync() + { + var todoOptions = _options.Value; + if (todoOptions is null) + { + throw new Exception("Fail to get todos configuration."); + } + + _logger.LogInformation("Username: {}; ProjectNumber: {}; Count: {}", todoOptions.Username, todoOptions.ProjectNumber, todoOptions.Count); + _logger.LogInformation("Getting todos from GitHub GraphQL API..."); + + using var httpClient = new HttpClient(); + + using var requestContent = new StringContent(JsonSerializer.Serialize(new + { + query = CreateGraphQLQuery(todoOptions) + })); + 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", todoOptions.Token); + request.Headers.TryAddWithoutValidation("User-Agent", todoOptions.Username); + + using var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + _logger.LogInformation("GitHub server returned status code: {}", response.StatusCode); + _logger.LogInformation("GitHub server returned body: {}", responseBody); + + if (response.IsSuccessStatusCode) + { + 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 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 + { + closed = false; + } + + result.Add(new TodoItem + { + Title = title, + Status = closed ? "Done" : "Todo", + Closed = closed, + color = closed ? "green" : "blue" + }); + } + + return result; + } + else + { + const string message = "Fail to get todos from GitHub."; + _logger.LogError(message); + throw new Exception(message); + } + } + } +} \ No newline at end of file -- cgit v1.2.3