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)
list_items list_items[]
@@unique([user_id, name], map: "lists_user_id_name_unique")
@@index([user_id], map: "lists_user_id_index")
}
@ -86,6 +87,7 @@ model sessions {
device String
user_agent String
@@unique([user, device], map: "sessions_user_device_unique")
@@index([user], type: Hash)
}

View file

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

View file

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

View file

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

View file

@ -40,47 +40,57 @@ export default defineEventHandler(async event => {
const validatedBody = createListSchema.parse(parsedBody);
const result = await prisma.$transaction(async tx => {
const existing = await tx.lists.findFirst({
where: { user_id: userId, name: validatedBody.name }
try {
const result = await prisma.$transaction(async tx => {
// 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' });
}
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',
};
throw err;
}
});

View file

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

View file

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