refactor: move stuff to presetsDb and start work on sharing

This commit is contained in:
2026-01-11 00:59:55 +01:00
parent 409f32090e
commit 6864748174
8 changed files with 211 additions and 118 deletions

View File

@@ -1,16 +1,10 @@
<template>
<div class="flex flex-col">
<form
:id="formId"
@submit.prevent="form.handleSubmit"
class="space-y-6"
>
<form :id="formId" @submit.prevent="form.handleSubmit" class="space-y-6">
<FieldGroup>
<form.Field v-slot="{ field }" name="name">
<Field :data-invalid="isInvalid(field)">
<FieldLabel :for="`${formId}-name`">
Preset name
</FieldLabel>
<FieldLabel :for="`${formId}-name`"> Preset name </FieldLabel>
<Input
:id="`${formId}-name`"
:name="field.name"
@@ -101,10 +95,12 @@ const props = defineProps<{
}>();
const emit = defineEmits<{
(e: 'success'): void
(e: "success"): void;
}>();
const formId = computed(() => props.isEdit ? `form-edit-preset-${props.presetId}` : 'form-new-preset');
const formId = computed(() =>
props.isEdit ? `form-edit-preset-${props.presetId}` : "form-new-preset",
);
const editorOptions = {
automaticLayout: true,
@@ -140,25 +136,32 @@ const form = useForm({
...value,
iceServers: JSON.parse(value.iceServers),
};
let url = "/api/presets/create";
let method = "POST";
if (props.isEdit && props.presetId) {
url = `/api/presets/${props.presetId}`;
method = "PUT";
}
const request = await $fetch(url, {
method: method as any,
body: JSON.stringify(parsedValue),
});
if (request.success) {
toast.success(props.isEdit ? "Preset updated successfully!" : "Preset created successfully!");
emit('success');
} else {
toast.error(props.isEdit ? "Failed to update preset." : "Failed to create preset.");
try {
const request = await $fetch<{ success: boolean; message: string }>(url, {
method: method as any,
body: JSON.stringify(parsedValue),
});
if (request.success) {
toast.success(
props.isEdit
? "Preset updated successfully!"
: "Preset created successfully!",
);
emit("success");
}
} catch (e) {
toast.error(
props.isEdit ? "Failed to update preset." : "Failed to create preset.",
);
}
},
});

View File

@@ -1,4 +1,4 @@
import { eq } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import { db } from "~/lib/db/index";
import * as schema from "~/lib/db/schema";
@@ -10,3 +10,117 @@ export async function getUserPresets(clerkUserId: string) {
},
});
}
export async function getPresetById(presetId: string) {
return await db.query.presets.findFirst({
where: eq(schema.presets.id, presetId),
});
}
export async function userHasPresetAccess(
presetId: string,
userId: string,
): Promise<boolean> {
const preset = await getPresetById(presetId);
if (!preset) return false;
if (preset.createdBy === userId) return true;
const userPreset = await db.query.presetUsers.findFirst({
where: and(
eq(schema.presetUsers.presetId, presetId),
eq(schema.presetUsers.userId, userId),
),
});
return !!userPreset;
}
export async function createPreset(
userId: string,
name: string,
iceServers: string,
isDefault: boolean = false,
) {
const presetCreate = await db
.insert(schema.presets)
.values({
createdBy: userId,
name: name,
iceServers: iceServers,
})
.returning({ insertedId: schema.presets.id });
const insertedId = presetCreate[0]?.insertedId;
if (!insertedId) {
throw new Error("Failed to get inserted preset ID");
}
await db.insert(schema.presetUsers).values({
presetId: insertedId,
userId: userId,
isDefault: isDefault,
});
return insertedId;
}
export async function updatePreset(
presetId: string,
name: string,
iceServers: string,
) {
await db
.update(schema.presets)
.set({
name: name,
iceServers: iceServers,
})
.where(eq(schema.presets.id, presetId));
}
export async function setPresetAsDefault(presetId: string, userId: string) {
await db
.update(schema.presetUsers)
.set({ isDefault: false })
.where(eq(schema.presetUsers.userId, userId));
// set as default
await db
.update(schema.presetUsers)
.set({ isDefault: true })
.where(
and(
eq(schema.presetUsers.presetId, presetId),
eq(schema.presetUsers.userId, userId),
),
);
}
export async function unsetPresetAsDefault(presetId: string, userId: string) {
await db
.update(schema.presetUsers)
.set({ isDefault: false })
.where(
and(
eq(schema.presetUsers.presetId, presetId),
eq(schema.presetUsers.userId, userId),
),
);
}
export async function updatePresetDefaultStatus(
presetId: string,
userId: string,
isDefault: boolean,
) {
if (isDefault) {
await setPresetAsDefault(presetId, userId);
} else {
await unsetPresetAsDefault(presetId, userId);
}
}
export async function deletePreset(presetId: string) {
await db.delete(schema.presets).where(eq(schema.presets.id, presetId));
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 px-4">
<div v-for="presetUser in data!.data" :key="presetUser.preset.id">
<Card class="flex flex-col h-full">
<CardHeader>
@@ -124,9 +124,22 @@ async function deletePreset(id: string) {
}
}
function handleShare(preset: any) {
// To be implemented by user
console.log("Share preset:", preset.id);
async function handleShare(preset: any) {
if (!confirm("Do you want to share this preset?")) return;
try {
const response = await $fetch(`/api/presets/${preset.id}/share`, {
method: "POST",
});
if (!response.success || !response.shareId) {
toast.error("Failed to generate shareable link");
return;
}
const shareableLink = `${window.location.origin}/presets/shared/${response.shareId}`;
navigator.clipboard.writeText(shareableLink);
toast.success("Link copied to clipboard");
} catch (error) {
toast.error("Failed to share preset");
}
}
// below types are ai generated

View File

@@ -1,10 +1,8 @@
import { eq, and } from "drizzle-orm";
import { db } from "~/lib/db";
import { presets, presetUsers } from "~/lib/db/schema";
import { getPresetById, deletePreset } from "~/lib/utils/presetsDb";
export default defineEventHandler(async (event) => {
const { isAuthenticated, userId } = event.context.auth();
if (!isAuthenticated || !userId) {
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
}
@@ -15,20 +13,21 @@ export default defineEventHandler(async (event) => {
}
// Check if the user is the creator of the preset
const preset = await db.query.presets.findFirst({
where: eq(presets.id, id),
});
const preset = await getPresetById(id);
if (!preset) {
throw createError({ statusCode: 404, statusMessage: "Preset not found" });
}
if (preset.createdBy !== userId) {
throw createError({ statusCode: 403, statusMessage: "Forbidden: You can only delete your own presets" });
throw createError({
statusCode: 403,
statusMessage: "Forbidden: You can only delete your own presets",
});
}
// Delete the preset (cascades to presetUsers)
await db.delete(presets).where(eq(presets.id, id));
await deletePreset(id);
return { success: true };
});

View File

@@ -1,10 +1,8 @@
import { eq } from "drizzle-orm";
import { db } from "~/lib/db";
import { presets, presetUsers } from "~/lib/db/schema";
import { getPresetById, userHasPresetAccess } from "~/lib/utils/presetsDb";
export default defineEventHandler(async (event) => {
const { isAuthenticated, userId } = event.context.auth();
if (!isAuthenticated || !userId) {
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
}
@@ -15,20 +13,15 @@ export default defineEventHandler(async (event) => {
}
// Fetch the preset
const preset = await db.query.presets.findFirst({
where: eq(presets.id, id),
});
const preset = await getPresetById(id);
if (!preset) {
throw createError({ statusCode: 404, statusMessage: "Preset not found" });
}
// Check if user has access (either creator or has it in their presetUsers)
const userPreset = await db.query.presetUsers.findFirst({
where: eq(presetUsers.presetId, id),
});
if (preset.createdBy !== userId && (!userPreset || userPreset.userId !== userId)) {
// Check if user has access
const hasAccess = await userHasPresetAccess(id, userId);
if (!hasAccess) {
throw createError({ statusCode: 403, statusMessage: "Forbidden" });
}

View File

@@ -1,10 +1,12 @@
import { eq, and } from "drizzle-orm";
import { db } from "~/lib/db";
import { presets, presetUsers } from "~/lib/db/schema";
import {
getPresetById,
updatePreset,
updatePresetDefaultStatus,
} from "~/lib/utils/presetsDb";
export default defineEventHandler(async (event) => {
const { isAuthenticated, userId } = event.context.auth();
if (!isAuthenticated || !userId) {
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
}
@@ -15,44 +17,27 @@ export default defineEventHandler(async (event) => {
}
const body = await readBody(event);
// Verify ownership
const preset = await db.query.presets.findFirst({
where: eq(presets.id, id),
});
const preset = await getPresetById(id);
if (!preset) {
throw createError({ statusCode: 404, statusMessage: "Preset not found" });
}
if (preset.createdBy !== userId) {
throw createError({ statusCode: 403, statusMessage: "Forbidden: You can only edit your own presets" });
throw createError({
statusCode: 403,
statusMessage: "Forbidden: You can only edit your own presets",
});
}
// Update preset
await db.update(presets)
.set({
name: body.name,
iceServers: JSON.stringify(body.iceServers),
})
.where(eq(presets.id, id));
await updatePreset(id, body.name, JSON.stringify(body.iceServers));
// Update default status in presetUsers
if (body.default !== undefined) {
// If setting as default, first unset all other defaults for this user
if (body.default) {
await db.update(presetUsers)
.set({ isDefault: false })
.where(eq(presetUsers.userId, userId));
}
// Update the default status for this preset
await db.update(presetUsers)
.set({ isDefault: body.default })
.where(and(
eq(presetUsers.presetId, id),
eq(presetUsers.userId, userId)
));
await updatePresetDefaultStatus(id, userId, body.default);
}
return { success: true };

View File

View File

@@ -1,65 +1,51 @@
import { clerkClient } from "@clerk/nuxt/server";
import { db } from "~/lib/db";
import * as schema from "~/lib/db/schema";
import { schema as zodSchema } from "~/lib/schema/new-preset";
import { createPreset } from "~/lib/utils/presetsDb";
export default defineEventHandler(async (req) => {
const reqBody = await readBody(req);
if (reqBody.iceServers) {
if (reqBody && reqBody.iceServers) {
reqBody.iceServers = JSON.stringify(reqBody.iceServers);
}
const body = zodSchema.safeParse(reqBody);
if (body.success === false) {
console.log(body.error, JSON.stringify(reqBody));
throw createError({
statusCode: 400,
statusMessage: "Invalid request body",
});
setResponseStatus(req, 400);
return {
success: false,
message: "Invalid request body",
};
}
const { isAuthenticated, userId } = req.context.auth();
if (!isAuthenticated) {
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
setResponseStatus(req, 401);
return {
success: false,
message: "Unauthorized",
};
}
const user = await clerkClient(req).users.getUser(userId);
try {
const presetCreate = await db
.insert(schema.presets)
.values({
createdBy: user.id,
name: body.data.name,
iceServers: body.data.iceServers,
})
.returning({ insertedId: schema.presets.id });
await db.insert(schema.presetUsers).values({
presetId: presetCreate[0].insertedId,
userId: user.id,
isDefault: body.data.default,
});
} catch (e) {
console.error("Error creating preset:", e);
const error = e as PostgresError;
if (error.code === "23505") {
throw createError({
statusCode: 400,
statusMessage: "A preset with this name already exists",
});
}
throw createError({
statusCode: 500,
statusMessage: "Failed to create preset",
});
await createPreset(
user.id,
body.data.name,
body.data.iceServers,
body.data.default,
);
} catch (e: any) {
setResponseStatus(req, 500);
return {
success: false,
message: "Database error",
};
}
return {
success: true,
message: "Preset created successfully",
};
});
interface PostgresError extends Error {
code: string;
detail?: string;
constraint?: string;
}