From bd414654c53c247844719672d4b80ccd5cd8bea9 Mon Sep 17 00:00:00 2001 From: Michael Liendo Date: Sun, 18 Aug 2024 02:19:09 -0400 Subject: [PATCH] feat: edit links (#33) * chore: create types and schemas for the link edit * feat: functions for the editing of the link with the id * chore: create the visual for the editing of the linsk * chore: format code --- .../links/linkx-table-row-actions.tsx | 66 ++++++++++----- .../components/datagrids/links/modal-edit.tsx | 80 +++++++++++++++++++ client/src/context/LinksContext.tsx | 13 ++- client/src/services/Link.ts | 15 ++++ server/src/controllers/Link/update.ts | 17 ++++ server/src/repository/Link.ts | 11 ++- server/src/routes/link/index.ts | 15 +++- server/src/services/Link.ts | 25 +++++- shared/src/interfaces/link.ts | 6 ++ shared/src/schema/link.ts | 6 ++ 10 files changed, 230 insertions(+), 24 deletions(-) create mode 100644 client/src/components/datagrids/links/modal-edit.tsx create mode 100644 server/src/controllers/Link/update.ts diff --git a/client/src/components/datagrids/links/linkx-table-row-actions.tsx b/client/src/components/datagrids/links/linkx-table-row-actions.tsx index d993d67..5a20500 100644 --- a/client/src/components/datagrids/links/linkx-table-row-actions.tsx +++ b/client/src/components/datagrids/links/linkx-table-row-actions.tsx @@ -3,6 +3,16 @@ import { DotsHorizontalIcon } from '@radix-ui/react-icons'; import type { Row } from '@tanstack/react-table'; +import { TextField } from '@/components/text-field'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; import { Button } from '../../ui/button'; import { DropdownMenu, @@ -13,7 +23,11 @@ import { } from '../../ui/dropdown-menu'; import useLinks from '@/hooks/useLinks'; -import { LinkSchema } from '@linx/shared'; +import { toFormikValidationSchema } from '@/utils/toFormikValidationSchema'; +import { LinkForUpdateSchema, LinkSchema } from '@linx/shared'; +import { useFormik } from 'formik'; +import { useState } from 'react'; +import { LinkDialogEdit } from './modal-edit'; interface DataTableRowActionsProps { row: Row; @@ -22,27 +36,39 @@ interface DataTableRowActionsProps { export function DataTableRowActions({ row, }: DataTableRowActionsProps) { - const { deleteById } = useLinks(); const link = LinkSchema.parse(row.original); + const [isOpen, setIsOpen] = useState(false); + + const { deleteById } = useLinks(); + return ( - - - - - - Edit - - deleteById(link.id)}> - Delete - - - + <> + + + + + + + + Edit + + + + deleteById(link.id)}> + Delete + + + + {/* dialog */} + + + ); } diff --git a/client/src/components/datagrids/links/modal-edit.tsx b/client/src/components/datagrids/links/modal-edit.tsx new file mode 100644 index 0000000..500db31 --- /dev/null +++ b/client/src/components/datagrids/links/modal-edit.tsx @@ -0,0 +1,80 @@ +import { TextField } from '@/components/text-field'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; +import useLinks from '@/hooks/useLinks'; +import { toFormikValidationSchema } from '@/utils/toFormikValidationSchema'; +import { type ILink, LinkForUpdateSchema } from '@linx/shared'; +import { Link1Icon } from '@radix-ui/react-icons'; +import { useFormik } from 'formik'; +import { useState } from 'react'; + +export function LinkDialogEdit({ + link, + setIsOpen, +}: { link: ILink; setIsOpen: (isOpen: boolean) => void }) { + const { update } = useLinks(); + const { values, errors, handleChange, handleSubmit } = useFormik({ + initialValues: { + url: link.url, + shorter_name: link.shorter_name, + id: link.id, + }, + validationSchema: toFormikValidationSchema(LinkForUpdateSchema), + validateOnChange: false, + validateOnBlur: false, + onSubmit: async (values) => { + const dto = LinkForUpdateSchema.parse(values); + + update(dto); + setIsOpen(false); + }, + }); + return ( + + + Edit link + +
+
+ + + +
+ ); +} diff --git a/client/src/context/LinksContext.tsx b/client/src/context/LinksContext.tsx index 284375d..3886a77 100644 --- a/client/src/context/LinksContext.tsx +++ b/client/src/context/LinksContext.tsx @@ -9,6 +9,7 @@ export interface LinksContextProps { isLoading: boolean; links: ILink[] | []; create: (link: ILinkForCreate) => void; + update: (link: ILinkForCreate) => void; deleteById: (link_id: string) => void; } @@ -45,6 +46,16 @@ export const LinksProvider = ({ children }: { children?: React.ReactNode }) => { }); }; + const update = async (linkDTO: ILinkForCreate) => { + const updated_link = await Services.link.update(linkDTO); + setLinks((oldLinks) => { + const newList = [...oldLinks]; + const index = newList.findIndex((link) => link.id === updated_link.id); + newList[index] = updated_link; + return newList; + }); + }; + const deleteById = async (id: string) => { const link_id = await Services.link.deleteById(id); @@ -61,7 +72,7 @@ export const LinksProvider = ({ children }: { children?: React.ReactNode }) => { return ( {children} diff --git a/client/src/services/Link.ts b/client/src/services/Link.ts index 5297c16..94802e9 100644 --- a/client/src/services/Link.ts +++ b/client/src/services/Link.ts @@ -33,6 +33,21 @@ export default class Link { } } + static async update(link: ILinkForCreate) { + try { + const request = await fetch('/links/edit', { + method: 'PUT', + body: JSON.stringify(link), + }); + const body = await request.json(); + + return LinkSchema.parse(body.data.link); + } catch (e) { + console.log('LinkService', e); + throw e; + } + } + static async deleteById(link_id: string) { try { const request = await fetch('/links/deleteById', { diff --git a/server/src/controllers/Link/update.ts b/server/src/controllers/Link/update.ts new file mode 100644 index 0000000..19e4e26 --- /dev/null +++ b/server/src/controllers/Link/update.ts @@ -0,0 +1,17 @@ +import Services from '../../services'; +import { BadRequestError } from '../../utils/errorHandler'; + +import type { ILinkForUpdate } from '@linx/shared'; +import type { Reply, Request } from '../../types'; + +export default async function update(request: Request, reply: Reply) { + const data = request.body as ILinkForUpdate; + const user = request.user; + + const link = await Services.link.update(user.id, data); + return reply.code(201).send({ + success: true, + message: 'Link modified successfully', + data: { link: link }, + }); +} diff --git a/server/src/repository/Link.ts b/server/src/repository/Link.ts index d1ca523..f6f0f7d 100644 --- a/server/src/repository/Link.ts +++ b/server/src/repository/Link.ts @@ -1,6 +1,6 @@ import database from './database'; -import type { ILink, ILinkForCreate } from '@linx/shared'; +import type { ILink, ILinkForCreate, ILinkForUpdate } from '@linx/shared'; export class Link { static async create(linkDTO: ILinkForCreate): Promise { @@ -31,6 +31,15 @@ export class Link { return links; } + + static async update(linkId: string, linkDTO: ILinkForUpdate): Promise { + const [link] = await database('links') + .update({ ...linkDTO, updated_at: new Date() }) + .where({ id: linkId }) + .returning('*'); + return link; + } + static async deleteById(linkId: string): Promise { const _link = await database('links').where('id', linkId).delete(); return linkId; diff --git a/server/src/routes/link/index.ts b/server/src/routes/link/index.ts index be0fc0f..b10b750 100644 --- a/server/src/routes/link/index.ts +++ b/server/src/routes/link/index.ts @@ -1,7 +1,11 @@ import create from '../../controllers/Link/create'; import checkJwt from '../../middlewares/checkJwt'; -import { LinkForCreateSchema, LinkForDeleteSchema } from '@linx/shared'; +import { + LinkForCreateSchema, + LinkForDeleteSchema, + LinkForUpdateSchema, +} from '@linx/shared'; import requestValidation from '../../utils/requestValidation'; import type { @@ -9,8 +13,10 @@ import type { FastifyInstance, RegisterOptions, } from 'fastify'; +import { z } from 'zod'; import deleteById from '../../controllers/Link/delete'; import getAll from '../../controllers/Link/getAll'; +import update from '../../controllers/Link/update'; export default function link( fastify: FastifyInstance, @@ -32,6 +38,13 @@ export default function link( handler: create, }); + fastify.route({ + method: 'PUT', + url: '/edit', + preHandler: requestValidation(LinkForUpdateSchema), + handler: update, + }); + fastify.route({ method: 'DELETE', url: '/deleteById', diff --git a/server/src/services/Link.ts b/server/src/services/Link.ts index 47dccd7..a3be1cd 100644 --- a/server/src/services/Link.ts +++ b/server/src/services/Link.ts @@ -1,11 +1,12 @@ import Repository from '../repository'; import { BadRequestError, UnauthorizedError } from '../utils/errorHandler'; -import type { ILink, ILinkForCreate } from '@linx/shared'; +import type { ILink, ILinkForCreate, ILinkForUpdate } from '@linx/shared'; export default class Link { static async create(link_dto: ILinkForCreate): Promise { const check = await Repository.link.getByShorterName(link_dto.shorter_name); + // todo: improve this if (check) throw new BadRequestError('Exists the shorter name, please select other'); @@ -25,6 +26,28 @@ export default class Link { return link; } + static async update(user_id: string, link_dto: ILinkForUpdate) { + try { + const check = await Repository.link.getById(link_dto.id); + if (check.user_id !== user_id) + throw new UnauthorizedError('You no are the owner of this link'); + const check_name = await Repository.link.getByShorterName( + link_dto.shorter_name, + ); + // todo: improve this + if (check_name) + throw new BadRequestError( + 'Exists the shorter name, please select other', + ); + + const link = await Repository.link.update(link_dto.id, link_dto); + + return link; + } catch (error) { + console.log(error); + throw error; + } + } static async deleteByIdUser( link_id: string, user_id: string, diff --git a/shared/src/interfaces/link.ts b/shared/src/interfaces/link.ts index 6b4db2a..9abd735 100644 --- a/shared/src/interfaces/link.ts +++ b/shared/src/interfaces/link.ts @@ -13,6 +13,12 @@ export interface ILinkForCreate { user_id?: string; } +export interface ILinkForUpdate { + id: string; + url: string; + shorter_name: string; +} + export interface ILinkForDelete { id: string; } diff --git a/shared/src/schema/link.ts b/shared/src/schema/link.ts index e32c3b7..2ad8838 100644 --- a/shared/src/schema/link.ts +++ b/shared/src/schema/link.ts @@ -14,6 +14,12 @@ export const LinkForCreateSchema = z.object({ shorter_name: z.string().max(20), }); +export const LinkForUpdateSchema = z.object({ + id: z.string(), + url: z.string().url(), + shorter_name: z.string().max(20), +}); + export const LinkForDeleteSchema = z.object({ id: z.string(), });