Add basic todo behavior
This commit is contained in:
parent
43a9187579
commit
62630d63f2
15
src/backend/server/Todo/Todo.Api/Hubs/Models/TodoResponse.cs
Normal file
15
src/backend/server/Todo/Todo.Api/Hubs/Models/TodoResponse.cs
Normal file
@ -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; }
|
||||
}
|
@ -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 CreateTodo(string createTodoRequest)
|
||||
public async Task UpdateTodo(string todoId, bool todoStatus)
|
||||
{
|
||||
var todo = JsonSerializer.Deserialize<Core.Entities.Todo>(createTodoRequest);
|
||||
if (todo is null)
|
||||
throw new InvalidOperationException("Failed to create todo because of invalid request");
|
||||
await _todoRepository.UpdateTodoStatus(todoId, todoStatus);
|
||||
|
||||
await _todoRepository.CreateTodoAsync(todo.Title);
|
||||
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 GetTodos()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -4,4 +4,5 @@ public record Todo
|
||||
{
|
||||
public string Id { get; init; }
|
||||
public string Title { get; init; }
|
||||
public bool Status { get; init; }
|
||||
}
|
@ -4,4 +4,5 @@ public interface ITodoRepository
|
||||
{
|
||||
Task<Entities.Todo> CreateTodoAsync(string title);
|
||||
Task<IEnumerable<Entities.Todo>> GetTodosAsync();
|
||||
Task UpdateTodoStatus(string todoId, bool todoStatus);
|
||||
}
|
@ -10,4 +10,5 @@ public record MongoTodo
|
||||
public string Id { get; init; }
|
||||
|
||||
[BsonRequired] public string Title { get; init; }
|
||||
[BsonRequired] public bool Status { get; set; }
|
||||
}
|
@ -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<MongoTodo>.Update.Set(t => t.Status, todoStatus));
|
||||
}
|
||||
}
|
@ -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>(
|
||||
CollapsedState.collapsed
|
||||
);
|
||||
@ -18,7 +19,9 @@ export function AddTodo() {
|
||||
|
||||
return (
|
||||
<AddTodoForm
|
||||
onAdd={(todoName) => {}}
|
||||
onAdd={(todoName) => {
|
||||
props.conn.invoke("CreateTodo", todoName).catch(console.error)
|
||||
}}
|
||||
onClose={() => setCollapsed(CollapsedState.collapsed)}
|
||||
/>
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ export const TodoItem: FC<TodoItemProps> = (props) => (
|
||||
<div className="py-3 border-b border-gray-300 dark:border-gray-700">
|
||||
<div className="flex items-center space-x-4">
|
||||
<TodoCheckmark {...props} />
|
||||
<span className="pb-1">{props.todo.name}</span>
|
||||
<span className="pb-1">{props.todo.title}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -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 }) => (
|
||||
<>
|
||||
<ul id="inbox">
|
||||
{props.todos.map((t, i) => (
|
||||
@ -10,12 +11,14 @@ export const TodoList = (props: { todos: Todo[] }) => (
|
||||
<TodoItem
|
||||
todo={t}
|
||||
updateTodo={(todo) => {
|
||||
console.log(todo);
|
||||
props.conn
|
||||
.invoke("UpdateTodo", todo.id, todo.status)
|
||||
.catch(console.error);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<AddTodo />
|
||||
<AddTodo conn={props.conn} />
|
||||
</>
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ export const StatusState: { done: Done; notDone: NotDone } = {
|
||||
};
|
||||
|
||||
export interface Todo {
|
||||
name: string;
|
||||
id: string;
|
||||
title: string;
|
||||
status: StatusState;
|
||||
}
|
||||
|
@ -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<HubConnection>();
|
||||
const [todos, setTodos] = useState<Todo[]>([]);
|
||||
|
||||
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 (
|
||||
<main className="py-8 px-14 space-y-6">
|
||||
<section className="space-y-2">
|
||||
<PageHeading title="Inbox" />
|
||||
<TodoList todos={inboxTodos} />
|
||||
<TodoList todos={todos} conn={conn} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
|
@ -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==
|
||||
|
Loading…
Reference in New Issue
Block a user