From b8b56a4abb4a6dc5472bc68b967a0d871e327563 Mon Sep 17 00:00:00 2001 From: Ivan Botygin Date: Thu, 20 Nov 2025 21:14:14 +0300 Subject: [PATCH] =?UTF-8?q?feat(saves):=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controllers/saves.controller.ts | 45 +++++++++++++++++++ apps/backend/src/services/saves.service.ts | 15 ++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/controllers/saves.controller.ts b/apps/backend/src/controllers/saves.controller.ts index ecb840a..0b4ff19 100644 --- a/apps/backend/src/controllers/saves.controller.ts +++ b/apps/backend/src/controllers/saves.controller.ts @@ -175,6 +175,51 @@ export const savesController = new Elysia({ prefix: '/saves' }) } ) + .delete( + '/:id', + async ({ params: { id }, user, set }) => { + if (!user) { + set.status = 401; + return { error: 'Unauthorized' }; + } + + const saveId = Number(id); + if (isNaN(saveId)) { + set.status = 400; + return { error: 'Invalid save ID' }; + } + + try { + await savesService.delete(saveId, user.id); + + return { + success: true, + message: 'Сейв успешно удален', + }; + } catch (error) { + if (error instanceof Error && error.message.includes('not found')) { + set.status = 404; + return { error: 'Save not found' }; + } + set.status = 500; + return { + error: error instanceof Error ? error.message : 'Failed to delete save' + }; + } + }, + { + params: t.Object({ + id: t.String(), + }), + detail: { + tags: ['Saves'], + summary: 'Delete save', + description: 'Deletes a save by ID (owner only)', + }, + auth: true + } + ) + .patch( '/:id', async ({ params: { id }, body, user, set }) => { diff --git a/apps/backend/src/services/saves.service.ts b/apps/backend/src/services/saves.service.ts index 5999b8b..f2085a8 100644 --- a/apps/backend/src/services/saves.service.ts +++ b/apps/backend/src/services/saves.service.ts @@ -4,7 +4,6 @@ import { s3Service } from './s3.service'; import { scraperService } from './scraper.service'; import { redis } from './redis.service'; import { nanoid } from 'nanoid'; - import type { Visibility, CreateSaveFromUrlRequest, @@ -203,6 +202,20 @@ class SavesService { return updated; } + async delete(id: number, userId: string): Promise { + const savedItem = await this.getById(id, userId); + + if (!savedItem || savedItem.userId !== userId) { + throw new Error('Save not found or access denied'); + } + + await s3Service.deleteFile(savedItem.s3Key); + + await db.delete(save).where(eq(save.id, id)); + + await this.invalidateCache(id, userId); + } + private hasAccess( savedItem: Save, requestUserId?: string,