diff options
| author | crupest <crupest@outlook.com> | 2022-12-01 19:19:47 +0800 | 
|---|---|---|
| committer | crupest <crupest@outlook.com> | 2022-12-02 13:35:35 +0800 | 
| commit | b44f957e6d886387e9f275dfb623540ae0acd737 (patch) | |
| tree | dee523de3ff29067fa0202135a322079b295df4c /docker/crupest-api/CrupestApi | |
| parent | 462ae43246799469f0c6cdac5e45e1a9d0f5da2e (diff) | |
| download | crupest-b44f957e6d886387e9f275dfb623540ae0acd737.tar.gz crupest-b44f957e6d886387e9f275dfb623540ae0acd737.tar.bz2 crupest-b44f957e6d886387e9f275dfb623540ae0acd737.zip | |
Restructure crupest-api.
Diffstat (limited to 'docker/crupest-api/CrupestApi')
20 files changed, 307 insertions, 232 deletions
| diff --git a/docker/crupest-api/CrupestApi/.dockerignore b/docker/crupest-api/CrupestApi/.dockerignore index 7de5508..f1c182d 100644 --- a/docker/crupest-api/CrupestApi/.dockerignore +++ b/docker/crupest-api/CrupestApi/.dockerignore @@ -1,2 +1,2 @@ -obj -bin +*/obj +*/bin diff --git a/docker/crupest-api/CrupestApi/Config/TodosConfiguration.cs b/docker/crupest-api/CrupestApi/Config/TodosConfiguration.cs deleted file mode 100644 index 68f893a..0000000 --- a/docker/crupest-api/CrupestApi/Config/TodosConfiguration.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace CrupestApi.Config -{ -    public class TodoConfiguration -    { -        [Required] -        public string Username { get; set; } = default!; -        [Required] -        public int ProjectNumber { get; set; } = default!; -        [Required] -        public string Token { get; set; } = default!; -        public int? Count { get; set; } -    } -}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Database/CrupestApi.Database.csproj index 4348979..72a1294 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.csproj +++ b/docker/crupest-api/CrupestApi/CrupestApi.Database/CrupestApi.Database.csproj @@ -3,6 +3,7 @@    <PropertyGroup>
      <TargetFramework>net7.0</TargetFramework>
      <Nullable>enable</Nullable>
 +    <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
  </Project>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Database/DatabaseMigrationManager.cs b/docker/crupest-api/CrupestApi/CrupestApi.Database/DatabaseMigrationManager.cs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Database/DatabaseMigrationManager.cs diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Database/DatabaseService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Database/DatabaseService.cs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Database/DatabaseService.cs diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Files/CrupestApi.Files.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Files/CrupestApi.Files.csproj new file mode 100644 index 0000000..72a1294 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Files/CrupestApi.Files.csproj @@ -0,0 +1,9 @@ +<Project Sdk="Microsoft.NET.Sdk.Web">
 +
 +  <PropertyGroup>
 +    <TargetFramework>net7.0</TargetFramework>
 +    <Nullable>enable</Nullable>
 +    <ImplicitUsings>enable</ImplicitUsings>
 +  </PropertyGroup>
 +
 +</Project>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Files/FilesService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Files/FilesService.cs new file mode 100644 index 0000000..c851a92 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Files/FilesService.cs @@ -0,0 +1,6 @@ +namespace CrupestApi.Files; + +public class FilesService +{ +     +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/CrupestApi.Todos.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Todos/CrupestApi.Todos.csproj new file mode 100644 index 0000000..72a1294 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/CrupestApi.Todos.csproj @@ -0,0 +1,9 @@ +<Project Sdk="Microsoft.NET.Sdk.Web">
 +
 +  <PropertyGroup>
 +    <TargetFramework>net7.0</TargetFramework>
 +    <Nullable>enable</Nullable>
 +    <ImplicitUsings>enable</ImplicitUsings>
 +  </PropertyGroup>
 +
 +</Project>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosConfiguration.cs b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosConfiguration.cs new file mode 100644 index 0000000..e8160d2 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosConfiguration.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace CrupestApi.Todos; + +public class TodosConfiguration +{ +    [Required] +    public string Username { get; set; } = default!; +    [Required] +    public int ProjectNumber { get; set; } = default!; +    [Required] +    public string Token { get; set; } = default!; +    public int Count { get; set; } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosService.cs new file mode 100644 index 0000000..f5ccc09 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosService.cs @@ -0,0 +1,144 @@ +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 Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace CrupestApi.Todos; + +public class TodosItem +{ +    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 TodosService +{ +    private readonly IOptionsSnapshot<TodosConfiguration> _options; +    private readonly ILogger<TodosService> _logger; + +    public TodosService(IOptionsSnapshot<TodosConfiguration> options, ILogger<TodosService> logger) +    { +        _options = options; +        _logger = logger; +    } + +    private static string CreateGraphQLQuery(TodosConfiguration 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<List<TodosItem>> 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<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 result = new List<TodosItem>(); + +            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 TodosItem +                { +                    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); +        } +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosServiceCollectionExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosServiceCollectionExtensions.cs new file mode 100644 index 0000000..cdf174d --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace CrupestApi.Todos; + +public static class TodosServiceCollectionExtensions +{ +    public static IServiceCollection AddTodos(this IServiceCollection services) +    { +        services.AddOptions<TodosConfiguration>().BindConfiguration("Todos"); +        services.PostConfigure<TodosConfiguration>(config => +        { +            if (config.Count == 0) +            { +                config.Count = 20; +            } +        }); +        services.TryAddScoped<TodosService>(); +        return services; +    } +} + diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs new file mode 100644 index 0000000..575dc4f --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Todos/TodosWebApplicationExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.Json; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace CrupestApi.Todos; + +public static class TodosWebApplicationExtensions +{ +    public static WebApplication MapTodos(this WebApplication app, string path) +    { +        if (app is null) +        { +            throw new ArgumentNullException(nameof(app)); +        } + +        app.MapGet(path, async ([FromServices] TodosService todosService) => +        { +            var jsonOptions = new JsonSerializerOptions +            { +                PropertyNamingPolicy = JsonNamingPolicy.CamelCase +            }; + +            try +            { +                var todos = await todosService.GetTodosAsync(); +                return Results.Json(todos, jsonOptions, statusCode: 200); +            } +            catch (Exception e) +            { +                return Results.Json(new +                { +                    e.Message +                }, jsonOptions, statusCode: StatusCodes.Status503ServiceUnavailable); +            } +        }); + +        return app; +    } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.sln b/docker/crupest-api/CrupestApi/CrupestApi.sln new file mode 100644 index 0000000..f22c307 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.sln @@ -0,0 +1,28 @@ +
 +Microsoft Visual Studio Solution File, Format Version 12.00
 +# Visual Studio Version 17
 +VisualStudioVersion = 17.0.31903.59
 +MinimumVisualStudioVersion = 10.0.40219.1
 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi", "CrupestApi\CrupestApi.csproj", "{E30916BB-08F9-45F0-BC1A-69B66AE79913}"
 +EndProject
 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Todos", "CrupestApi.Todos\CrupestApi.Todos.csproj", "{BF9F5F71-AE65-4896-8E6F-FE0D4AD0E7D1}"
 +EndProject
 +Global
 +	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 +		Debug|Any CPU = Debug|Any CPU
 +		Release|Any CPU = Release|Any CPU
 +	EndGlobalSection
 +	GlobalSection(SolutionProperties) = preSolution
 +		HideSolutionNode = FALSE
 +	EndGlobalSection
 +	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 +		{E30916BB-08F9-45F0-BC1A-69B66AE79913}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 +		{E30916BB-08F9-45F0-BC1A-69B66AE79913}.Debug|Any CPU.Build.0 = Debug|Any CPU
 +		{E30916BB-08F9-45F0-BC1A-69B66AE79913}.Release|Any CPU.ActiveCfg = Release|Any CPU
 +		{E30916BB-08F9-45F0-BC1A-69B66AE79913}.Release|Any CPU.Build.0 = Release|Any CPU
 +		{BF9F5F71-AE65-4896-8E6F-FE0D4AD0E7D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 +		{BF9F5F71-AE65-4896-8E6F-FE0D4AD0E7D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
 +		{BF9F5F71-AE65-4896-8E6F-FE0D4AD0E7D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
 +		{BF9F5F71-AE65-4896-8E6F-FE0D4AD0E7D1}.Release|Any CPU.Build.0 = Release|Any CPU
 +	EndGlobalSection
 +EndGlobal
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi/CrupestApi.csproj b/docker/crupest-api/CrupestApi/CrupestApi/CrupestApi.csproj new file mode 100644 index 0000000..c15ec91 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi/CrupestApi.csproj @@ -0,0 +1,14 @@ +<Project Sdk="Microsoft.NET.Sdk.Web">
 +
 +  <ItemGroup> +    <ProjectReference Include="..\CrupestApi.Todos\CrupestApi.Todos.csproj" /> +    <ProjectReference Include="..\CrupestApi.Files\CrupestApi.Files.csproj" /> +  </ItemGroup>
 +
 +  <PropertyGroup>
 +    <TargetFramework>net7.0</TargetFramework>
 +    <Nullable>enable</Nullable>
 +    <ImplicitUsings>enable</ImplicitUsings>
 +  </PropertyGroup>
 +
 +</Project>
 diff --git a/docker/crupest-api/CrupestApi/CrupestApi/Program.cs b/docker/crupest-api/CrupestApi/CrupestApi/Program.cs new file mode 100644 index 0000000..e18252f --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi/Program.cs @@ -0,0 +1,17 @@ +using System;
 +using Microsoft.AspNetCore.Builder;
 +using Microsoft.Extensions.Configuration;
 +using CrupestApi.Todos;
 +
 +var builder = WebApplication.CreateBuilder(args);
 +
 +string configFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_CONFIG_FILE") ?? "/crupest-api-config.json";
 +builder.Configuration.AddJsonFile(configFilePath, optional: false, reloadOnChange: true);
 +
 +builder.Services.AddTodos();
 +
 +var app = builder.Build();
 +
 +app.MapTodos("/api/todos");
 +
 +app.Run();
 diff --git a/docker/crupest-api/CrupestApi/Properties/launchSettings.json b/docker/crupest-api/CrupestApi/CrupestApi/Properties/launchSettings.json index a4a5cbf..a4a5cbf 100644 --- a/docker/crupest-api/CrupestApi/Properties/launchSettings.json +++ b/docker/crupest-api/CrupestApi/CrupestApi/Properties/launchSettings.json diff --git a/docker/crupest-api/CrupestApi/appsettings.json b/docker/crupest-api/CrupestApi/CrupestApi/appsettings.json index 53753bd..53753bd 100644 --- a/docker/crupest-api/CrupestApi/appsettings.json +++ b/docker/crupest-api/CrupestApi/CrupestApi/appsettings.json diff --git a/docker/crupest-api/CrupestApi/Program.cs b/docker/crupest-api/CrupestApi/Program.cs deleted file mode 100644 index 9f7e7df..0000000 --- a/docker/crupest-api/CrupestApi/Program.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System;
 -using System.Text.Json;
 -using Microsoft.AspNetCore.Builder;
 -using Microsoft.AspNetCore.Mvc;
 -using Microsoft.Extensions.Configuration;
 -using Microsoft.AspNetCore.Http;
 -using Microsoft.Extensions.DependencyInjection.Extensions;
 -using Microsoft.Extensions.DependencyInjection;
 -using CrupestApi.Config;
 -using CrupestApi.Services;
 -
 -namespace CrupestApi
 -{
 -    internal class Program
 -    {
 -        private static void Main(string[] args)
 -        {
 -
 -            var builder = WebApplication.CreateBuilder(args);
 -
 -            string configFilePath = Environment.GetEnvironmentVariable("CRUPEST_API_CONFIG_FILE") ?? "/crupest-api-config.json";
 -            builder.Configuration.AddJsonFile(configFilePath, optional: false, reloadOnChange: true);
 -
 -            builder.Services.AddOptions<TodoConfiguration>();
 -            builder.Services.Configure<TodoConfiguration>(builder.Configuration.GetSection("Todos"));
 -            builder.Services.PostConfigure<TodoConfiguration>(config =>
 -            {
 -                if (config.Count is null)
 -                {
 -                    config.Count = 20;
 -                }
 -            });
 -            builder.Services.TryAddScoped<TodoService>();
 -
 -            var app = builder.Build();
 -
 -            app.MapGet("/api/todos", async ([FromServices] TodoService todoService) =>
 -            {
 -                try
 -                {
 -                    var todos = await todoService.GetTodosAsync();
 -                    return Results.Json(todos, new JsonSerializerOptions
 -                    {
 -                        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
 -                    }, statusCode: 200);
 -                }
 -                catch (Exception e)
 -                {
 -                    return Results.Json(new
 -                    {
 -                        e.Message
 -                    }, statusCode: StatusCodes.Status503ServiceUnavailable);
 -                }
 -            });
 -
 -            app.Run();
 -        }
 -    }
 -}
 diff --git a/docker/crupest-api/CrupestApi/Services/FileService.cs b/docker/crupest-api/CrupestApi/Services/FileService.cs deleted file mode 100644 index d862702..0000000 --- a/docker/crupest-api/CrupestApi/Services/FileService.cs +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index eb4fef6..0000000 --- a/docker/crupest-api/CrupestApi/Services/TodoService.cs +++ /dev/null @@ -1,146 +0,0 @@ -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<TodoConfiguration> _options; -        private readonly ILogger<TodoService> _logger; - -        public TodoService(IOptionsSnapshot<TodoConfiguration> options, ILogger<TodoService> 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<List<TodoItem>> 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<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 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 -                    { -                        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 | 
