Archived
1
0

feat/saves-service Добавлен CRUD для сохранений #4

Merged
mrqiz merged 3 commits from feat/saves-service into lord 2025-11-20 21:34:49 +03:00
2 changed files with 311 additions and 1 deletions
Showing only changes of commit fd1e9018ee - Show all commits

View File

@ -175,6 +175,63 @@ export const savesController = new Elysia({ prefix: '/saves' })
}
)
.patch(
'/:id',
async ({ params: { id }, body, 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 {
const updated = await savesService.update(saveId, user.id, body);
return {
id: updated.id,
name: updated.name,
type: updated.type,
description: updated.description,
tags: updated.tags,
visibility: updated.visibility,
shareUrl: updated.visibility === 'link' ? updated.shareUrl : undefined,
updatedAt: updated.updatedAt.toISOString(),
};
} catch (error) {
if (error instanceof Error && error.message.includes('not found')) {
set.status = 404;
return { error: 'Save not found or access denied' };
}
set.status = 500;
return {
error: error instanceof Error ? error.message : 'Failed to update save'
};
}
},
{
params: t.Object({
id: t.String(),
}),
body: t.Object({
name: t.Optional(t.String()),
description: t.Optional(t.String()),
tags: t.Optional(t.Array(t.String())),
visibility: t.Optional(t.Union([t.Literal('public'), t.Literal('link')])),
}),
detail: {
tags: ['Saves'],
summary: 'Update save',
description: 'Updates save metadata (owner only)',
},
auth: true
}
)
.post(
'/external',
async ({ body, user, set }) => {

View File

@ -8,6 +8,7 @@ import { nanoid } from 'nanoid';
import type {
Visibility,
CreateSaveFromUrlRequest,
UpdateSaveRequest,
} from '@p1ctos4ve/shared-types';
class SavesService {
@ -161,6 +162,47 @@ class SavesService {
return publicSaves;
}
async update(
id: number,
userId: string,
data: UpdateSaveRequest
): Promise<Save> {
const savedItem = await this.getById(id, userId);
if (!savedItem || savedItem.userId !== userId) {
throw new Error('Save not found or access denied');
}
const updateData: Partial<NewSave> = {
updatedAt: new Date(),
};
if (data.name !== undefined) updateData.name = data.name;
if (data.description !== undefined) updateData.description = data.description;
if (data.tags !== undefined) updateData.tags = data.tags;
if (data.visibility !== undefined) {
updateData.visibility = data.visibility;
if (data.visibility === 'link' && !savedItem.shareUrl) {
updateData.shareUrl = this.generateShareUrl();
}
if (data.visibility === 'public') {
updateData.shareUrl = null;
}
}
const [updated] = await db
.update(save)
.set(updateData)
.where(eq(save.id, id))
.returning();
await this.invalidateCache(id, userId);
return updated;
}
private hasAccess(
savedItem: Save,
requestUserId?: string,
@ -185,6 +227,11 @@ class SavesService {
return nanoid(16);
}
private async invalidateCache(saveId: number, userId: string): Promise<void> {
await redis.del(`save:${saveId}`);
await this.invalidateUserCache(userId);
}
private async invalidateUserCache(userId: string): Promise<void> {
await redis.del(`user_saves:${userId}`);
await redis.del(`public_saves:${userId}`);