From 65b924c174017b25a89ffadbf81f2c51339538be Mon Sep 17 00:00:00 2001 From: vasich Date: Tue, 23 Jul 2024 01:43:13 +0700 Subject: [PATCH] creae update devices --- .idea/.gitignore | 6 ++ .idea/Yuna.iml | 8 +++ .idea/dataSources.xml | 12 ++++ .idea/modules.xml | 8 +++ .idea/sqldialects.xml | 7 ++ .idea/vcs.xml | 6 ++ .../Repositories/DeviceRepositoryTests.cs | 22 ++++++ .../API/DeviceEndpoints.cs | 58 +++++++++++++-- .../Infrastructure/Settings.cs | 5 ++ .../Services/DeviceService/DeviceService.cs | 9 ++- .../Services/DeviceService/IDeviceService.cs | 1 + .../Repositories/Device/DeviceRepository.cs | 42 +++++++++-- .../Repositories/Device/IDeviceRepository.cs | 2 +- .../src/pages/home/HomePage.tsx | 3 + .../src/pages/home/components/DeviceCard.tsx | 22 +++++- .../home/components/modals/EditModal.tsx | 71 +++++++++++++++++++ .../src/pages/home/model/HomePageStore.ts | 37 ++++++++++ .../pages/home/services/HomePageService.tsx | 26 +++++++ .../src/pages/home/types.ts | 6 +- .../src/resources/styles/header.scss | 1 + .../src/resources/styles/home.scss | 25 ++++--- package-lock.json | 11 ++- package.json | 3 +- 23 files changed, 359 insertions(+), 32 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/Yuna.iml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/sqldialects.xml create mode 100644 .idea/vcs.xml create mode 100644 Yuna.Website/yuna.website.client/src/pages/home/components/modals/EditModal.tsx diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..8bf4d45 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,6 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Yuna.iml b/.idea/Yuna.iml new file mode 100644 index 0000000..0399c4b --- /dev/null +++ b/.idea/Yuna.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..f4f086b --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/ + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..6e64bdc --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..f75096d --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Yuna.Tests/Repositories/DeviceRepositoryTests.cs b/Yuna.Tests/Repositories/DeviceRepositoryTests.cs index 107ce4f..0b81aaf 100644 --- a/Yuna.Tests/Repositories/DeviceRepositoryTests.cs +++ b/Yuna.Tests/Repositories/DeviceRepositoryTests.cs @@ -80,5 +80,27 @@ namespace Yuna.Tests.Repositories Assert.NotNull(result); Assert.True(result.All(x => x.UserId == 1)); } + + [Fact] + public async Task Update_Updates() + { + //Arrange + Settings.Init(); + var _context = new DapperContext(true); + var repo = new DeviceRepository(_context); + + //Act + var initial = await repo.GetById(1); + + initial!.Name += "1"; + var returnResult = await repo.Update(initial); + + var realChanged = await repo.GetById(1); + + + //Assert + Assert.Equal(initial.Name, returnResult!.Name); + Assert.Equal(initial!.Name, realChanged!.Name); + } } } \ No newline at end of file diff --git a/Yuna.Website/Yuna.Website.Server/API/DeviceEndpoints.cs b/Yuna.Website/Yuna.Website.Server/API/DeviceEndpoints.cs index 348f835..ba8c4f9 100644 --- a/Yuna.Website/Yuna.Website.Server/API/DeviceEndpoints.cs +++ b/Yuna.Website/Yuna.Website.Server/API/DeviceEndpoints.cs @@ -16,9 +16,6 @@ namespace Yuna.Website.Server.API app.MapPost("/api/device", CreateDevice) .WithTags("device"); - app.MapDelete("/api/device/{id:long}", () => { }) - .WithTags("device"); - app.MapGet("/api/device/{deviceId:long}", GetById) .WithTags("device"); @@ -32,6 +29,12 @@ namespace Yuna.Website.Server.API app.MapPut("/api/device/{deviceId:long}", AddSkillsToDevice) .WithTags("device"); + + app.MapPut("/api/device", Update) + .WithTags("device"); + + app.MapDelete("/api/device/{deviceId:long}", Delete) + .WithTags("device"); } @@ -99,10 +102,18 @@ namespace Yuna.Website.Server.API } [Authorize] - public async Task Delete(IDeviceService deviceService, long id) + public async Task Delete(IDeviceService deviceService, HttpContext context, long deviceId) { - var result = await deviceService.Delete(id); - if (result is null) return Results.NotFound(); + var isAdmin = context.GetRoleFromCookie(); + var userId = context.GetUserIdFromCookie(); + + var deviceToDelete = await deviceService.GetById(deviceId); + if(deviceToDelete is null) return Results.NotFound(); + + if (userId != deviceToDelete.UserId && !isAdmin) return Results.Forbid(); + + var result = await deviceService.Delete(deviceId); + if (result is null) return Results.Problem(statusCode: 500); return Results.Ok(result); } @@ -129,5 +140,40 @@ namespace Yuna.Website.Server.API return Results.Ok(result); } + + + public class UpdateDeviceRequest + { + [JsonPropertyName("id")] + public long Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("description")] + public string Description { get; set; } = ""; + + [JsonPropertyName("deviceUrl")] + public string DeviceUrl { get; set; } = null!; + } + + [Authorize] + public async Task Update([FromBody] UpdateDeviceRequest request, HttpContext context, IDeviceService deviceService) + { + var userId = context.GetUserIdFromCookie(); + var isAdmin = context.GetRoleFromCookie(); + + var device = await deviceService.GetById(request.Id); + + if (device is null) return Results.NotFound(); + if (device.UserId != userId && !isAdmin) return Results.Forbid(); + + device.DeviceUrl = request.DeviceUrl; + device.Name = request.Name; + device.Description = request.Description; + + var result = await deviceService.Update(device); + return Results.Ok(result); + } } } diff --git a/Yuna.Website/Yuna.Website.Server/Infrastructure/Settings.cs b/Yuna.Website/Yuna.Website.Server/Infrastructure/Settings.cs index 96bd448..b6bae6e 100644 --- a/Yuna.Website/Yuna.Website.Server/Infrastructure/Settings.cs +++ b/Yuna.Website/Yuna.Website.Server/Infrastructure/Settings.cs @@ -36,6 +36,11 @@ namespace Yuna.Website.Server.Infrastructure private static void LoadConnectionStrs(JsonElement connectionStrs) { + var env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); + { + + } + DbConnectionStr = connectionStrs.GetProperty("Db").GetString()!; } diff --git a/Yuna.Website/Yuna.Website.Server/Services/DeviceService/DeviceService.cs b/Yuna.Website/Yuna.Website.Server/Services/DeviceService/DeviceService.cs index 9cc8b1b..9701855 100644 --- a/Yuna.Website/Yuna.Website.Server/Services/DeviceService/DeviceService.cs +++ b/Yuna.Website/Yuna.Website.Server/Services/DeviceService/DeviceService.cs @@ -107,9 +107,6 @@ namespace Yuna.Website.Server.Services.DeviceService public async Task Delete(long id) { - var prop = await _deviceRepository.GetById(id); - if (prop is null) return null; - var result = await _deviceRepository.Delete(id); return result; } @@ -130,5 +127,11 @@ namespace Yuna.Website.Server.Services.DeviceService var result = await _deviceRepository.GetList(userId); return result ?? []; } + + public async Task Update(Device device) + { + var result = await _deviceRepository.Update(device); + return result; + } } } diff --git a/Yuna.Website/Yuna.Website.Server/Services/DeviceService/IDeviceService.cs b/Yuna.Website/Yuna.Website.Server/Services/DeviceService/IDeviceService.cs index a303450..b39afc7 100644 --- a/Yuna.Website/Yuna.Website.Server/Services/DeviceService/IDeviceService.cs +++ b/Yuna.Website/Yuna.Website.Server/Services/DeviceService/IDeviceService.cs @@ -12,5 +12,6 @@ namespace Yuna.Website.Server.Services.DeviceService public Task Delete(long id); public Task AddProps(IReadOnlyList props, long deviceId); public Task?> FetchPropsData(Device device); + public Task Update(Device device); } } diff --git a/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/DeviceRepository.cs b/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/DeviceRepository.cs index 786f7fd..db055c0 100644 --- a/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/DeviceRepository.cs +++ b/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/DeviceRepository.cs @@ -1,5 +1,6 @@ using Dapper; using System.Data; +using Yuna.Website.Server.Model; namespace Yuna.Website.Server.Storage.Repositories.Device { @@ -36,9 +37,18 @@ namespace Yuna.Website.Server.Storage.Repositories.Device return result; } - public Task Delete(long id) + public async Task Delete(long id) { - throw new NotImplementedException(); + var query = + $@" + UPDATE ""Yuna_Devices"" + SET ""IsDeleted"" = TRUE + WHERE ""Id"" = {id} + AND NOT ""IsDeleted"" + returning * + "; + + return await _context.Connection.QuerySingleOrDefaultAsync(query); } public async Task GetById(long id) @@ -58,7 +68,8 @@ namespace Yuna.Website.Server.Storage.Repositories.Device FROM ""Yuna_Devices"" d LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId"" LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id"" - WHERE d.""Id"" = {id}"; + WHERE d.""Id"" = {id} + AND NOT d.""IsDeleted"""; var deviceDict = new Dictionary(); var result = await _context.Connection.QueryAsync( @@ -96,7 +107,9 @@ namespace Yuna.Website.Server.Storage.Repositories.Device p.""Type"" as {nameof(Model.Prop.Type)} FROM ""Yuna_Devices"" d LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId"" - LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id"""; + LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id"" + WHERE NOT d.""IsDeleted"" + ORDER BY d.""Id"""; var deviceDict = new Dictionary(); var result = await _context.Connection.QueryAsync( @@ -135,7 +148,9 @@ namespace Yuna.Website.Server.Storage.Repositories.Device FROM ""Yuna_Devices"" d LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId"" LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id"" - WHERE d.""UserId"" = {userId};"; + WHERE d.""UserId"" = {userId} + AND NOT d.""IsDeleted"" + ORDER BY d.""Id"""; var deviceDict = new Dictionary(); var result = await _context.Connection.QueryAsync( @@ -156,5 +171,22 @@ namespace Yuna.Website.Server.Storage.Repositories.Device return result.Distinct().ToList(); } + + public async Task Update(Model.Device device) + { + var query = + $@" + UPDATE public.""Yuna_Devices"" + SET ""Name"" = @Name, + ""Description"" = @Description, + ""DeviceUrl"" = @DeviceUrl, + ""UserId"" = @UserId + WHERE ""Id"" = @Id + AND NOT ""IsDeleted"" + returning * + "; + + return await _context.Connection.QuerySingleOrDefaultAsync(query, device); + } } } \ No newline at end of file diff --git a/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/IDeviceRepository.cs b/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/IDeviceRepository.cs index 9995c7e..6b5852b 100644 --- a/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/IDeviceRepository.cs +++ b/Yuna.Website/Yuna.Website.Server/Storage/Repositories/Device/IDeviceRepository.cs @@ -8,7 +8,7 @@ namespace Yuna.Website.Server.Storage.Repositories.Device public Task> GetList(); public Task> GetList(long userId); public Task Create(Model.Device device); - //public Task Update(User user); + public Task Update(Model.Device device); public Task Delete(long id); public Task AddProps(IReadOnlyList skills, long deviceId); } diff --git a/Yuna.Website/yuna.website.client/src/pages/home/HomePage.tsx b/Yuna.Website/yuna.website.client/src/pages/home/HomePage.tsx index f37e753..4b2f05b 100644 --- a/Yuna.Website/yuna.website.client/src/pages/home/HomePage.tsx +++ b/Yuna.Website/yuna.website.client/src/pages/home/HomePage.tsx @@ -1,11 +1,14 @@ import { Header } from "../../components/Header" import { DeviceList } from "./components/DeviceList"; +import { EditModal } from "./components/modals/EditModal"; export const HomePage = () => { return ( <> +
+ ); } \ No newline at end of file diff --git a/Yuna.Website/yuna.website.client/src/pages/home/components/DeviceCard.tsx b/Yuna.Website/yuna.website.client/src/pages/home/components/DeviceCard.tsx index a607bf8..1ed18aa 100644 --- a/Yuna.Website/yuna.website.client/src/pages/home/components/DeviceCard.tsx +++ b/Yuna.Website/yuna.website.client/src/pages/home/components/DeviceCard.tsx @@ -2,8 +2,10 @@ import { useEffect, useMemo } from "react"; import "../../../resources/styles/home.scss" import { IDeviceDto, IPropDto } from "../types"; import { DeviceCardStore } from "../model/DeviceCardStore"; -import { Button, Center, Skeleton, useInterval } from "@chakra-ui/react"; +import { Button, Center, IconButton, Skeleton, Spacer, useInterval } from "@chakra-ui/react"; import { observer } from "mobx-react"; +import { MdOutlineDelete, MdOutlineSettings } from "react-icons/md"; +import { homePageStore } from "../model/HomePageStore"; @@ -36,11 +38,25 @@ export const DeviceCard = observer((props: { data: IDeviceDto }) => {

{props.data.name}

- +
+ homePageStore.deleteDevice(props.data.id)} + icon={} + aria-label={"delete"} + fontSize={"90%"} + size={'sm'} /> + homePageStore.setEditModalOpened(props.data.id, true)} + icon={} + aria-label={"edit"} + fontSize={"90%"} + size={'sm'} /> +

{props.data.description}

    - {getPropsList(props.data.props, deviceCardStore.isLoading)} + {getPropsList(props.data.props ?? [], deviceCardStore.isLoading)}
diff --git a/Yuna.Website/yuna.website.client/src/pages/home/components/modals/EditModal.tsx b/Yuna.Website/yuna.website.client/src/pages/home/components/modals/EditModal.tsx new file mode 100644 index 0000000..7af8fc8 --- /dev/null +++ b/Yuna.Website/yuna.website.client/src/pages/home/components/modals/EditModal.tsx @@ -0,0 +1,71 @@ +import { observer } from "mobx-react"; +import { homePageStore } from "../../model/HomePageStore"; +import { Button, Center, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Stack, Textarea } from "@chakra-ui/react"; +import { IDeviceDto } from "../../types"; +import { useEffect, useState } from "react"; + +export const EditModal = observer(() => { + const currentDevice = homePageStore.currentEditDevice + + const [name, setName] = useState(currentDevice?.name); + const [description, setDescription] = useState(currentDevice?.description); + const [url, setUrl] = useState(currentDevice?.deviceUrl); + + useEffect( + () => { + setName(currentDevice?.name); + setDescription(currentDevice?.description); + setUrl(currentDevice?.deviceUrl) + }, + [currentDevice] + ) + + const onUpdateDevice = () => { + homePageStore.setEditModalOpened(currentDevice!.id, false); + homePageStore.updateDevice({ + id: currentDevice?.id, + name: name, + description: description, + deviceUrl: url + } as IDeviceDto); + } + + return ( homePageStore.setEditModalOpened(homePageStore.editModalCurrentId!, false)} + isOpen={homePageStore.isEditModalOpened} + isCentered + blockScrollOnMount={false} + > + + + Редактировать + + + + + setName(e.target.value)} + disabled={homePageStore.isLoading} /> + +