Small updates

This commit is contained in:
Dum 2026-03-01 20:34:34 +05:30
parent a5baed2ee4
commit ed08f2daab
8 changed files with 102 additions and 84 deletions

View file

@ -0,0 +1,24 @@
/*
Warnings:
- A unique constraint covering the columns `[user_id,name]` on the table `lists` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[user,device]` on the table `sessions` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE INDEX "bookmarks_user_id_idx" ON "bookmarks" USING HASH ("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "lists_user_id_name_unique" ON "lists"("user_id", "name");
-- CreateIndex
CREATE INDEX "progress_items_user_id_idx" ON "progress_items" USING HASH ("user_id");
-- CreateIndex
CREATE INDEX "sessions_user_idx" ON "sessions" USING HASH ("user");
-- CreateIndex
CREATE UNIQUE INDEX "sessions_user_device_unique" ON "sessions"("user", "device");
-- CreateIndex
CREATE INDEX "watch_history_user_id_watched_at_idx" ON "watch_history"("user_id", "watched_at" DESC);

View file

@ -51,6 +51,7 @@ model lists {
public Boolean @default(false) public Boolean @default(false)
list_items list_items[] list_items list_items[]
@@unique([user_id, name], map: "lists_user_id_name_unique")
@@index([user_id], map: "lists_user_id_index") @@index([user_id], map: "lists_user_id_index")
} }
@ -86,6 +87,7 @@ model sessions {
device String device String
user_agent String user_agent String
@@unique([user, device], map: "sessions_user_device_unique")
@@index([user], type: Hash) @@index([user], type: Hash)
} }

View file

@ -3,6 +3,7 @@ import { prisma } from '#imports';
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const id = event.context.params?.id; const id = event.context.params?.id;
const listInfo = await prisma.lists.findUnique({ const listInfo = await prisma.lists.findUnique({
relationLoadStrategy: 'join',
where: { where: {
id: id, id: id,
}, },

View file

@ -39,18 +39,13 @@ export default defineEventHandler(async event => {
const body = await readBody(event); const body = await readBody(event);
const validatedBody = updateSessionSchema.parse(body); const validatedBody = updateSessionSchema.parse(body);
if (validatedBody.deviceName) { // Use update return value directly — no redundant findUnique
await prisma.sessions.update({ const updatedSession = validatedBody.deviceName
where: { id: sessionId }, ? await prisma.sessions.update({
data: { where: { id: sessionId },
device: validatedBody.deviceName, data: { device: validatedBody.deviceName },
}, })
}); : targetedSession;
}
const updatedSession = await prisma.sessions.findUnique({
where: { id: sessionId },
});
return { return {
id: updatedSession.id, id: updatedSession.id,
@ -65,16 +60,7 @@ export default defineEventHandler(async event => {
} }
if (event.method === 'DELETE') { if (event.method === 'DELETE') {
const sid = event.context.params?.sid; // targetedSession already validated above — no redundant findUnique or session bump needed
const sessionExists = await prisma.sessions.findUnique({
where: { id: sid },
});
if (!sessionExists) {
return { success: true };
}
const session = await useAuth().getSessionAndBump(sid);
await prisma.sessions.delete({ await prisma.sessions.delete({
where: { id: sessionId }, where: { id: sessionId },
}); });

View file

@ -32,6 +32,7 @@ export default defineEventHandler(async event => {
const validatedBody = updateListSchema.parse(body); const validatedBody = updateListSchema.parse(body);
const list = await prisma.lists.findUnique({ const list = await prisma.lists.findUnique({
relationLoadStrategy: 'join',
where: { id: validatedBody.list_id }, where: { id: validatedBody.list_id },
include: { list_items: true }, include: { list_items: true },
}); });
@ -100,6 +101,7 @@ export default defineEventHandler(async event => {
} }
return tx.lists.findUnique({ return tx.lists.findUnique({
relationLoadStrategy: 'join',
where: { id: list.id }, where: { id: list.id },
include: { list_items: true }, include: { list_items: true },
}); });

View file

@ -40,47 +40,57 @@ export default defineEventHandler(async event => {
const validatedBody = createListSchema.parse(parsedBody); const validatedBody = createListSchema.parse(parsedBody);
const result = await prisma.$transaction(async tx => { try {
const existing = await tx.lists.findFirst({ const result = await prisma.$transaction(async tx => {
where: { user_id: userId, name: validatedBody.name } // App-level guard for a clean 409 message
const existing = await tx.lists.findFirst({
where: { user_id: userId, name: validatedBody.name },
});
if (existing) {
throw createError({ statusCode: 409, message: 'A list with this name already exists' });
}
const now = new Date();
const newList = await tx.lists.create({
data: {
id: uuidv7(),
user_id: userId,
name: validatedBody.name,
description: validatedBody.description || null,
public: validatedBody.public || false,
updated_at: now,
},
});
if (validatedBody.items && validatedBody.items.length > 0) {
await tx.list_items.createMany({
data: validatedBody.items.map(item => ({
id: uuidv7(),
list_id: newList.id,
tmdb_id: item.tmdb_id,
type: item.type,
})),
skipDuplicates: true,
});
}
return tx.lists.findUnique({
relationLoadStrategy: 'join',
where: { id: newList.id },
include: { list_items: true },
});
}); });
if (existing) { return {
list: result,
message: 'List created successfully',
};
} catch (err: any) {
// DB-level safety net: catch unique constraint violation from @@unique([user_id, name])
if (err.code === 'P2002') {
throw createError({ statusCode: 409, message: 'A list with this name already exists' }); throw createError({ statusCode: 409, message: 'A list with this name already exists' });
} }
throw err;
const now = new Date(); }
const newList = await tx.lists.create({
data: {
id: uuidv7(),
user_id: userId,
name: validatedBody.name,
description: validatedBody.description || null,
public: validatedBody.public || false,
updated_at: now,
},
});
if (validatedBody.items && validatedBody.items.length > 0) {
await tx.list_items.createMany({
data: validatedBody.items.map(item => ({
id: uuidv7(),
list_id: newList.id,
tmdb_id: item.tmdb_id,
type: item.type, // Type is mapped here
})),
skipDuplicates: true,
});
}
return tx.lists.findUnique({
where: { id: newList.id },
include: { list_items: true },
});
});
return {
list: result,
message: 'List created successfully',
};
}); });

View file

@ -144,27 +144,14 @@ export default defineEventHandler(async event => {
if (body.seasonId) whereClause.season_id = body.seasonId; if (body.seasonId) whereClause.season_id = body.seasonId;
if (body.episodeId) whereClause.episode_id = body.episodeId; if (body.episodeId) whereClause.episode_id = body.episodeId;
const itemsToDelete = await prisma.watch_history.findMany({ // Use deleteMany return count directly — no redundant findMany
where: whereClause, const { count } = await prisma.watch_history.deleteMany({
});
if (itemsToDelete.length === 0) {
return {
success: true,
count: 0,
tmdbId,
episodeId: body.episodeId,
seasonId: body.seasonId,
};
}
await prisma.watch_history.deleteMany({
where: whereClause, where: whereClause,
}); });
return { return {
success: true, success: true,
count: itemsToDelete.length, count,
tmdbId, tmdbId,
episodeId: body.episodeId, episodeId: body.episodeId,
seasonId: body.seasonId, seasonId: body.seasonId,

View file

@ -40,13 +40,19 @@ export function useAuth() {
const now = new Date(); const now = new Date();
const expiryDate = new Date(now.getTime() + SESSION_EXPIRY_MS); const expiryDate = new Date(now.getTime() + SESSION_EXPIRY_MS);
// Cleanup existing session for this device // Atomic upsert — backed by @@unique([user, device]) in schema
await prisma.sessions.deleteMany({ return await prisma.sessions.upsert({
where: { user, device }, where: {
}); sessions_user_device_unique: { user, device },
},
return await prisma.sessions.create({ update: {
data: { id: uuidv7(),
user_agent: userAgent,
created_at: now,
accessed_at: now,
expires_at: expiryDate,
},
create: {
id: uuidv7(), id: uuidv7(),
user, user,
device, device,