diff --git a/src/backend/server/src/Todo.Api/Controllers/TodosController.cs b/src/backend/server/src/Todo.Api/Controllers/TodosController.cs index ef083cc..4234103 100644 --- a/src/backend/server/src/Todo.Api/Controllers/TodosController.cs +++ b/src/backend/server/src/Todo.Api/Controllers/TodosController.cs @@ -44,8 +44,13 @@ public class TodosController : ApiController => await Mediator.Send(new GetTodoByIdQuery(todoId)); [HttpGet] - public async Task>> GetTodos() - => Ok(await _todoRepository.GetTodosAsync()); + public async Task>> GetTodos([FromQuery] bool onlyActive = false) + { + if (onlyActive) + return Ok(await Mediator.Send(new GetActiveTodosQuery())); + + return Ok(await Mediator.Send(new GetTodosQuery())); + } [HttpPut("{todoId}")] public async Task ReplaceTodo([FromRoute] string todoId, [FromBody] ReplaceTodoRequest request) @@ -69,11 +74,15 @@ public class TodosController : ApiController [JsonPropertyName("status")] public bool Status { get; init; } + [JsonPropertyName("created")] + public long Created { get; init; } = 0; + internal ReplaceTodoCommand To(string id) => new( id, Title, Project, Description, - Status); + Status, + Created); } } \ 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 b38ffe5..f969424 100644 --- a/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs +++ b/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs @@ -14,20 +14,14 @@ namespace Todo.Api.Hubs; public class TodoHub : Hub { 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) + ICurrentUserService currentUserService) { - _todoRepository = todoRepository; _userConnectionStore = userConnectionStore; _currentUserService = currentUserService; - _mediator = mediator; } public override Task OnConnectedAsync() @@ -46,149 +40,4 @@ public class TodoHub : Hub return base.OnDisconnectedAsync(exception); } - - public async Task CreateTodo( - string todoTitle, - string? projectName, - string? description) - { - if (todoTitle is null) - throw new ArgumentException("title cannot be null"); - - //var userId = GetUserId(); - - //var _ = await _todoRepository.CreateTodoAsync(todoTitle, projectName, userId); - await _mediator.Send( - new CreateTodoCommand( - todoTitle, - projectName, - description)); - - await GetInboxTodos(); - } - - - public async Task UpdateTodo(string todoId, bool todoStatus) - { - var userId = GetUserId(); - await _todoRepository.UpdateTodoStatus( - todoId, - todoStatus, - userId); - - await GetInboxTodos(); - } - - 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, - Description = t.Description - }) - .ToList()); - - 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, - Description = t.Description - }) - .ToList()); - - 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, - Description = todo.Description - }); - - await RunOnUserConnections( - async connections => - await Clients.Clients(connections) - .SendAsync("getTodo", serializedTodo)); - } - - public async Task ReplaceTodo(string 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, - Description = updateTodo.Description - }); - - 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)); - } - - private async Task RunOnUserConnections( - Func, Task> action) - { - var userId = GetUserId(); - 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"); } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Application/Commands/Todo/ReplaceTodoCommand.cs b/src/backend/server/src/Todo.Core/Application/Commands/Todo/ReplaceTodoCommand.cs index 56261dc..065b709 100644 --- a/src/backend/server/src/Todo.Core/Application/Commands/Todo/ReplaceTodoCommand.cs +++ b/src/backend/server/src/Todo.Core/Application/Commands/Todo/ReplaceTodoCommand.cs @@ -9,9 +9,10 @@ namespace Todo.Core.Application.Commands.Todo; public record ReplaceTodoCommand( string Id, string Title, - string Project, - string Description, - bool Status) : IRequest + string? Project, + string? Description, + bool Status, + long Created) : IRequest { internal class Handler : IRequestHandler { @@ -43,13 +44,12 @@ public record ReplaceTodoCommand( } } - private Entities.Todo To(string authorId) => new() - { - Id = Id, - Description = Description, - Project = Project, - Status = Status, - Title = Title, - AuthorId = authorId - }; + private Entities.Todo To(string authorId) => new( + Id, + Title, + Status, + Project, + authorId, + Description, + Created); } \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Application/Queries/Todos/GetActiveTodosQuery.cs b/src/backend/server/src/Todo.Core/Application/Queries/Todos/GetActiveTodosQuery.cs new file mode 100644 index 0000000..30072e4 --- /dev/null +++ b/src/backend/server/src/Todo.Core/Application/Queries/Todos/GetActiveTodosQuery.cs @@ -0,0 +1,34 @@ +using System.Threading; +using MediatR; +using Todo.Core.Interfaces.Persistence; +using Todo.Core.Interfaces.User; + +namespace Todo.Core.Application.Queries.Todos; + +public record GetActiveTodosQuery : IRequest> +{ + internal class Handler : IRequestHandler> + { + private readonly ICurrentUserService _currentUserService; + private readonly ITodoRepository _todoRepository; + + public Handler(ICurrentUserService currentUserService, ITodoRepository todoRepository) + { + _currentUserService = currentUserService; + _todoRepository = todoRepository; + } + + public async Task> Handle( + GetActiveTodosQuery request, + CancellationToken cancellationToken) + { + var userId = _currentUserService.GetUserId(); + if (userId is null) + throw new InvalidOperationException("Cannot get userId"); + + var todos = await _todoRepository.GetActiveTodosAsync(userId); + + return todos.Select(TodoViewModel.From); + } + } +} \ No newline at end of file diff --git a/src/backend/server/src/Todo.Core/Application/Queries/Todos/GetTodosQuery.cs b/src/backend/server/src/Todo.Core/Application/Queries/Todos/GetTodosQuery.cs new file mode 100644 index 0000000..32eec99 --- /dev/null +++ b/src/backend/server/src/Todo.Core/Application/Queries/Todos/GetTodosQuery.cs @@ -0,0 +1,34 @@ +using System.Threading; +using MediatR; +using Todo.Core.Interfaces.Persistence; +using Todo.Core.Interfaces.User; + +namespace Todo.Core.Application.Queries.Todos; + +public record GetTodosQuery : IRequest> +{ + internal class Handler : IRequestHandler> + { + private readonly ICurrentUserService _currentUserService; + private readonly ITodoRepository _todoRepository; + + public Handler(ICurrentUserService currentUserService, ITodoRepository todoRepository) + { + _currentUserService = currentUserService; + _todoRepository = todoRepository; + } + + public async Task> Handle( + GetTodosQuery request, + CancellationToken cancellationToken) + { + var userId = _currentUserService.GetUserId(); + if (userId is null) + throw new InvalidOperationException("Cannot get userId"); + + var todos = await _todoRepository.GetTodosAsync(userId); + + return todos.Select(TodoViewModel.From); + } + } +} \ 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 b3df207..5d44882 100644 --- a/src/backend/server/src/Todo.Core/Entities/Todo.cs +++ b/src/backend/server/src/Todo.Core/Entities/Todo.cs @@ -1,12 +1,10 @@ namespace Todo.Core.Entities; -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 AuthorId { get; init; } - public string? Description { get; init; } - public long Created { get; init; } -} \ No newline at end of file +public record Todo( + string Id, + string Title, + bool Status, + string? Project, + string AuthorId, + string? Description, + long Created); \ 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 ec355f9..8af3ba2 100644 --- a/src/backend/server/src/Todo.Core/Interfaces/Persistence/ITodoRepository.cs +++ b/src/backend/server/src/Todo.Core/Interfaces/Persistence/ITodoRepository.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace Todo.Core.Interfaces.Persistence; public interface ITodoRepository @@ -9,14 +11,14 @@ public interface ITodoRepository string userId, long created); - Task> GetTodosAsync(); + Task> GetTodosAsync(string userId, CancellationToken cancellationToken = new()); Task UpdateTodoStatus( string todoId, bool todoStatus, string userId); - Task> GetNotDoneTodos(); Task GetTodoByIdAsync(string todoId); Task UpdateTodoAsync(Entities.Todo todo); + Task> GetActiveTodosAsync(string authorId, CancellationToken cancellationToken = new()); } \ 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 70725a3..49325bc 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 @@ -20,4 +20,13 @@ public record MongoTodo public string? ProjectName { get; set; } = string.Empty; public string AuthorId { get; set; } public long Created { get; set; } = 0; + + public Core.Entities.Todo To() => new( + Id, + Title, + Status, + ProjectName, + AuthorId, + Description, + Created); } \ 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 fb71d76..34f8814 100644 --- a/src/backend/server/src/Todo.Persistence/Mongo/Repositories/TodoRepository.cs +++ b/src/backend/server/src/Todo.Persistence/Mongo/Repositories/TodoRepository.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Threading; using MongoDB.Driver; using Todo.Core.Interfaces.Persistence; using Todo.Persistence.Mongo.Repositories.Dtos; @@ -27,40 +28,21 @@ public class TodoRepository : ITodoRepository { Title = title, ProjectName = projectName, + Status = false, AuthorId = userId, Description = description, Created = created }; await _todosCollection.InsertOneAsync(todo); - return new Core.Entities.Todo - { - Id = todo.Id, - Title = todo.Title, - Status = false, - Project = todo.ProjectName, - Description = todo.Description, - AuthorId = todo.AuthorId, - Created = todo.Created - }; + return todo.To(); } - public async Task> GetTodosAsync() + public async Task> GetTodosAsync(string userId, CancellationToken cancellationToken) { - var todos = await _todosCollection.FindAsync(_ => true); + var todos = await _todosCollection.FindAsync(t => t.AuthorId == userId, cancellationToken: cancellationToken); return todos .ToEnumerable() - .Select( - t => - new Core.Entities.Todo - { - Id = t.Id, - Title = t.Title, - Status = t.Status, - Project = t.ProjectName, - Description = t.Description, - AuthorId = t.AuthorId, - Created = t.Created - }); + .Select(t => t.To()); } public async Task UpdateTodoStatus( @@ -74,27 +56,12 @@ public class TodoRepository : ITodoRepository Builders.Update.Set(t => t.Status, todoStatus)); } - public async Task> GetNotDoneTodos() - { - var todos = await GetTodosAsync(); - return todos.Where(t => t.Status == false); - } - public async Task GetTodoByIdAsync(string todoId) { var todoCursor = await _todosCollection.FindAsync(f => f.Id == todoId); var todo = await todoCursor.FirstOrDefaultAsync(); - return new Core.Entities.Todo - { - Id = todo.Id, - Project = todo.ProjectName, - Status = todo.Status, - Title = todo.Title, - Description = todo.Description, - AuthorId = todo.AuthorId, - Created = todo.Created - }; + return todo.To(); } public async Task UpdateTodoAsync( @@ -113,15 +80,18 @@ public class TodoRepository : ITodoRepository Created = todo.Created }); - return new Core.Entities.Todo - { - Id = updatedTodo.Id, - Project = updatedTodo.ProjectName, - Status = updatedTodo.Status, - Title = updatedTodo.Title, - AuthorId = updatedTodo.AuthorId, - Description = updatedTodo.Description, - Created = updatedTodo.Created - }; + return updatedTodo.To(); + } + + public async Task> GetActiveTodosAsync( + string authorId, + CancellationToken cancellationToken) + { + var todosCursor = await _todosCollection.FindAsync( + t => t.AuthorId == authorId && t.Status == false, + cancellationToken: cancellationToken); + var todos = todosCursor.ToEnumerable(cancellationToken); + + return todos.Select(t => t.To()); } } \ No newline at end of file diff --git a/src/client/src/infrastructure/apis/todosApi.ts b/src/client/src/infrastructure/apis/todosApi.ts index 0a76b8e..44ba855 100644 --- a/src/client/src/infrastructure/apis/todosApi.ts +++ b/src/client/src/infrastructure/apis/todosApi.ts @@ -23,6 +23,22 @@ export const todosApi = createApi({ : [], }), + getActiveTodos: build.query({ + query: () => ({ + url: `api/todos`, + params: { + onlyActive: true, + }, + }), + providesTags: (result) => + result + ? [ + ...result.map(({ id }) => ({ type: "todo" as const, id })), + { type: "todo", id: "LIST" }, + ] + : [], + }), + createTodo: build.mutation>({ query(body) { return { diff --git a/src/client/src/pages/index.tsx b/src/client/src/pages/index.tsx index 7d8e05f..4dfcd4a 100644 --- a/src/client/src/pages/index.tsx +++ b/src/client/src/pages/index.tsx @@ -6,7 +6,7 @@ import { todosApi } from "@src/infrastructure/apis/todosApi"; import { AddTodo } from "@src/components/todos/addTodo"; const HomePage = () => { - const { data, isLoading } = todosApi.useGetAllTodosQuery(); + const { data, isLoading } = todosApi.useGetActiveTodosQuery(); if (isLoading) { return "loading...";