From 62630d63f243b9d2df632f53eeb98282f938e016 Mon Sep 17 00:00:00 2001 From: Kasper Juul Hermansen Date: Sat, 13 Nov 2021 17:35:33 +0100 Subject: [PATCH] Add basic todo behavior --- .../Todo/Todo.Api/Hubs/Models/TodoResponse.cs | 15 ++++++ .../server/Todo/Todo.Api/Hubs/TodoHub.cs | 37 +++++++++++--- .../Todo.Api/Properties/launchSettings.json | 2 +- src/backend/server/Todo/Todo.Api/Startup.cs | 2 - .../server/Todo/Todo.Core/Entities/Todo.cs | 1 + .../Interfaces/Persistence/ITodoRepository.cs | 1 + .../Mongo/Repositories/Dtos/MongoTodo.cs | 1 + .../Mongo/Repositories/TodoRepository.cs | 9 +++- src/client/src/components/todos/addTodo.tsx | 7 ++- src/client/src/components/todos/todoItem.tsx | 2 +- src/client/src/components/todos/todoList.tsx | 9 ++-- src/client/src/core/entities/todo.tsx | 3 +- src/client/src/pages/index.tsx | 21 +++++--- src/client/yarn.lock | 49 ++++++++++--------- 14 files changed, 110 insertions(+), 49 deletions(-) create mode 100644 src/backend/server/Todo/Todo.Api/Hubs/Models/TodoResponse.cs diff --git a/src/backend/server/Todo/Todo.Api/Hubs/Models/TodoResponse.cs b/src/backend/server/Todo/Todo.Api/Hubs/Models/TodoResponse.cs new file mode 100644 index 0000000..ae5d28a --- /dev/null +++ b/src/backend/server/Todo/Todo.Api/Hubs/Models/TodoResponse.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Todo.Api.Hubs.Models; + +public record TodoResponse +{ + [JsonPropertyName("id")] + public string Id { get; init; } + + [JsonPropertyName("title")] + public string Title { get; init; } + + [JsonPropertyName("status")] + public bool Status { get; init; } +} \ No newline at end of file diff --git a/src/backend/server/Todo/Todo.Api/Hubs/TodoHub.cs b/src/backend/server/Todo/Todo.Api/Hubs/TodoHub.cs index bee5658..fcc4d53 100644 --- a/src/backend/server/Todo/Todo.Api/Hubs/TodoHub.cs +++ b/src/backend/server/Todo/Todo.Api/Hubs/TodoHub.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Microsoft.AspNetCore.SignalR; +using Todo.Api.Hubs.Models; using Todo.Core.Interfaces.Persistence; namespace Todo.Api.Hubs @@ -13,18 +14,40 @@ namespace Todo.Api.Hubs _todoRepository = todoRepository; } - public async Task GetInboxTodos() + public async Task CreateTodo(string todoTitle) { - await Clients.Caller.SendAsync("InboxTodos", "some data"); + var _ = await _todoRepository.CreateTodoAsync(todoTitle); + + var todos = await _todoRepository.GetTodosAsync(); + var serializedTodos = + JsonSerializer.Serialize(todos + .Select(t => new TodoResponse { Id = t.Id, Title = t.Title }) + .ToList()); + + await Clients.Caller.SendAsync("todos", serializedTodos); + } + + public async Task UpdateTodo(string todoId, bool todoStatus) + { + await _todoRepository.UpdateTodoStatus(todoId, todoStatus); + + var todos = await _todoRepository.GetTodosAsync(); + var serializedTodos = + JsonSerializer.Serialize(todos + .Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status }) + .ToList()); + + await Clients.Caller.SendAsync("todos", serializedTodos); } - public async Task CreateTodo(string createTodoRequest) + public async Task GetTodos() { - var todo = JsonSerializer.Deserialize(createTodoRequest); - if (todo is null) - throw new InvalidOperationException("Failed to create todo because of invalid request"); + var todos = await _todoRepository.GetTodosAsync(); + var serializedTodos = JsonSerializer.Serialize(todos + .Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status}) + .ToList()); - await _todoRepository.CreateTodoAsync(todo.Title); + await Clients.Caller.SendAsync("todos", serializedTodos); } } } \ No newline at end of file diff --git a/src/backend/server/Todo/Todo.Api/Properties/launchSettings.json b/src/backend/server/Todo/Todo.Api/Properties/launchSettings.json index 0f90f26..b89443e 100644 --- a/src/backend/server/Todo/Todo.Api/Properties/launchSettings.json +++ b/src/backend/server/Todo/Todo.Api/Properties/launchSettings.json @@ -14,7 +14,7 @@ "dotnetRunMessages": "true", "launchBrowser": false, "launchUrl": "swagger", - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/backend/server/Todo/Todo.Api/Startup.cs b/src/backend/server/Todo/Todo.Api/Startup.cs index 838ba14..3c8121b 100644 --- a/src/backend/server/Todo/Todo.Api/Startup.cs +++ b/src/backend/server/Todo/Todo.Api/Startup.cs @@ -53,8 +53,6 @@ namespace Todo.Api app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Todo.Api v1")); } - app.UseHttpsRedirection(); - app.UseRouting(); app.UseCors(); diff --git a/src/backend/server/Todo/Todo.Core/Entities/Todo.cs b/src/backend/server/Todo/Todo.Core/Entities/Todo.cs index ece0360..ef0c713 100644 --- a/src/backend/server/Todo/Todo.Core/Entities/Todo.cs +++ b/src/backend/server/Todo/Todo.Core/Entities/Todo.cs @@ -4,4 +4,5 @@ public record Todo { public string Id { get; init; } public string Title { get; init; } + public bool Status { get; init; } } \ No newline at end of file diff --git a/src/backend/server/Todo/Todo.Core/Interfaces/Persistence/ITodoRepository.cs b/src/backend/server/Todo/Todo.Core/Interfaces/Persistence/ITodoRepository.cs index 2665816..3f5fcb9 100644 --- a/src/backend/server/Todo/Todo.Core/Interfaces/Persistence/ITodoRepository.cs +++ b/src/backend/server/Todo/Todo.Core/Interfaces/Persistence/ITodoRepository.cs @@ -4,4 +4,5 @@ public interface ITodoRepository { Task CreateTodoAsync(string title); Task> GetTodosAsync(); + Task UpdateTodoStatus(string todoId, bool todoStatus); } \ No newline at end of file diff --git a/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs b/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs index ea48a95..7f291f1 100644 --- a/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs +++ b/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/Dtos/MongoTodo.cs @@ -10,4 +10,5 @@ public record MongoTodo public string Id { get; init; } [BsonRequired] public string Title { get; init; } + [BsonRequired] public bool Status { get; set; } } \ No newline at end of file diff --git a/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/TodoRepository.cs b/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/TodoRepository.cs index 790b70a..fed9133 100644 --- a/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/TodoRepository.cs +++ b/src/backend/server/Todo/Todo.Persistence/Mongo/Repositories/TodoRepository.cs @@ -29,6 +29,13 @@ public class TodoRepository : ITodoRepository return todos .ToEnumerable() .Select(t => - new Core.Entities.Todo() { Id = t.Id, Title = t.Title }); + new Core.Entities.Todo() { Id = t.Id, Title = t.Title, Status = t.Status}); + } + + public async Task UpdateTodoStatus(string todoId, bool todoStatus) + { + await _todosCollection + .UpdateOneAsync(t => t.Id == todoId, + Builders.Update.Set(t => t.Status, todoStatus)); } } \ No newline at end of file diff --git a/src/client/src/components/todos/addTodo.tsx b/src/client/src/components/todos/addTodo.tsx index 823537c..6f69484 100644 --- a/src/client/src/components/todos/addTodo.tsx +++ b/src/client/src/components/todos/addTodo.tsx @@ -2,8 +2,9 @@ import { useState } from "react"; import { CollapsedAddTodo } from "@src/components/todos/collapsed/collapsedAddTodo"; import { AddTodoForm } from "@src/components/todos/collapsed/addTodoForm"; import { CollapsedState } from "@src/components/todos/collapsed/collapsedState"; +import {HubConnection} from "@microsoft/signalr"; -export function AddTodo() { +export function AddTodo(props: {conn: HubConnection}) { const [collapsed, setCollapsed] = useState( CollapsedState.collapsed ); @@ -18,7 +19,9 @@ export function AddTodo() { return ( {}} + onAdd={(todoName) => { + props.conn.invoke("CreateTodo", todoName).catch(console.error) + }} onClose={() => setCollapsed(CollapsedState.collapsed)} /> ); diff --git a/src/client/src/components/todos/todoItem.tsx b/src/client/src/components/todos/todoItem.tsx index 5a8361d..c583690 100644 --- a/src/client/src/components/todos/todoItem.tsx +++ b/src/client/src/components/todos/todoItem.tsx @@ -11,7 +11,7 @@ export const TodoItem: FC = (props) => (
- {props.todo.name} + {props.todo.title}
); diff --git a/src/client/src/components/todos/todoList.tsx b/src/client/src/components/todos/todoList.tsx index b620f1a..26b95a9 100644 --- a/src/client/src/components/todos/todoList.tsx +++ b/src/client/src/components/todos/todoList.tsx @@ -1,8 +1,9 @@ import { Todo } from "@src/core/entities/todo"; import { TodoItem } from "@src/components/todos/todoItem"; import { AddTodo } from "@src/components/todos/addTodo"; +import { HubConnection } from "@microsoft/signalr"; -export const TodoList = (props: { todos: Todo[] }) => ( +export const TodoList = (props: { todos: Todo[]; conn: HubConnection }) => ( <>
    {props.todos.map((t, i) => ( @@ -10,12 +11,14 @@ export const TodoList = (props: { todos: Todo[] }) => ( { - console.log(todo); + props.conn + .invoke("UpdateTodo", todo.id, todo.status) + .catch(console.error); }} /> ))}
- + ); diff --git a/src/client/src/core/entities/todo.tsx b/src/client/src/core/entities/todo.tsx index 3268c93..afc0115 100644 --- a/src/client/src/core/entities/todo.tsx +++ b/src/client/src/core/entities/todo.tsx @@ -7,6 +7,7 @@ export const StatusState: { done: Done; notDone: NotDone } = { }; export interface Todo { - name: string; + id: string; + title: string; status: StatusState; } diff --git a/src/client/src/pages/index.tsx b/src/client/src/pages/index.tsx index 7ffbc3d..1b497e4 100644 --- a/src/client/src/pages/index.tsx +++ b/src/client/src/pages/index.tsx @@ -1,27 +1,34 @@ -import { useEffect, useMemo } from "react"; -import { getInboxTodos } from "@src/core/actions/todos"; +import { useEffect, useState } from "react"; import { PageHeading } from "@src/components/common/headings/pageHeading"; import { TodoList } from "@src/components/todos"; import * as signalR from "@microsoft/signalr"; +import { HubConnection } from "@microsoft/signalr"; +import { Todo } from "@src/core/entities/todo"; const HomePage = () => { - const inboxTodos = useMemo(() => getInboxTodos(), []); + const [conn, setConn] = useState(); + const [todos, setTodos] = useState([]); useEffect(() => { const connection = new signalR.HubConnectionBuilder() - .withUrl("https://localhost:5001/hubs/todo") + .withUrl("http://localhost:5000/hubs/todo") .build(); - connection.on("InboxTodos", (todos) => console.log(todos)); + connection.on("todos", (todos) => { + const parsedTodos = JSON.parse(todos); + setTodos(parsedTodos); + }); - connection.start().then(() => connection.invoke("getInboxTodos")); + connection.start().then(() => connection.invoke("GetTodos")); + + setConn(connection); }, []); return (
- +
); diff --git a/src/client/yarn.lock b/src/client/yarn.lock index 76f843c..246e668 100644 --- a/src/client/yarn.lock +++ b/src/client/yarn.lock @@ -57,7 +57,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3": version "7.16.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== @@ -458,7 +458,7 @@ aria-query@^4.2.2: "@babel/runtime" "^7.10.2" "@babel/runtime-corejs3" "^7.10.2" -array-includes@^3.1.1, array-includes@^3.1.3, array-includes@^3.1.4: +array-includes@^3.1.3, array-includes@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== @@ -539,7 +539,7 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -axe-core@^4.0.2: +axe-core@^4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.5.tgz#78d6911ba317a8262bfee292aeafcc1e04b49cc5" integrity sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA== @@ -739,9 +739,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001274: - version "1.0.30001279" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz#eb06818da481ef5096a3b3760f43e5382ed6b0ce" - integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ== + version "1.0.30001280" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz#066a506046ba4be34cde5f74a08db7a396718fb7" + integrity sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA== chalk@2.4.2, chalk@^2.0.0: version "2.4.2" @@ -1098,7 +1098,7 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw== -damerau-levenshtein@^1.0.6: +damerau-levenshtein@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" integrity sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw== @@ -1244,9 +1244,9 @@ domutils@^2.6.0: domhandler "^4.2.0" electron-to-chromium@^1.3.723, electron-to-chromium@^1.3.886: - version "1.3.893" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.893.tgz#9d804c68953b05ede35409dba0d73dd54c077b4d" - integrity sha512-ChtwF7qB03INq1SyMpue08wc6cve+ktj2UC/Y7se9vB+JryfzziJeYwsgb8jLaCA5GMkHCdn5M62PfSMWhifZg== + version "1.3.896" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.896.tgz#4a94efe4870b1687eafd5c378198a49da06e8a1b" + integrity sha512-NcGkBVXePiuUrPLV8IxP43n1EOtdg+dudVjrfVEUd/bOqpQUFZ2diL5PPYzbgEhZFEltdXV3AcyKwGnEQ5lhMA== elliptic@^6.5.3: version "6.5.4" @@ -1266,7 +1266,7 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.0.0: +emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== @@ -1420,21 +1420,22 @@ eslint-plugin-import@^2.22.1: tsconfig-paths "^3.11.0" eslint-plugin-jsx-a11y@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" - integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== + version "6.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" + integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== dependencies: - "@babel/runtime" "^7.11.2" + "@babel/runtime" "^7.16.3" aria-query "^4.2.2" - array-includes "^3.1.1" + array-includes "^3.1.4" ast-types-flow "^0.0.7" - axe-core "^4.0.2" + axe-core "^4.3.5" axobject-query "^2.2.0" - damerau-levenshtein "^1.0.6" - emoji-regex "^9.0.0" + damerau-levenshtein "^1.0.7" + emoji-regex "^9.2.2" has "^1.0.3" - jsx-ast-utils "^3.1.0" + jsx-ast-utils "^3.2.1" language-tags "^1.0.5" + minimatch "^3.0.4" eslint-plugin-react-hooks@^4.2.0: version "4.3.0" @@ -1702,9 +1703,9 @@ foreach@^2.0.5: integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= fraction.js@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff" - integrity sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg== + version "4.1.2" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8" + integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== fs-extra@^10.0.0: version "10.0.0" @@ -2250,7 +2251,7 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" integrity sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==