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: {