diff --git a/src/backend/server/src/Todo.Api/Controllers/AuthController.cs b/src/backend/server/src/Todo.Api/Controllers/AuthController.cs index 013a832..f2a0d39 100644 --- a/src/backend/server/src/Todo.Api/Controllers/AuthController.cs +++ b/src/backend/server/src/Todo.Api/Controllers/AuthController.cs @@ -1,66 +1,70 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Todo.Core.Interfaces.Persistence; -namespace Todo.Api.Controllers +namespace Todo.Api.Controllers; + +[ApiController] +[Route("api/auth")] +[Authorize] +[AllowAnonymous] +public class AuthController : ControllerBase { - [ApiController] - [Route("api/auth")] - [Authorize] - [AllowAnonymous] - public class AuthController : ControllerBase + private readonly IUserRepository _userRepository; + + public AuthController(IUserRepository userRepository) { - private readonly IUserRepository _userRepository; + _userRepository = userRepository; + } - public AuthController(IUserRepository userRepository) - { - _userRepository = userRepository; - } + [HttpPost("register")] + public async Task Register( + [FromBody] RegisterUserRequest request) + { + var user = await _userRepository.Register( + request.Email, + request.Password); - [HttpPost("register")] - public async Task Register([FromBody] RegisterUserRequest request) - { - var user = await _userRepository.Register(request.Email, request.Password); + return Ok(user); + } - return Ok(user); - } - - [HttpGet("login")] - public async Task Login([FromQuery] string returnUrl) + [HttpGet("login")] + public async Task Login([FromQuery] string returnUrl) + { + var props = new AuthenticationProperties { - var props = new AuthenticationProperties + RedirectUri = Url.Action(nameof(Callback)), + Items = { - RedirectUri = Url.Action(nameof(Callback)), - Items = { - {"returnUrl", returnUrl} + "returnUrl", returnUrl } - }; - return Challenge(props); - } - - [HttpGet] - public async Task Callback() - { - // read external identity from the temporary cookie - var result = - await HttpContext.AuthenticateAsync("oidc"); - if (result?.Succeeded != true) - { - throw new Exception("External authentication error"); } + }; + return Challenge(props); + } - var returnUrl = result.Properties?.Items["returnUrl"] ?? "~/"; - return Redirect(returnUrl); - } + [HttpGet] + public async Task Callback() + { + // read external identity from the temporary cookie + var result = + await HttpContext.AuthenticateAsync("oidc"); + if (result?.Succeeded != true) + throw new Exception("External authentication error"); - public record RegisterUserRequest - { - [Required] public string Email { get; init; } - [Required] public string Password { get; init; } - } + var returnUrl = result.Properties?.Items["returnUrl"] ?? "~/"; + return Redirect(returnUrl); + } + + public record RegisterUserRequest + { + [Required] + public string Email { get; init; } + + [Required] + public string Password { get; init; } } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Controllers/TodosController.cs b/src/backend/server/src/Todo.Api/Controllers/TodosController.cs index 26c1dca..84af365 100644 --- a/src/backend/server/src/Todo.Api/Controllers/TodosController.cs +++ b/src/backend/server/src/Todo.Api/Controllers/TodosController.cs @@ -19,25 +19,37 @@ public class TodosController : ControllerBase [HttpPost] [Authorize] - public async Task> CreateTodo([FromBody] CreateTodoRequest request) + public async Task> CreateTodo( + [FromBody] CreateTodoRequest request) { var userId = User.FindFirstValue("sub") ?? throw new InvalidOperationException("Could not get user, something has gone terribly wrong"); - - return Ok(await _todoRepository.CreateTodoAsync(request.Title, String.Empty, userId)); + + return Ok( + await _todoRepository.CreateTodoAsync( + request.Title, + string.Empty, + "", + userId)); } [HttpGet] [Authorize] - public async Task>> GetTodos() => - Ok(await _todoRepository.GetTodosAsync()); + public async Task>> GetTodos() + { + return Ok(await _todoRepository.GetTodosAsync()); + } [HttpGet("not-done")] - public async Task>> GetNotDoneTodos() => - Ok(await _todoRepository.GetNotDoneTodos()); + public async Task>> + GetNotDoneTodos() + { + return Ok(await _todoRepository.GetNotDoneTodos()); + } public record CreateTodoRequest { - [Required] public string Title { get; init; } + [Required] + public string Title { get; init; } } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Hubs/Models/TodoResponse.cs b/src/backend/server/src/Todo.Api/Hubs/Models/TodoResponse.cs index ca9f681..b392c28 100644 --- a/src/backend/server/src/Todo.Api/Hubs/Models/TodoResponse.cs +++ b/src/backend/server/src/Todo.Api/Hubs/Models/TodoResponse.cs @@ -5,14 +5,17 @@ namespace Todo.Api.Hubs.Models; public record TodoResponse { [JsonPropertyName("id")] - public string Id { get; init; } - + public string Id { get; init; } + [JsonPropertyName("title")] - public string Title { get; init; } - + public string Title { get; init; } + + [JsonPropertyName("description")] + public string? Description { get; init; } + [JsonPropertyName("status")] public bool Status { get; init; } [JsonPropertyName("project")] - public string Project { get; set; } + public string? Project { get; init; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Hubs/Models/UpdateTodoRequest.cs b/src/backend/server/src/Todo.Api/Hubs/Models/UpdateTodoRequest.cs index 84dbe01..44b40fc 100644 --- a/src/backend/server/src/Todo.Api/Hubs/Models/UpdateTodoRequest.cs +++ b/src/backend/server/src/Todo.Api/Hubs/Models/UpdateTodoRequest.cs @@ -5,14 +5,17 @@ namespace Todo.Api.Hubs.Models; public record UpdateTodoRequest { [JsonPropertyName("id")] - public string Id { get; init; } - + public string Id { get; init; } + [JsonPropertyName("title")] - public string Title { get; init; } - + public string Title { get; init; } + [JsonPropertyName("status")] public bool Status { get; init; } [JsonPropertyName("project")] - public string Project { get; set; } + public string? Project { get; init; } + + [JsonPropertyName("description")] + public string? Description { get; init; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs b/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs index 54e61dc..a377478 100644 --- a/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs +++ b/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs @@ -1,5 +1,3 @@ -using System.Collections.Concurrent; -using System.Security.Claims; using System.Text.Json; using MediatR; using Microsoft.AspNetCore.Authorization; @@ -9,17 +7,28 @@ using Todo.Core.Application.Commands.Todo; using Todo.Core.Application.Services.UserConnectionStore; using Todo.Core.Interfaces.Persistence; using Todo.Core.Interfaces.User; -using Todo.Infrastructure; namespace Todo.Api.Hubs; [Authorize] public class TodoHub : Hub { - private readonly ITodoRepository _todoRepository; - private readonly IUserConnectionStore _userConnectionStore; private readonly ICurrentUserService _currentUserService; private readonly IMediator _mediator; + private readonly ITodoRepository _todoRepository; + private readonly IUserConnectionStore _userConnectionStore; + + public TodoHub( + ITodoRepository todoRepository, + IUserConnectionStore userConnectionStore, + ICurrentUserService currentUserService, + IMediator mediator) + { + _todoRepository = todoRepository; + _userConnectionStore = userConnectionStore; + _currentUserService = currentUserService; + _mediator = mediator; + } public override Task OnConnectedAsync() { @@ -38,19 +47,10 @@ public class TodoHub : Hub return base.OnDisconnectedAsync(exception); } - public TodoHub( - ITodoRepository todoRepository, - IUserConnectionStore userConnectionStore, - ICurrentUserService currentUserService, - IMediator mediator) - { - _todoRepository = todoRepository; - _userConnectionStore = userConnectionStore; - _currentUserService = currentUserService; - _mediator = mediator; - } - - public async Task CreateTodo(string todoTitle, string? projectName) + public async Task CreateTodo( + string todoTitle, + string? projectName, + string? description) { if (todoTitle is null) throw new ArgumentException("title cannot be null"); @@ -58,7 +58,11 @@ public class TodoHub : Hub //var userId = GetUserId(); //var _ = await _todoRepository.CreateTodoAsync(todoTitle, projectName, userId); - await _mediator.Send(new CreateTodoCommand(todoTitle, projectName)); + await _mediator.Send( + new CreateTodoCommand( + todoTitle, + projectName, + description)); await GetInboxTodos(); } @@ -67,7 +71,10 @@ public class TodoHub : Hub public async Task UpdateTodo(string todoId, bool todoStatus) { var userId = GetUserId(); - await _todoRepository.UpdateTodoStatus(todoId, todoStatus, userId); + await _todoRepository.UpdateTodoStatus( + todoId, + todoStatus, + userId); await GetInboxTodos(); } @@ -75,78 +82,115 @@ public class TodoHub : Hub public async Task GetTodos() { var todos = await _todoRepository.GetTodosAsync(); - var serializedTodos = JsonSerializer.Serialize(todos - .Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project }) - .ToList()); + var serializedTodos = JsonSerializer.Serialize( + todos + .Select( + t => new TodoResponse + { + Id = t.Id, + Title = t.Title, + Status = t.Status, + Project = t.Project, + Description = t.Description + }) + .ToList()); - await RunOnUserConnections(async (connections) => - await Clients.Clients(connections).SendAsync("todos", serializedTodos)); + await RunOnUserConnections( + async connections => + await Clients.Clients(connections) + .SendAsync("todos", serializedTodos)); } public async Task GetInboxTodos() { var todos = await _todoRepository.GetNotDoneTodos(); - var serializedTodos = JsonSerializer.Serialize(todos - .Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project }) - .ToList()); + var serializedTodos = JsonSerializer.Serialize( + todos + .Select( + t => new TodoResponse + { + Id = t.Id, + Title = t.Title, + Status = t.Status, + Project = t.Project, + Description = t.Description + }) + .ToList()); - await RunOnUserConnections(async (connections) => - await Clients.Clients(connections).SendAsync("getInboxTodos", serializedTodos)); + await RunOnUserConnections( + async connections => + await Clients.Clients(connections) + .SendAsync("getInboxTodos", serializedTodos)); } public async Task GetTodo(string todoId) { var todo = await _todoRepository.GetTodoByIdAsync(todoId); - var serializedTodo = JsonSerializer.Serialize(new TodoResponse() - { - Id = todo.Id, - Project = todo.Project, - Status = todo.Status, - Title = todo.Title, - }); + var serializedTodo = JsonSerializer.Serialize( + new TodoResponse + { + Id = todo.Id, + Project = todo.Project, + Status = todo.Status, + Title = todo.Title, + Description = todo.Description + }); - await RunOnUserConnections(async (connections) => - await Clients.Clients(connections).SendAsync("getTodo", serializedTodo)); + await RunOnUserConnections( + async connections => + await Clients.Clients(connections) + .SendAsync("getTodo", serializedTodo)); } public async Task ReplaceTodo(string updateTodoRequest) { - var updateTodo = JsonSerializer.Deserialize(updateTodoRequest); + var updateTodo = + JsonSerializer.Deserialize(updateTodoRequest); if (updateTodo is null) throw new InvalidOperationException("Could not parse invalid updateTodo"); var userId = GetUserId(); - var updatedTodo = await _todoRepository.UpdateTodoAsync(new Core.Entities.Todo() - { - Id = updateTodo.Id, - Project = updateTodo.Project, - Status = updateTodo.Status, - Title = updateTodo.Title, - AuthorId = userId - }); + var updatedTodo = await _todoRepository.UpdateTodoAsync( + new Core.Entities.Todo + { + Id = updateTodo.Id, + Project = updateTodo.Project, + Status = updateTodo.Status, + Title = updateTodo.Title, + AuthorId = userId, + Description = updateTodo.Description + }); - var serializedTodo = JsonSerializer.Serialize(new TodoResponse() - { - Id = updatedTodo.Id, - Project = updatedTodo.Project, - Status = updatedTodo.Status, - Title = updatedTodo.Title, - }); + var serializedTodo = JsonSerializer.Serialize( + new TodoResponse + { + Id = updatedTodo.Id, + Project = updatedTodo.Project, + Status = updatedTodo.Status, + Title = updatedTodo.Title, + Description = updatedTodo.Description + }); - await RunOnUserConnections(async (connections) => - await Clients.Clients(connections).SendAsync("getTodo", serializedTodo)); + await RunOnUserConnections( + async connections => + await Clients.Clients(connections) + .SendAsync("getTodo", serializedTodo)); } - private async Task RunOnUserConnections(Func, Task> action) + private async Task RunOnUserConnections( + Func, Task> action) { var userId = GetUserId(); - var connections = await _userConnectionStore.GetConnectionsAsync(userId); + var connections = + await _userConnectionStore.GetConnectionsAsync(userId); await action(connections); } - private string GetUserId() => - _currentUserService.GetUserId() ?? - throw new InvalidOperationException("User id was invalid. Something has gone terribly wrong"); + private string GetUserId() + { + return _currentUserService.GetUserId() ?? + throw new InvalidOperationException("User id was invalid. Something has gone terribly wrong"); + } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Program.cs b/src/backend/server/src/Todo.Api/Program.cs index 9e2d293..df14596 100644 --- a/src/backend/server/src/Todo.Api/Program.cs +++ b/src/backend/server/src/Todo.Api/Program.cs @@ -3,19 +3,20 @@ global using System.Collections.Generic; global using System.Linq; global using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -namespace Todo.Api +namespace Todo.Api; + +public class Program { - public class Program + public static void Main(string[] args) { - public static void Main(string[] args) => - CreateHostBuilder(args).Build().Run(); + CreateHostBuilder(args).Build().Run(); + } - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Properties/launchSettings.json b/src/backend/server/src/Todo.Api/Properties/launchSettings.json index 63ce39b..bf2325a 100644 --- a/src/backend/server/src/Todo.Api/Properties/launchSettings.json +++ b/src/backend/server/src/Todo.Api/Properties/launchSettings.json @@ -23,7 +23,7 @@ "MONGODB__Host": "localhost", "MONGODB__Port": "27017", "GITEA__Url": "https://git.front.kjuulh.io", - "GITEA__ClientId": "6982ef4f-cfc1-431c-a442-fad98355a059", + "GITEA__ClientId": "6982ef4f-cfc1-431c-a442-fad98355a059", "GITEA__ClientSecret": "hXUrUz5xPhC7IE3dQKft9lHboBEwhNC8yFjSzKgF9Nyr" } } diff --git a/src/backend/server/src/Todo.Api/Publishers/TodoPublisher.cs b/src/backend/server/src/Todo.Api/Publishers/TodoPublisher.cs index 2b3b998..09a3b17 100644 --- a/src/backend/server/src/Todo.Api/Publishers/TodoPublisher.cs +++ b/src/backend/server/src/Todo.Api/Publishers/TodoPublisher.cs @@ -10,15 +10,15 @@ namespace Todo.Api.Publishers; public class TodoPublisher : ITodoPublisher { - private readonly IHubContext _hubContext; private readonly ICurrentUserService _currentUserService; - private readonly IUserConnectionStore _userConnectionStore; + private readonly IHubContext _hubContext; private readonly ILogger _logger; + private readonly IUserConnectionStore _userConnectionStore; public TodoPublisher( IHubContext hubContext, - ICurrentUserService currentUserService, - IUserConnectionStore userConnectionStore, + ICurrentUserService currentUserService, + IUserConnectionStore userConnectionStore, ILogger logger) { _hubContext = hubContext; @@ -26,17 +26,24 @@ public class TodoPublisher : ITodoPublisher _userConnectionStore = userConnectionStore; _logger = logger; } - - public async Task Publish(string todoId, CancellationToken cancellationToken) + + public async Task Publish( + string todoId, + CancellationToken cancellationToken) { - var userId = _currentUserService.GetUserId() ?? throw new InvalidOperationException("Cannot proceed without user"); - var connections = await _userConnectionStore.GetConnectionsAsync(userId); - - await _hubContext - .Clients - .Clients(connections) - .SendAsync("todoCreated", todoId , cancellationToken); - - _logger.LogInformation("todo created {TodoId}", todoId); + var userId = _currentUserService.GetUserId() ?? + throw new InvalidOperationException("Cannot proceed without user"); + var connections = + await _userConnectionStore.GetConnectionsAsync(userId); + + await _hubContext + .Clients + .Clients(connections) + .SendAsync( + "todoCreated", + todoId, + cancellationToken); + + _logger.LogInformation("todo created {TodoId}", todoId); } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Services/HttpContextCurrentUserService.cs b/src/backend/server/src/Todo.Api/Services/HttpContextCurrentUserService.cs index 11a66f7..fa7a472 100644 --- a/src/backend/server/src/Todo.Api/Services/HttpContextCurrentUserService.cs +++ b/src/backend/server/src/Todo.Api/Services/HttpContextCurrentUserService.cs @@ -8,10 +8,14 @@ public class HttpContextCurrentUserService : ICurrentUserService { private readonly IHttpContextAccessor _httpContextAccessor; - public HttpContextCurrentUserService(IHttpContextAccessor httpContextAccessor) + public HttpContextCurrentUserService( + IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } - - public string? GetUserId() => _httpContextAccessor.HttpContext?.User.FindFirstValue("sub"); + + public string? GetUserId() + { + return _httpContextAccessor.HttpContext?.User.FindFirstValue("sub"); + } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Startup.cs b/src/backend/server/src/Todo.Api/Startup.cs index c791736..e31ef48 100644 --- a/src/backend/server/src/Todo.Api/Startup.cs +++ b/src/backend/server/src/Todo.Api/Startup.cs @@ -10,112 +10,134 @@ using Microsoft.OpenApi.Models; using Todo.Api.Hubs; using Todo.Api.Publishers; using Todo.Api.Services; +using Todo.Core; +using Todo.Core.Interfaces.Publisher; using Todo.Core.Interfaces.User; using Todo.Infrastructure; using Todo.Persistence; using Todo.Persistence.Mongo; -using Todo.Core; -using Todo.Core.Interfaces.Publisher; -namespace Todo.Api +namespace Todo.Api; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + Configuration = configuration; + } - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddHttpContextAccessor(); - services.AddCors(options => + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddHttpContextAccessor(); + services.AddCors( + options => { - options.AddDefaultPolicy(builder => - builder.WithOrigins("http://localhost:3000", "https://todo.front.kjuulh.io") - .AllowCredentials() - .AllowAnyHeader() - .AllowAnyMethod()); + options.AddDefaultPolicy( + builder => + builder.WithOrigins( + "http://localhost:3000", + "https://todo.front.kjuulh.io") + .AllowCredentials() + .AllowAnyHeader() + .AllowAnyMethod()); }); - services.AddCore(); - services.AddScoped(); - services.AddScoped(); + services.AddCore(); + services + .AddScoped(); + services.AddScoped(); - services.AddSwaggerGen(c => + services.AddSwaggerGen( + c => { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "Todo.Api", Version = "v1" }); + c.SwaggerDoc( + "v1", + new OpenApiInfo + { + Title = "Todo.Api", + Version = "v1" + }); }); - JwtSecurityTokenHandler.DefaultMapInboundClaims = false; + JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - services.AddInfrastructure(Configuration); + services.AddInfrastructure(Configuration); - services.AddPersistence(Configuration, out var mongoDbOptions); - services - .AddHealthChecks() - .AddMongoDb(MongoDbConnectionHandler.FormatConnectionString(mongoDbOptions)); - services.AddSignalR(); + services.AddPersistence(Configuration, out var mongoDbOptions); + services + .AddHealthChecks() + .AddMongoDb( + MongoDbConnectionHandler + .FormatConnectionString(mongoDbOptions)); + services.AddSignalR(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.MigrateMongoDb(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI( + c => c.SwaggerEndpoint( + "/swagger/v1/swagger.json", + "Todo.Api v1")); } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.MigrateMongoDb(); + app.UseRouting(); + app.UseCors(); + app.UseInfrastructure(); + app.UseAuthentication(); + app.UseAuthorization(); - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Todo.Api v1")); - } - - app.UseRouting(); - app.UseCors(); - app.UseInfrastructure(); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => + app.UseEndpoints( + endpoints => { endpoints.MapControllers(); endpoints.MapHub("/hubs/todo"); - endpoints.MapHealthChecks("/health/live", new HealthCheckOptions() - { - ResponseWriter = async (context, report) => + endpoints.MapHealthChecks( + "/health/live", + new HealthCheckOptions { - var response = new HealthCheckResponse() + ResponseWriter = async (context, report) => { - Status = report.Status.ToString(), - HealthChecks = report.Entries.Select(x => new IndividualHealthCheckResponse + var response = new HealthCheckResponse { - Component = x.Key, - Status = x.Value.Status.ToString(), - Description = x.Value.Description - }), - HealthCheckDuration = report.TotalDuration - }; - await context.Response.WriteAsJsonAsync(response); - } - }); + Status = report.Status.ToString(), + HealthChecks = report.Entries.Select( + x => new IndividualHealthCheckResponse + { + Component = x.Key, + Status = x.Value.Status.ToString(), + Description = x.Value.Description + }), + HealthCheckDuration = report.TotalDuration + }; + await context.Response.WriteAsJsonAsync(response); + } + }); }); - } + } - private class HealthCheckResponse - { - public string Status { get; set; } - public IEnumerable HealthChecks { get; set; } - public TimeSpan HealthCheckDuration { get; set; } - } + private class HealthCheckResponse + { + public string Status { get; set; } - private class IndividualHealthCheckResponse - { - public string Status { get; set; } - public string Component { get; set; } - public string Description { get; set; } - } + public IEnumerable HealthChecks { get; set; } + + public TimeSpan HealthCheckDuration { get; set; } + } + + private class IndividualHealthCheckResponse + { + public string Status { get; set; } + public string Component { get; set; } + public string Description { get; set; } } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Api/Todo.Api.csproj b/src/backend/server/src/Todo.Api/Todo.Api.csproj index 1e68ddb..af582bd 100644 --- a/src/backend/server/src/Todo.Api/Todo.Api.csproj +++ b/src/backend/server/src/Todo.Api/Todo.Api.csproj @@ -7,13 +7,13 @@ - + - - - + + + diff --git a/src/backend/server/src/Todo.Core/Application/Commands/Todo/CreateTodoCommand.cs b/src/backend/server/src/Todo.Core/Application/Commands/Todo/CreateTodoCommand.cs index 2c6bff4..6f86737 100644 --- a/src/backend/server/src/Todo.Core/Application/Commands/Todo/CreateTodoCommand.cs +++ b/src/backend/server/src/Todo.Core/Application/Commands/Todo/CreateTodoCommand.cs @@ -6,30 +6,44 @@ using Todo.Core.Interfaces.User; namespace Todo.Core.Application.Commands.Todo; -public record CreateTodoCommand(string TodoTitle, string? TodoProject) : IRequest +public record CreateTodoCommand( + string TodoTitle, + string? TodoProject, + string? TodoDescription) : IRequest { internal class Handler : IRequestHandler { private readonly ICurrentUserService _currentUserService; - private readonly ITodoRepository _todoRepository; private readonly IMediator _mediator; + private readonly ITodoRepository _todoRepository; - public Handler(ICurrentUserService currentUserService, ITodoRepository todoRepository, IMediator mediator) + public Handler( + ICurrentUserService currentUserService, + ITodoRepository todoRepository, + IMediator mediator) { _currentUserService = currentUserService; _todoRepository = todoRepository; _mediator = mediator; } - public async Task Handle(CreateTodoCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateTodoCommand request, + CancellationToken cancellationToken) { var userId = _currentUserService.GetUserId(); if (userId is null) throw new InvalidOperationException("User was not found"); - var todo = await _todoRepository.CreateTodoAsync(request.TodoTitle, request.TodoProject, userId); + var todo = await _todoRepository.CreateTodoAsync( + request.TodoTitle, + request.TodoProject, + request.TodoDescription, + userId); - await _mediator.Publish(new TodoCreated(todo.Id), cancellationToken); + await _mediator.Publish( + new TodoCreated(todo.Id), + cancellationToken); return todo.Id; } diff --git a/src/backend/server/src/Todo.Core/Application/Notifications/Todo/TodoCreated.cs b/src/backend/server/src/Todo.Core/Application/Notifications/Todo/TodoCreated.cs index 5f538db..8d860ad 100644 --- a/src/backend/server/src/Todo.Core/Application/Notifications/Todo/TodoCreated.cs +++ b/src/backend/server/src/Todo.Core/Application/Notifications/Todo/TodoCreated.cs @@ -2,14 +2,12 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using MediatR; -using Microsoft.AspNetCore.SignalR; -using Todo.Core.Application.Services.UserConnectionStore; using Todo.Core.Interfaces.Publisher; -using Todo.Core.Interfaces.User; namespace Todo.Core.Application.Notifications.Todo; -public record TodoCreated([property: JsonPropertyName("todoId")] string TodoId) : INotification +public record TodoCreated( + [property: JsonPropertyName("todoId")] string TodoId) : INotification { internal class Handler : INotificationHandler { @@ -20,9 +18,13 @@ public record TodoCreated([property: JsonPropertyName("todoId")] string TodoId) _todoPublisher = todoPublisher; } - public async Task Handle(TodoCreated notification, CancellationToken cancellationToken) + public async Task Handle( + TodoCreated notification, + CancellationToken cancellationToken) { - await _todoPublisher.Publish(JsonSerializer.Serialize(notification), cancellationToken); + await _todoPublisher.Publish( + JsonSerializer.Serialize(notification), + cancellationToken); } } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Application/Publisher/ITodoPublisher.cs b/src/backend/server/src/Todo.Core/Application/Publisher/ITodoPublisher.cs index 754ab05..dbe0157 100644 --- a/src/backend/server/src/Todo.Core/Application/Publisher/ITodoPublisher.cs +++ b/src/backend/server/src/Todo.Core/Application/Publisher/ITodoPublisher.cs @@ -4,5 +4,5 @@ namespace Todo.Core.Interfaces.Publisher; public interface ITodoPublisher { - Task Publish(string todoId, CancellationToken cancellationToken = new ()); + Task Publish(string todoId, CancellationToken cancellationToken = new()); } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Application/Services/UserConnectionStore/IUserConnectionStore.cs b/src/backend/server/src/Todo.Core/Application/Services/UserConnectionStore/IUserConnectionStore.cs index 0d9f3e4..77feeae 100644 --- a/src/backend/server/src/Todo.Core/Application/Services/UserConnectionStore/IUserConnectionStore.cs +++ b/src/backend/server/src/Todo.Core/Application/Services/UserConnectionStore/IUserConnectionStore.cs @@ -5,4 +5,4 @@ public interface IUserConnectionStore Task AddAsync(string userId, string connectionId); Task RemoveAsync(string userId, string connectionId); Task> GetConnectionsAsync(string userId); -} +} \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/DependencyInjection.cs b/src/backend/server/src/Todo.Core/DependencyInjection.cs index 6d62ced..d0c9cd0 100644 --- a/src/backend/server/src/Todo.Core/DependencyInjection.cs +++ b/src/backend/server/src/Todo.Core/DependencyInjection.cs @@ -10,5 +10,8 @@ namespace Todo.Core; public static class DependencyInjection { - public static IServiceCollection AddCore(this IServiceCollection services) => services.AddMediatR(Assembly.GetExecutingAssembly()); + public static IServiceCollection AddCore(this IServiceCollection services) + { + return services.AddMediatR(Assembly.GetExecutingAssembly()); + } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Entities/Todo.cs b/src/backend/server/src/Todo.Core/Entities/Todo.cs index 5ab0959..fcc52b2 100644 --- a/src/backend/server/src/Todo.Core/Entities/Todo.cs +++ b/src/backend/server/src/Todo.Core/Entities/Todo.cs @@ -5,6 +5,7 @@ public record Todo public string Id { get; init; } public string Title { get; init; } public bool Status { get; init; } - public string Project { get; init; } + public string? Project { get; init; } public string AuthorId { get; init; } + public string? Description { get; init; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Entities/User.cs b/src/backend/server/src/Todo.Core/Entities/User.cs index 6f98979..86f81ef 100644 --- a/src/backend/server/src/Todo.Core/Entities/User.cs +++ b/src/backend/server/src/Todo.Core/Entities/User.cs @@ -1,8 +1,7 @@ -namespace Todo.Core.Entities +namespace Todo.Core.Entities; + +public class User { - public class User - { - public string Id { get; set; } - public string Email { get; set; } - } + public string Id { get; set; } + public string Email { get; set; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Interfaces/Persistence/ITodoRepository.cs b/src/backend/server/src/Todo.Core/Interfaces/Persistence/ITodoRepository.cs index e25b0ac..8aef0ee 100644 --- a/src/backend/server/src/Todo.Core/Interfaces/Persistence/ITodoRepository.cs +++ b/src/backend/server/src/Todo.Core/Interfaces/Persistence/ITodoRepository.cs @@ -1,11 +1,20 @@ - namespace Todo.Core.Interfaces.Persistence; public interface ITodoRepository { - Task CreateTodoAsync(string title, string projectName, string userId); + Task CreateTodoAsync( + string title, + string? projectName, + string? description, + string userId); + Task> GetTodosAsync(); - Task UpdateTodoStatus(string todoId, bool todoStatus, string userId); + + Task UpdateTodoStatus( + string todoId, + bool todoStatus, + string userId); + Task> GetNotDoneTodos(); Task GetTodoByIdAsync(string todoId); Task UpdateTodoAsync(Entities.Todo todo); diff --git a/src/backend/server/src/Todo.Core/Interfaces/Persistence/IUserRepository.cs b/src/backend/server/src/Todo.Core/Interfaces/Persistence/IUserRepository.cs index 87ee453..07df12f 100644 --- a/src/backend/server/src/Todo.Core/Interfaces/Persistence/IUserRepository.cs +++ b/src/backend/server/src/Todo.Core/Interfaces/Persistence/IUserRepository.cs @@ -1,5 +1,3 @@ -using Todo.Core.Entities; - namespace Todo.Core.Interfaces.Persistence; public interface IUserRepository diff --git a/src/backend/server/src/Todo.Core/Todo.Core.csproj b/src/backend/server/src/Todo.Core/Todo.Core.csproj index 823c1a6..e888c21 100644 --- a/src/backend/server/src/Todo.Core/Todo.Core.csproj +++ b/src/backend/server/src/Todo.Core/Todo.Core.csproj @@ -4,18 +4,18 @@ net6.0 enable - + - + - + - - + + diff --git a/src/backend/server/src/Todo.Infrastructure/DependencyInjection.cs b/src/backend/server/src/Todo.Infrastructure/DependencyInjection.cs index 5647920..8ab039f 100644 --- a/src/backend/server/src/Todo.Infrastructure/DependencyInjection.cs +++ b/src/backend/server/src/Todo.Infrastructure/DependencyInjection.cs @@ -10,7 +10,9 @@ namespace Todo.Infrastructure; public static class DependencyInjection { - public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddInfrastructure( + this IServiceCollection services, + IConfiguration configuration) { var giteaAuthOptions = new GiteaAuthOptions(); var giteaOptions = configuration.GetRequiredSection("GITEA"); @@ -21,35 +23,49 @@ public static class DependencyInjection .Bind(giteaOptions) .ValidateDataAnnotations(); - services.AddSingleton(); + services + .AddSingleton(); - return services.AddAuthentication(options => - { - options.DefaultScheme = "Cookies"; - options.DefaultChallengeScheme = "oidc"; - }) + return services.AddAuthentication( + options => + { + options.DefaultScheme = "Cookies"; + options.DefaultChallengeScheme = "oidc"; + }) .AddCookie("Cookies") - .AddOpenIdConnect("oidc", options => - { - options.Authority = giteaAuthOptions.Url; - options.ClientId = giteaAuthOptions.ClientId; - options.ClientSecret = giteaAuthOptions.ClientSecret; - options.ResponseType = "code"; + .AddOpenIdConnect( + "oidc", + options => + { + options.Authority = giteaAuthOptions.Url; + options.ClientId = giteaAuthOptions.ClientId; + options.ClientSecret = giteaAuthOptions.ClientSecret; + options.ResponseType = "code"; - options.SaveTokens = true; - }).Services; + options.SaveTokens = true; + }) + .Services; } - public static IApplicationBuilder UseInfrastructure(this IApplicationBuilder app) => app.UseCookiePolicy( - new CookiePolicyOptions - { - Secure = CookieSecurePolicy.Always - }); + public static IApplicationBuilder UseInfrastructure( + this IApplicationBuilder app) + { + return app.UseCookiePolicy( + new CookiePolicyOptions + { + Secure = CookieSecurePolicy.Always + }); + } } public class GiteaAuthOptions { - [Required] public string Url { get; set; } - [Required] public string ClientId { get; init; } - [Required] public string ClientSecret { get; init; } + [Required] + public string Url { get; set; } + + [Required] + public string ClientId { get; init; } + + [Required] + public string ClientSecret { get; init; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Infrastructure/Todo.Infrastructure.csproj b/src/backend/server/src/Todo.Infrastructure/Todo.Infrastructure.csproj index 39c1189..58f8eb7 100644 --- a/src/backend/server/src/Todo.Infrastructure/Todo.Infrastructure.csproj +++ b/src/backend/server/src/Todo.Infrastructure/Todo.Infrastructure.csproj @@ -5,17 +5,17 @@ enable enable - + - + - + - + - + - + diff --git a/src/backend/server/src/Todo.Infrastructure/UserConnectionStore/InMemoryUserConnectionStore.cs b/src/backend/server/src/Todo.Infrastructure/UserConnectionStore/InMemoryUserConnectionStore.cs index e580df6..ca4bae7 100644 --- a/src/backend/server/src/Todo.Infrastructure/UserConnectionStore/InMemoryUserConnectionStore.cs +++ b/src/backend/server/src/Todo.Infrastructure/UserConnectionStore/InMemoryUserConnectionStore.cs @@ -3,9 +3,10 @@ using Todo.Core.Application.Services.UserConnectionStore; namespace Todo.Infrastructure.UserConnectionStore; -class InMemoryUserConnectionStore : IUserConnectionStore +internal class InMemoryUserConnectionStore : IUserConnectionStore { - private static readonly ConcurrentDictionary> ConnectedUsers = new(); + private static readonly ConcurrentDictionary> + ConnectedUsers = new(); public Task AddAsync(string userId, string connectionId) { @@ -27,7 +28,10 @@ class InMemoryUserConnectionStore : IUserConnectionStore public Task> GetConnectionsAsync(string userId) { ConnectedUsers.TryGetValue(userId, out var connections); - return Task.FromResult(connections is null ? new List().AsEnumerable() : connections.AsEnumerable()); + return Task.FromResult( + connections is null + ? new List().AsEnumerable() + : connections.AsEnumerable()); } public Task RemoveAsync(string userId, string connectionId) @@ -39,12 +43,10 @@ class InMemoryUserConnectionStore : IUserConnectionStore // If there are no connection ids in the List, delete the user from the global cache (ConnectedUsers). if (existingUserConnectionIds?.Count == 0) - { // if there are no connections for the user, // just delete the userName key from the ConnectedUsers concurent dictionary ConnectedUsers.TryRemove(userId, out _); - } return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/DependencyInjection.cs b/src/backend/server/src/Todo.Persistence/DependencyInjection.cs index 29aa794..3c1bced 100644 --- a/src/backend/server/src/Todo.Persistence/DependencyInjection.cs +++ b/src/backend/server/src/Todo.Persistence/DependencyInjection.cs @@ -4,32 +4,32 @@ global using System.Linq; global using System.Collections.Generic; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; using Todo.Core.Interfaces.Persistence; using Todo.Persistence.Mongo; using Todo.Persistence.Mongo.Repositories; -namespace Todo.Persistence +namespace Todo.Persistence; + +public static class DependencyInjection { - public static class DependencyInjection + public static IServiceCollection AddPersistence( + this IServiceCollection services, + IConfiguration configuration, + out MongoDbOptions mongoDbOptions) { - public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration configuration, - out MongoDbOptions mongoDbOptions) - { - var exportableMongoDbOptions = new MongoDbOptions(); - var options = configuration.GetRequiredSection("MONGODB"); - options.Bind(exportableMongoDbOptions); - mongoDbOptions = exportableMongoDbOptions; + var exportableMongoDbOptions = new MongoDbOptions(); + var options = configuration.GetRequiredSection("MONGODB"); + options.Bind(exportableMongoDbOptions); + mongoDbOptions = exportableMongoDbOptions; - services - .AddOptions() - .Bind(options) - .ValidateDataAnnotations(); + services + .AddOptions() + .Bind(options) + .ValidateDataAnnotations(); - return services - .AddSingleton() - .AddScoped() - .AddScoped(); - } + return services + .AddSingleton() + .AddScoped() + .AddScoped(); } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Mongo/Migrations.cs b/src/backend/server/src/Todo.Persistence/Mongo/Migrations.cs index d0cf070..b55fe0d 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/Migrations.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/Migrations.cs @@ -3,33 +3,42 @@ using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using Todo.Persistence.Mongo.Repositories.Dtos; -namespace Todo.Persistence.Mongo +namespace Todo.Persistence.Mongo; + +public static class Migrations { - public static class Migrations + public static IApplicationBuilder MigrateMongoDb( + this IApplicationBuilder app) { - public static IApplicationBuilder MigrateMongoDb(this IApplicationBuilder app) - { - Task.Run(async () => - { - var connectionHandler = app.ApplicationServices.GetRequiredService(); - var database = connectionHandler.CreateDatabaseConnection(); + Task.Run( + async () => + { + var connectionHandler = app.ApplicationServices + .GetRequiredService(); + var database = connectionHandler.CreateDatabaseConnection(); - await CreateIndexes(database); - }).Wait(10000); + await CreateIndexes(database); + }) + .Wait(10000); - return app; - } + return app; + } - private static async Task CreateIndexes(IMongoDatabase mongoDatabase) - { - Console.WriteLine("Running: CreateIndexes"); - var collection = mongoDatabase.GetCollection("users"); - var indexKeysDefinition = Builders.IndexKeys.Ascending(user => user.Email); - var indexModel = - new CreateIndexModel(indexKeysDefinition, new CreateIndexOptions() { Unique = true }); + private static async Task CreateIndexes(IMongoDatabase mongoDatabase) + { + Console.WriteLine("Running: CreateIndexes"); + var collection = mongoDatabase.GetCollection("users"); + var indexKeysDefinition = + Builders.IndexKeys.Ascending(user => user.Email); + var indexModel = + new CreateIndexModel( + indexKeysDefinition, + new CreateIndexOptions + { + Unique = true + }); - await collection.Indexes.CreateOneAsync(indexModel); - Console.WriteLine("Finished: CreateIndexes"); - } + await collection.Indexes.CreateOneAsync(indexModel); + Console.WriteLine("Finished: CreateIndexes"); } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Mongo/MongoDbConnectionHandler.cs b/src/backend/server/src/Todo.Persistence/Mongo/MongoDbConnectionHandler.cs index d85f230..5063489 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/MongoDbConnectionHandler.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/MongoDbConnectionHandler.cs @@ -7,7 +7,8 @@ public class MongoDbConnectionHandler { private readonly IOptionsMonitor _optionsMonitor; - public MongoDbConnectionHandler(IOptionsMonitor optionsMonitor) + public MongoDbConnectionHandler( + IOptionsMonitor optionsMonitor) { _optionsMonitor = optionsMonitor; } @@ -19,7 +20,8 @@ public class MongoDbConnectionHandler return CreateConnectionFromOptions(options); } - private static IMongoDatabase CreateConnectionFromOptions(MongoDbOptions options) + private static IMongoDatabase CreateConnectionFromOptions( + MongoDbOptions options) { var conn = new MongoClient(FormatConnectionString(options)); var database = conn.GetDatabase(options.Database); @@ -27,6 +29,9 @@ public class MongoDbConnectionHandler return database; } - public static string FormatConnectionString(MongoDbOptions options) => - $"mongodb://{options.Username}:{options.Password}@{options.Host}:{options.Port}"; + public static string FormatConnectionString(MongoDbOptions options) + { + return + $"mongodb://{options.Username}:{options.Password}@{options.Host}:{options.Port}"; + } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Mongo/MongoDbOptions.cs b/src/backend/server/src/Todo.Persistence/Mongo/MongoDbOptions.cs index ea6ce42..7bdb82a 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/MongoDbOptions.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/MongoDbOptions.cs @@ -6,9 +6,18 @@ public class MongoDbOptions { public const string MongoDb = "MongoDb"; - [Required] public string Username { get; set; } - [Required] public string Password { get; set; } - [Required] public string Host { get; set; } - [Required] public string Port { get; set; } - [Required] public string Database { get; set; } + [Required] + public string Username { get; set; } + + [Required] + public string Password { get; set; } + + [Required] + public string Host { get; set; } + + [Required] + public string Port { get; set; } + + [Required] + public string Database { get; set; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs index cf91a54..8292af2 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs @@ -9,8 +9,14 @@ public record MongoTodo [BsonRepresentation(BsonType.ObjectId)] public string Id { get; init; } - [BsonRequired] public string Title { get; init; } - [BsonRequired] public bool Status { get; set; } - public string ProjectName { get; set; } = String.Empty; + [BsonRequired] + public string Title { get; init; } + + public string? Description { get; init; } + + [BsonRequired] + public bool Status { get; set; } + + public string? ProjectName { get; set; } = string.Empty; public string AuthorId { get; set; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoUser.cs b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoUser.cs index 6b88d82..a11ba0f 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoUser.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/Dtos/MongoUser.cs @@ -1,15 +1,17 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace Todo.Persistence.Mongo.Repositories.Dtos -{ - public record MongoUser - { - [BsonId] - [BsonRepresentation(BsonType.ObjectId)] - public string Id { get; init; } +namespace Todo.Persistence.Mongo.Repositories.Dtos; - [BsonRequired] public string Email { get; init; } - [BsonRequired] public string Password { get; set; } - } +public record MongoUser +{ + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; init; } + + [BsonRequired] + public string Email { get; init; } + + [BsonRequired] + public string Password { get; set; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/TodoRepository.cs b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/TodoRepository.cs index 57e23a5..68dd2b2 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/TodoRepository.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/TodoRepository.cs @@ -15,12 +15,28 @@ public class TodoRepository : ITodoRepository _todosCollection = database.GetCollection("todos"); } - public async Task CreateTodoAsync(string title, string projectName, string userId) + public async Task CreateTodoAsync( + string title, + string projectName, + string? description, + string userId) { - var todo = new MongoTodo() { Title = title, ProjectName = projectName, AuthorId = userId }; + var todo = new MongoTodo + { + Title = title, + ProjectName = projectName, + AuthorId = userId, + Description = description + }; await _todosCollection.InsertOneAsync(todo); - return new Core.Entities.Todo() - { Id = todo.Id, Title = todo.Title, Status = false, Project = todo.ProjectName }; + return new Core.Entities.Todo + { + Id = todo.Id, + Title = todo.Title, + Status = false, + Project = todo.ProjectName, + Description = todo.Description + }; } public async Task> GetTodosAsync() @@ -28,14 +44,26 @@ public class TodoRepository : ITodoRepository var todos = await _todosCollection.FindAsync(_ => true); return todos .ToEnumerable() - .Select(t => - new Core.Entities.Todo() { Id = t.Id, Title = t.Title, Status = t.Status, Project = t.ProjectName }); + .Select( + t => + new Core.Entities.Todo + { + Id = t.Id, + Title = t.Title, + Status = t.Status, + Project = t.ProjectName, + Description = t.Description + }); } - public async Task UpdateTodoStatus(string todoId, bool todoStatus, string userId) + public async Task UpdateTodoStatus( + string todoId, + bool todoStatus, + string userId) { await _todosCollection - .UpdateOneAsync(t => t.Id == todoId && t.AuthorId == userId, + .UpdateOneAsync( + t => t.Id == todoId && t.AuthorId == userId, Builders.Update.Set(t => t.Status, todoStatus)); } @@ -50,33 +78,39 @@ public class TodoRepository : ITodoRepository var todoCursor = await _todosCollection.FindAsync(f => f.Id == todoId); var todo = await todoCursor.FirstOrDefaultAsync(); - return new Core.Entities.Todo() + return new Core.Entities.Todo { Id = todo.Id, Project = todo.ProjectName, Status = todo.Status, - Title = todo.Title + Title = todo.Title, + Description = todo.Description }; } - public async Task UpdateTodoAsync(Core.Entities.Todo todo) + public async Task UpdateTodoAsync( + Core.Entities.Todo todo) { - var updatedTodo = await _todosCollection.FindOneAndReplaceAsync(f => f.Id == todo.Id, new MongoTodo() - { - Id = todo.Id, - Status = todo.Status, - Title = todo.Title, - ProjectName = todo.Project, - AuthorId = todo.AuthorId - }); + var updatedTodo = await _todosCollection.FindOneAndReplaceAsync( + f => f.Id == todo.Id, + new MongoTodo + { + Id = todo.Id, + Status = todo.Status, + Title = todo.Title, + ProjectName = todo.Project, + AuthorId = todo.AuthorId, + Description = todo.Description + }); - return new Core.Entities.Todo() + return new Core.Entities.Todo { Id = updatedTodo.Id, Project = updatedTodo.ProjectName, Status = updatedTodo.Status, Title = updatedTodo.Title, - AuthorId = updatedTodo.AuthorId + AuthorId = updatedTodo.AuthorId, + Description = updatedTodo.Description }; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/UserRepository.cs b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/UserRepository.cs index 23ca4bd..8faa596 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/UserRepository.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/UserRepository.cs @@ -3,23 +3,30 @@ using Todo.Core.Entities; using Todo.Core.Interfaces.Persistence; using Todo.Persistence.Mongo.Repositories.Dtos; -namespace Todo.Persistence.Mongo.Repositories +namespace Todo.Persistence.Mongo.Repositories; + +public class UserRepository : IUserRepository { - public class UserRepository : IUserRepository + private readonly IMongoCollection _usersCollection; + + public UserRepository(MongoDbConnectionHandler mongoDbConnectionHandler) { - private readonly IMongoCollection _usersCollection; + var database = mongoDbConnectionHandler.CreateDatabaseConnection(); + _usersCollection = database.GetCollection("users"); + } - public UserRepository(MongoDbConnectionHandler mongoDbConnectionHandler) + public async Task Register(string email, string password) + { + var dtoUser = new MongoUser { - var database = mongoDbConnectionHandler.CreateDatabaseConnection(); - _usersCollection = database.GetCollection("users"); - } - - public async Task Register(string email, string password) + Email = email, + Password = password + }; + await _usersCollection.InsertOneAsync(dtoUser); + return new User { - var dtoUser = new MongoUser() { Email = email, Password = password }; - await _usersCollection.InsertOneAsync(dtoUser); - return new User() { Email = dtoUser.Email, Id = dtoUser.Id }; - } + Email = dtoUser.Email, + Id = dtoUser.Id + }; } } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Persistence/Todo.Persistence.csproj b/src/backend/server/src/Todo.Persistence/Todo.Persistence.csproj index 358426b..3c9c302 100644 --- a/src/backend/server/src/Todo.Persistence/Todo.Persistence.csproj +++ b/src/backend/server/src/Todo.Persistence/Todo.Persistence.csproj @@ -2,20 +2,21 @@ net6.0 + enable - + - - - + + + - + diff --git a/src/client/src/components/todos/add/addTodoForm.tsx b/src/client/src/components/todos/add/addTodoForm.tsx index b646062..c0efcec 100644 --- a/src/client/src/components/todos/add/addTodoForm.tsx +++ b/src/client/src/components/todos/add/addTodoForm.tsx @@ -4,7 +4,7 @@ import { PrimaryButton } from "@src/components/common/buttons/primaryButton"; import { TodoShortForm } from "@src/components/todos/collapsed/todoShortForm"; export const AddTodoForm: FC<{ - onAdd: (todoName: string, project: string) => void; + onAdd: (todoName: string, project: string, description: string) => void; onClose: () => void; project: string; }> = ({ onAdd, onClose, ...props }) => { @@ -16,7 +16,7 @@ export const AddTodoForm: FC<{
{ e.preventDefault(); - onAdd(todoName, project); + onAdd(todoName, project, todoDescription); setTodoName(""); setTodoDescription(""); }} diff --git a/src/client/src/components/todos/addTodo.tsx b/src/client/src/components/todos/addTodo.tsx index de2e689..1f465a6 100644 --- a/src/client/src/components/todos/addTodo.tsx +++ b/src/client/src/components/todos/addTodo.tsx @@ -22,8 +22,8 @@ export function AddTodo(props: { project: string }) { return ( { - createTodo(todoName, project); + onAdd={(todoName, project, description) => { + createTodo(todoName, project, description); }} onClose={() => setCollapsed(CollapsedState.collapsed)} /> diff --git a/src/client/src/components/todos/todoItem.tsx b/src/client/src/components/todos/todoItem.tsx index 87341ff..56800e6 100644 --- a/src/client/src/components/todos/todoItem.tsx +++ b/src/client/src/components/todos/todoItem.tsx @@ -29,7 +29,12 @@ export const TodoItem: FC = (props) => { {props.todo.title} -
+
+
+ {props.todo.description && ( +
+ )} +
{props.displayProject && props.todo.project && (
{props.todo.project} diff --git a/src/client/src/core/entities/todo.tsx b/src/client/src/core/entities/todo.tsx index 22f97c1..b2204f6 100644 --- a/src/client/src/core/entities/todo.tsx +++ b/src/client/src/core/entities/todo.tsx @@ -9,6 +9,7 @@ export const StatusState: { done: Done; notDone: NotDone } = { export interface Todo { id: string; title: string; + description?: string; status: StatusState; project?: string; } diff --git a/src/client/src/presentation/contexts/SocketContext.tsx b/src/client/src/presentation/contexts/SocketContext.tsx index 7278212..40b9797 100644 --- a/src/client/src/presentation/contexts/SocketContext.tsx +++ b/src/client/src/presentation/contexts/SocketContext.tsx @@ -12,7 +12,7 @@ interface SocketContextProps { inboxTodos: Todo[]; getTodos: () => void; getInboxTodos: () => void; - createTodo: (todoName: string, project: string) => void; + createTodo: (todoName: string, project: string, description: string) => void; updateTodo: (todoId: string, todoStatus: StatusState) => void; getTodoById(todoId: string): void; replaceTodo(todo: Todo): void; @@ -24,7 +24,7 @@ export const SocketContext = createContext({ inboxTodos: [], getTodos: () => {}, getInboxTodos: () => {}, - createTodo: (todoName, project) => {}, + createTodo: (todoName, project, description) => {}, updateTodo: (todoId, todoStatus) => {}, getTodoById(todoId: string) {}, replaceTodo(todo: Todo) {}, @@ -89,8 +89,8 @@ export const SocketProvider: FC = (props) => { getInboxTodos: () => { conn.invoke("GetInboxTodos").catch(console.error); }, - createTodo: (todoName, project) => { - conn.invoke("CreateTodo", todoName, project).catch(console.error); + createTodo: (todoName, project, description) => { + conn.invoke("CreateTodo", todoName, project, description).catch(console.error); }, updateTodo: (todoId, todoStatus) => { conn.invoke("UpdateTodo", todoId, todoStatus).catch(console.error); diff --git a/src/client/src/presentation/hooks/socketHooks.tsx b/src/client/src/presentation/hooks/socketHooks.tsx index 2ae141d..8271c19 100644 --- a/src/client/src/presentation/hooks/socketHooks.tsx +++ b/src/client/src/presentation/hooks/socketHooks.tsx @@ -19,8 +19,8 @@ export const useCreateTodo = () => { const socketContext = useContext(SocketContext); return { - createTodo: (todoName: string, project: string) => { - socketContext.createTodo(todoName, project); + createTodo: (todoName: string, project: string, description: string) => { + socketContext.createTodo(todoName, project, description); }, }; };