From 45865d3122c493cd6a2dddd3032bc714b0a2ffd6 Mon Sep 17 00:00:00 2001 From: Pascal Syma Date: Fri, 31 Jul 2026 02:14:34 +0200 Subject: [PATCH] feat(quest): add support for private images in answers --- app/lang/de.json | 2 ++ app/lang/en.json | 2 ++ .../hunt/[huntSlug]/[questSlug]/index.vue | 17 +++++++++++++++- .../migration.sql | 2 ++ prisma/schema.prisma | 11 +++++----- server/api/hunt/[huntId]/diashow.get.ts | 4 ++-- server/api/hunt/[huntId]/showcase.get.ts | 6 ++++-- server/api/quest/[questId]/index.get.ts | 3 ++- server/api/quest/[questId]/submit.post.ts | 20 ++++++++++++++++--- 9 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 prisma/migrations/20260730235137_add_private_to_file/migration.sql diff --git a/app/lang/de.json b/app/lang/de.json index 1fe9106..f1956be 100644 --- a/app/lang/de.json +++ b/app/lang/de.json @@ -216,6 +216,8 @@ "finalize": "Antwort finalisieren", "finalize_explain": "Dies gibt den Admins die Möglichkeit deine Antwort zu bewerten und dir Punkte zu geben. Du kannst deine Antwort anschließend nicht mehr ändern!", "last_update": "Letztes Update: {date} von {name}", + "private_image": "Privates Bild", + "private_image_explain": "Private Bilder werden in der öffentlichen Galerie, Diashow und im Showcase ausgeblendet. Admins können sie weiterhin zur Bewertung sehen.", "required_answer": "Erforderliche Antwort", "review": "Bewertung", "reviewed_by": "von {name} am {date}", diff --git a/app/lang/en.json b/app/lang/en.json index 294fe8e..913e3f1 100644 --- a/app/lang/en.json +++ b/app/lang/en.json @@ -216,6 +216,8 @@ "finalize": "Finalize answer", "finalize_explain": "This allows the admins to review your answer and give you points. You cannot change your answer afterwards!", "last_update": "Last update: {date} by {name}", + "private_image": "Private image", + "private_image_explain": "Private images are hidden from the public gallery, diashow and showcase. Admins can still see them for review.", "required_answer": "Required Answer", "review": "Review", "reviewed_by": "by {name} at {date}", diff --git a/app/pages/hunt/[huntSlug]/[questSlug]/index.vue b/app/pages/hunt/[huntSlug]/[questSlug]/index.vue index 1f5ce56..f31c20f 100644 --- a/app/pages/hunt/[huntSlug]/[questSlug]/index.vue +++ b/app/pages/hunt/[huntSlug]/[questSlug]/index.vue @@ -72,6 +72,11 @@ useHead({ const answer = ref(data.value?.answer?.text); const final = ref(data.value?.answer?.final || false); +const privateImage = ref(data.value?.answer?.picture?.private ?? false); + +const privateChanged = computed( + () => (data.value?.answer?.picture?.private ?? false) !== privateImage.value +); const textType = { TEXT: 'text', @@ -114,6 +119,7 @@ async function submit() { if (final.value) { body.set('final', 'true'); } + body.set('private', privateImage.value ? 'true' : 'false'); if (newImage.value) { uploadingImage.value = true; compressing.value = true; @@ -141,6 +147,7 @@ async function submit() { await refresh(); answer.value = data.value?.answer?.text; + privateImage.value = data.value?.answer?.picture?.private ?? false; newImage.value = undefined; } catch (e) { if (xhr.status === 418) { @@ -325,6 +332,13 @@ async function submit() { type="single" file-types="jpg,png,jpeg" /> +

{{ t('page.quest.last_update', { @@ -352,7 +366,8 @@ async function submit() { !( newImage || data.answer?.text !== answer || - data.answer?.final !== final + data.answer?.final !== final || + privateChanged ) || uploading " :loading="uploading" diff --git a/prisma/migrations/20260730235137_add_private_to_file/migration.sql b/prisma/migrations/20260730235137_add_private_to_file/migration.sql new file mode 100644 index 0000000..9d3d006 --- /dev/null +++ b/prisma/migrations/20260730235137_add_private_to_file/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "public"."File" ADD COLUMN "private" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 97b3eed..000be0a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,7 +1,7 @@ generator client { - provider = "prisma-client" + provider = "prisma-client" engineType = "client" - output = "../shared/generated/prisma" + output = "../shared/generated/prisma" } datasource db { @@ -42,9 +42,10 @@ enum UserRole { } model File { - id Int @id @default(autoincrement()) - uuid String - url String + id Int @id @default(autoincrement()) + uuid String + url String + private Boolean @default(false) creator User @relation(fields: [creatorId], references: [id], onUpdate: Cascade, onDelete: Cascade) creatorId Int diff --git a/server/api/hunt/[huntId]/diashow.get.ts b/server/api/hunt/[huntId]/diashow.get.ts index 115464a..3b879d1 100644 --- a/server/api/hunt/[huntId]/diashow.get.ts +++ b/server/api/hunt/[huntId]/diashow.get.ts @@ -77,7 +77,7 @@ export default defineEventHandler(async (event) => { where: { final: true, picture: { - isNot: null + private: false } } } @@ -95,7 +95,7 @@ export default defineEventHandler(async (event) => { some: { final: true, picture: { - isNot: null + private: false } } } diff --git a/server/api/hunt/[huntId]/showcase.get.ts b/server/api/hunt/[huntId]/showcase.get.ts index 6b707c7..1e8bcf4 100644 --- a/server/api/hunt/[huntId]/showcase.get.ts +++ b/server/api/hunt/[huntId]/showcase.get.ts @@ -138,7 +138,8 @@ export default defineEventHandler(async (event) => { where: { review: { showcase: true - } + }, + picture: { private: false } }, orderBy: [ { @@ -217,6 +218,7 @@ export default defineEventHandler(async (event) => { pictureCount, totalScore, leaderboard, - ...hunt + ...hunt, + quests: hunt.quests.filter((q) => q.answers.length > 0) }; }); diff --git a/server/api/quest/[questId]/index.get.ts b/server/api/quest/[questId]/index.get.ts index 30becaa..a05d7d7 100644 --- a/server/api/quest/[questId]/index.get.ts +++ b/server/api/quest/[questId]/index.get.ts @@ -71,7 +71,8 @@ export default defineEventHandler(async (event) => { text: true, picture: { select: { - url: true + url: true, + private: true } }, review: { diff --git a/server/api/quest/[questId]/submit.post.ts b/server/api/quest/[questId]/submit.post.ts index 6f8d44c..9b37782 100644 --- a/server/api/quest/[questId]/submit.post.ts +++ b/server/api/quest/[questId]/submit.post.ts @@ -101,7 +101,8 @@ export default defineEventHandler(async (event) => { .mime(['image/png', 'image/jpeg', 'image/jpg']) .max(10_000_000) .optional(), - final: zh.boolAsString.optional() + final: zh.boolAsString.optional(), + private: zh.boolAsString.default(false) }) .parse(formDataObj); @@ -110,7 +111,8 @@ export default defineEventHandler(async (event) => { ? quest.answers![0]! : { updatedAt: null, - id: null + id: null, + picture: null }; if ( @@ -158,7 +160,8 @@ export default defineEventHandler(async (event) => { const file = await tx.file.create({ data: { ...picture, - answerId: prevAnswer.id + answerId: prevAnswer.id, + private: data.private }, select: { id: true } }); @@ -190,6 +193,7 @@ export default defineEventHandler(async (event) => { data: { ...picture, answerId: answer.id, + private: data.private, answers: { connect: answer } @@ -210,6 +214,16 @@ export default defineEventHandler(async (event) => { lang: data.lang } }); + if (prevAnswer.picture) { + await prisma.file.update({ + where: { + id: prevAnswer.picture.id + }, + data: { + private: data.private + } + }); + } } else { await prisma.questAnswer.create({ data: {