+
{{ hunt.name }}
+
{{ hunt.name }}
{{ hunt.description }}
diff --git a/app/pages/profile.vue b/app/pages/profile.vue
new file mode 100644
index 0000000..aaea34e
--- /dev/null
+++ b/app/pages/profile.vue
@@ -0,0 +1,11 @@
+
+
+
+ profile
+
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
index dcbec56..973ca17 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: '3.9'
-
volumes:
db:
redis:
diff --git a/prisma/migrations/20250804155319_change_auth_to_password/migration.sql b/prisma/migrations/20250804155319_change_auth_to_password/migration.sql
new file mode 100644
index 0000000..d9e6d9f
--- /dev/null
+++ b/prisma/migrations/20250804155319_change_auth_to_password/migration.sql
@@ -0,0 +1,15 @@
+/*
+ Warnings:
+
+ - You are about to drop the `UserLoginLinks` table. If the table is not empty, all the data it contains will be lost.
+
+*/
+-- DropForeignKey
+ALTER TABLE "public"."UserLoginLinks" DROP CONSTRAINT "UserLoginLinks_userId_fkey";
+
+-- AlterTable
+ALTER TABLE "public"."User" ADD COLUMN "password" TEXT NOT NULL DEFAULT '',
+ALTER COLUMN "role" SET DEFAULT 'USER';
+
+-- DropTable
+DROP TABLE "public"."UserLoginLinks";
diff --git a/prisma/migrations/20250804160026_remember_uploaded_images/migration.sql b/prisma/migrations/20250804160026_remember_uploaded_images/migration.sql
new file mode 100644
index 0000000..c94af9f
--- /dev/null
+++ b/prisma/migrations/20250804160026_remember_uploaded_images/migration.sql
@@ -0,0 +1,40 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `picture` on the `QuestAnswer` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "public"."QuestAnswer" DROP COLUMN "picture",
+ADD COLUMN "pictureId" INTEGER;
+
+-- CreateTable
+CREATE TABLE "public"."File" (
+ "id" SERIAL NOT NULL,
+ "uuid" TEXT NOT NULL,
+ "creatorId" INTEGER NOT NULL,
+ "huntId" INTEGER,
+ "questId" INTEGER,
+ "answerId" INTEGER,
+ "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "File_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "File_uuid_key" ON "public"."File"("uuid");
+
+-- AddForeignKey
+ALTER TABLE "public"."File" ADD CONSTRAINT "File_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "public"."File" ADD CONSTRAINT "File_huntId_fkey" FOREIGN KEY ("huntId") REFERENCES "public"."Hunt"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "public"."File" ADD CONSTRAINT "File_questId_fkey" FOREIGN KEY ("questId") REFERENCES "public"."HuntQuest"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "public"."File" ADD CONSTRAINT "File_answerId_fkey" FOREIGN KEY ("answerId") REFERENCES "public"."QuestAnswer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "public"."QuestAnswer" ADD CONSTRAINT "QuestAnswer_pictureId_fkey" FOREIGN KEY ("pictureId") REFERENCES "public"."File"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/migrations/20250804160243_allow_uploading_images_to_hunt_and_quest/migration.sql b/prisma/migrations/20250804160243_allow_uploading_images_to_hunt_and_quest/migration.sql
new file mode 100644
index 0000000..82134d5
--- /dev/null
+++ b/prisma/migrations/20250804160243_allow_uploading_images_to_hunt_and_quest/migration.sql
@@ -0,0 +1,18 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `image` on the `HuntQuest` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "public"."Hunt" ADD COLUMN "pictureId" INTEGER;
+
+-- AlterTable
+ALTER TABLE "public"."HuntQuest" DROP COLUMN "image",
+ADD COLUMN "pictureId" INTEGER;
+
+-- AddForeignKey
+ALTER TABLE "public"."Hunt" ADD CONSTRAINT "Hunt_pictureId_fkey" FOREIGN KEY ("pictureId") REFERENCES "public"."File"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "public"."HuntQuest" ADD CONSTRAINT "HuntQuest_pictureId_fkey" FOREIGN KEY ("pictureId") REFERENCES "public"."File"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/migrations/20250804160732_add_link_to_file/migration.sql b/prisma/migrations/20250804160732_add_link_to_file/migration.sql
new file mode 100644
index 0000000..3967477
--- /dev/null
+++ b/prisma/migrations/20250804160732_add_link_to_file/migration.sql
@@ -0,0 +1,12 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[url]` on the table `File` will be added. If there are existing duplicate values, this will fail.
+ - Added the required column `url` to the `File` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE "public"."File" ADD COLUMN "url" TEXT NOT NULL;
+
+-- CreateIndex
+CREATE UNIQUE INDEX "File_url_key" ON "public"."File"("url");
diff --git a/prisma/migrations/20250804170804_rename_reveal_on_hunt/migration.sql b/prisma/migrations/20250804170804_rename_reveal_on_hunt/migration.sql
new file mode 100644
index 0000000..96c8776
--- /dev/null
+++ b/prisma/migrations/20250804170804_rename_reveal_on_hunt/migration.sql
@@ -0,0 +1,9 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `revealPictures` on the `Hunt` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "public"."Hunt" DROP COLUMN "revealPictures",
+ADD COLUMN "revealQuests" BOOLEAN NOT NULL DEFAULT false;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 4362662..7a7f9f4 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -8,15 +8,14 @@ datasource db {
}
model User {
- id Int @id @default(autoincrement())
- email String @unique
- name String
- role UserRole
+ id Int @id @default(autoincrement())
+ email String @unique
+ password String @default("")
+ name String
+ role UserRole @default(USER)
emailConfirmedAt DateTime? @db.Timestamptz(3)
- loginLinks UserLoginLinks[]
-
deactivated Boolean @default(false)
createdAt DateTime @default(now()) @db.Timestamptz(3)
@@ -30,6 +29,8 @@ model User {
teamsMember TeamMember[]
answers QuestAnswer[] @relation("AnswerMember")
reviews QuestAnswer[] @relation("AnswerReview")
+
+ uploaded_files File[]
}
enum UserRole {
@@ -38,16 +39,28 @@ enum UserRole {
ADMIN
}
-model UserLoginLinks {
- id Int @id @default(autoincrement())
+model File {
+ id Int @id @default(autoincrement())
+ uuid String @unique
+ url String @unique
- secret String
- used Boolean @default(false)
+ creator User @relation(fields: [creatorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+ creatorId Int
- user User @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: Cascade)
- userId Int
+ hunt Hunt? @relation(name: "OldHuntFiles", fields: [huntId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+ huntId Int?
+
+ quest HuntQuest? @relation(name: "OldQuestFiles", fields: [questId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+ questId Int?
+
+ answer QuestAnswer? @relation(name: "OldAnswerFiles", fields: [answerId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+ answerId Int?
createdAt DateTime @default(now()) @db.Timestamptz(3)
+
+ answers QuestAnswer[]
+ quests HuntQuest[]
+ hunts Hunt[]
}
model Hunt {
@@ -55,16 +68,19 @@ model Hunt {
name String
description String
- virtual Boolean @default(false)
- start DateTime? @db.Timestamptz(3)
- end DateTime? @db.Timestamptz(3)
- allowJoin Boolean @default(false)
- revealPictures Boolean @default(false)
- revealAnswers Boolean @default(false)
+ virtual Boolean @default(false)
+ start DateTime? @db.Timestamptz(3)
+ end DateTime? @db.Timestamptz(3)
+ allowJoin Boolean @default(false)
+ revealQuests Boolean @default(false)
+ revealAnswers Boolean @default(false)
creator User @relation(fields: [creatorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
creatorId Int
+ picture File? @relation(fields: [pictureId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+ pictureId Int?
+
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
deletedAt DateTime? @db.Timestamptz(3)
@@ -74,6 +90,8 @@ model Hunt {
quests HuntQuest[]
+ old_files File[] @relation("OldHuntFiles")
+
@@index([creatorId])
}
@@ -140,10 +158,12 @@ model HuntQuest {
title String
description String @default("")
question String?
- image String?
location String?
locationLink String?
+ picture File? @relation(fields: [pictureId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+ pictureId Int?
+
order Int @default(0)
optional Boolean @default(true)
pictureRequired Boolean @default(false)
@@ -161,13 +181,14 @@ model HuntQuest {
answers QuestAnswer[]
+ old_files File[] @relation("OldQuestFiles")
+
@@index([huntId])
}
model QuestAnswer {
id Int @id @default(autoincrement())
text String?
- picture String?
location String?
lat Decimal? @db.Decimal(10, 8)
lon Decimal? @db.Decimal(10, 8)
@@ -181,6 +202,9 @@ model QuestAnswer {
quest HuntQuest @relation(fields: [questId], references: [id], onUpdate: Cascade, onDelete: Cascade)
questId Int
+ picture File? @relation(fields: [pictureId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+ pictureId Int?
+
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
@@ -190,6 +214,8 @@ model QuestAnswer {
score Int? @db.SmallInt
review String?
+ old_files File[] @relation("OldAnswerFiles")
+
@@index([teamId])
@@index([memberId])
@@index([questId])
diff --git a/prisma/seed/hunt.ts b/prisma/seed/hunt.ts
index 4982c96..e1e1e8e 100644
--- a/prisma/seed/hunt.ts
+++ b/prisma/seed/hunt.ts
@@ -1,182 +1,203 @@
import { PrismaClient } from '@prisma/client';
+import { randomUUID } from 'node:crypto';
const prisma = new PrismaClient();
async function main() {
- // region Users
- const admin = await prisma.user.upsert({
- where: { email: 'admin@mail.com' },
- update: {},
- create: {
- email: 'admin@mail.com',
- name: 'Admin user',
- role: 'ADMIN',
- emailConfirmedAt: new Date()
- }
- });
- const huntMember = await prisma.user.upsert({
- where: { email: 'team@mail.com' },
- update: {},
- create: {
- email: 'team@mail.com',
- name: 'Hunt member',
- role: 'ADMIN',
- emailConfirmedAt: new Date()
- }
- });
+ await prisma.$transaction(async (tx) => {
+ // region Users
+ const admin = await tx.user.upsert({
+ where: { email: 'admin@mail.com' },
+ update: {},
+ create: {
+ email: 'admin@mail.com',
+ name: 'Admin user',
+ role: 'ADMIN',
+ emailConfirmedAt: new Date()
+ }
+ });
+ const huntMember = await tx.user.upsert({
+ where: { email: 'team@mail.com' },
+ update: {},
+ create: {
+ email: 'team@mail.com',
+ name: 'Hunt member',
+ role: 'ADMIN',
+ emailConfirmedAt: new Date()
+ }
+ });
- const user = await prisma.user.upsert({
- where: { email: 'user@mail.com' },
- update: {},
- create: {
- email: 'user@mail.com',
- name: 'User',
- role: 'USER',
- emailConfirmedAt: new Date()
- }
- });
- const teamMember = await prisma.user.upsert({
- where: { email: 'member@mail.com' },
- update: {},
- create: {
- email: 'member@mail.com',
- name: 'Team Member',
- role: 'USER',
- emailConfirmedAt: new Date()
- }
- });
- // endregion
+ const user = await tx.user.upsert({
+ where: { email: 'user@mail.com' },
+ update: {},
+ create: {
+ email: 'user@mail.com',
+ name: 'User',
+ role: 'USER',
+ emailConfirmedAt: new Date()
+ }
+ });
+ const teamMember = await tx.user.upsert({
+ where: { email: 'member@mail.com' },
+ update: {},
+ create: {
+ email: 'member@mail.com',
+ name: 'Team Member',
+ role: 'USER',
+ emailConfirmedAt: new Date()
+ }
+ });
+ // endregion
- const hunt = await prisma.hunt.create({
- include: {
- teams: true,
- quests: {
- orderBy: {
- order: 'asc'
+ const hunt = await tx.hunt.create({
+ include: {
+ teams: true,
+ quests: {
+ orderBy: {
+ order: 'asc'
+ }
+ }
+ },
+ data: {
+ name: 'Bremer Stadtrallye!',
+ description: 'DIE Rallye durch Bremen!',
+ start: new Date('2025-08-23T10:00:00.000Z'),
+ end: new Date('2025-08-30T10:00:00.000Z'),
+ allowJoin: true,
+ revealQuests: true,
+ revealAnswers: false,
+ quests: {
+ create: [
+ {
+ title: 'MOIN!',
+ description: 'Finde das größte MOIN',
+ question: 'Wo steht es?',
+ order: 0,
+ optional: false,
+ pictureRequired: true,
+ textRequired: false,
+ textType: 'TEXT',
+ textSolution: 'Am Herdentor',
+ points: 10
+ },
+ {
+ title: 'Die Musikanten',
+ description:
+ 'Dort wo die vier Bremer Tiere musizieren, müsst ihr ihre Beine anfassen!',
+ question: 'Wie viele Beine haben die Musikanten zusammen?',
+ extraText:
+ 'Mehr Punkte, je mehr unterschiedliche Beine ihre berührt!',
+ points: 10,
+ extraPoints: 5,
+ pictureRequired: true,
+ optional: false,
+ textRequired: true,
+ textType: 'INTEGER',
+ textSolution: '14',
+ order: 1
+ },
+ {
+ title: 'Tolle Aussicht',
+ description:
+ 'Mache ein Bild aus einem Gebäude der Hochschule, von so weit oben wie möglich!',
+ points: 10,
+ extraPoints: 10,
+ extraText:
+ 'Wenn ihr das einzige Team in diesem Gebäude/Standort oder die Höchsten wart!',
+ optional: false,
+ pictureRequired: true,
+ textRequired: false,
+ question: 'In welchem Gebäude und Stockwerk wart ihr?',
+ textType: 'TEXT',
+ order: 2
+ }
+ ]
+ },
+ creatorId: admin.id,
+ members: {
+ create: [
+ {
+ memberId: huntMember.id
+ }
+ ]
+ },
+ teams: {
+ create: [
+ {
+ ownerId: user.id,
+ name: 'Team 1',
+ description: 'Das beste Informatik Team!',
+ password: 'top secret',
+ members: {
+ create: [
+ {
+ memberId: teamMember.id
+ }
+ ]
+ }
+ }
+ ]
}
}
- },
- data: {
- name: 'Bremer Stadtrallye!',
- description: 'DIE Rallye durch Bremen!',
- start: new Date('2025-08-23T10:00:00.000Z'),
- end: new Date('2025-08-30T10:00:00.000Z'),
+ });
- quests: {
- create: [
- {
- title: 'MOIN!',
- description: 'Finde das größte MOIN',
- question: 'Wo steht es?',
- order: 0,
- optional: false,
- pictureRequired: true,
- textRequired: false,
- textType: 'TEXT',
- textSolution: 'Am Herdentor',
- points: 10
- },
- {
- title: 'Die Musikanten',
- description:
- 'Dort wo die vier Bremer Tiere musizieren, müsst ihr ihre Beine anfassen!',
- question: 'Wie viele Beine haben die Musikanten zusammen?',
- extraText:
- 'Mehr Punkte, je mehr unterschiedliche Beine ihre berührt!',
- points: 10,
- extraPoints: 5,
- pictureRequired: true,
- optional: false,
- textRequired: true,
- textType: 'INTEGER',
- textSolution: '14',
- order: 1
- },
- {
- title: 'Tolle Aussicht',
- description:
- 'Mache ein Bild aus einem Gebäude der Hochschule, von so weit oben wie möglich!',
- points: 10,
- extraPoints: 10,
- extraText:
- 'Wenn ihr das einzige Team in diesem Gebäude/Standort oder die Höchsten wart!',
- optional: false,
- pictureRequired: true,
- textRequired: false,
- question: 'In welchem Gebäude und Stockwerk wart ihr?',
- textType: 'TEXT',
- order: 2
+ const team = hunt.teams[0];
+
+ const images = [
+ '/img/dummy/moin1.jpeg',
+ '/img/dummy/musikanten1.jpg',
+ '/img/dummy/aussicht1.jpeg'
+ ];
+ const answers = await tx.questAnswer.createManyAndReturn({
+ data: [
+ {
+ teamId: team.id,
+ memberId: user.id,
+ questId: hunt.quests[0].id,
+ text: 'Am Herdentor',
+ location: 'An der Haltestelle Herdentor',
+ lat: 53.079138,
+ lon: 8.809734,
+ reviewerId: admin.id,
+ score: 10,
+ review: 'Schönes Bild!'
+ },
+ {
+ teamId: team.id,
+ memberId: teamMember.id,
+ questId: hunt.quests[1].id,
+ text: '14',
+ location: 'Marktplatz',
+ score: 10
+ },
+ {
+ teamId: team.id,
+ memberId: teamMember.id,
+ questId: hunt.quests[2].id,
+ text: 'Neustadtswall AB-Gebäude Etage 10',
+ score: 17,
+ reviewerId: huntMember.id,
+ review: 'Höchstes Team, aber leider gabs andere im AB!'
+ }
+ ]
+ });
+
+ await Promise.all(
+ answers.map((a, i) =>
+ tx.file.create({
+ data: {
+ url: images[i],
+ creatorId: a.memberId,
+ uuid: randomUUID(),
+ answers: { connect: { id: a.id } },
+ answerId: a.id
}
- ]
- },
- creatorId: admin.id,
- members: {
- create: [
- {
- memberId: huntMember.id
- }
- ]
- },
- teams: {
- create: [
- {
- ownerId: user.id,
- name: 'Team 1',
- description: 'Das beste Informatik Team!',
- password: 'top secret',
- members: {
- create: [
- {
- memberId: teamMember.id
- }
- ]
- }
- }
- ]
- }
- }
+ })
+ )
+ );
+
+ console.log(JSON.stringify(hunt, null, 2));
});
-
- const team = hunt.teams[0];
-
- const answers = await prisma.questAnswer.createManyAndReturn({
- data: [
- {
- teamId: team.id,
- memberId: user.id,
- questId: hunt.quests[0].id,
- text: 'Am Herdentor',
- picture: '/img/dummy/moin1.jpeg',
- location: 'An der Haltestelle Herdentor',
- lat: 53.079138,
- lon: 8.809734,
- reviewerId: admin.id,
- score: 10,
- review: 'Schönes Bild!'
- },
- {
- teamId: team.id,
- memberId: teamMember.id,
- questId: hunt.quests[1].id,
- text: '14',
- picture: '/img/dummy/musikanten1.jpg',
- location: 'Marktplatz',
- score: 10
- },
- {
- teamId: team.id,
- memberId: teamMember.id,
- questId: hunt.quests[2].id,
- picture: '/img/dummy/aussicht1.jpeg',
- text: 'Neustadtswall AB-Gebäude Etage 10',
- score: 17,
- reviewerId: huntMember.id,
- review: 'Höchstes Team, aber leider gabs andere im AB!'
- }
- ]
- });
-
- console.log(JSON.stringify(hunt, null, 2));
}
main()
diff --git a/server/api/home.get.ts b/server/api/home.get.ts
index 1bcc22b..f3c2093 100644
--- a/server/api/home.get.ts
+++ b/server/api/home.get.ts
@@ -9,6 +9,11 @@ async function getOwnHunts(userId: number | undefined) {
id: true,
name: true,
description: true,
+ picture: {
+ select: {
+ url: true
+ }
+ },
createdAt: true,
start: true,
end: true,
@@ -57,6 +62,11 @@ export default defineEventHandler(async (event) => {
id: true,
name: true,
description: true,
+ picture: {
+ select: {
+ url: true
+ }
+ },
createdAt: true,
start: true,
end: true,
diff --git a/server/api/hunt/[huntId]/index.get.ts b/server/api/hunt/[huntId]/index.get.ts
index e529418..7f18560 100644
--- a/server/api/hunt/[huntId]/index.get.ts
+++ b/server/api/hunt/[huntId]/index.get.ts
@@ -20,13 +20,21 @@ export default defineEventHandler(async (event) => {
select: {
id: true,
name: true,
+ revealAnswers: true,
+ revealQuests: true,
+ picture: {
+ select: {
+ url: true
+ }
+ },
description: true,
createdAt: true,
start: true,
end: true,
_count: {
select: {
- teams: true
+ teams: true,
+ quests: true
}
},
creator: {
@@ -73,6 +81,11 @@ export default defineEventHandler(async (event) => {
select: {
id: true,
title: true,
+ picture: {
+ select: {
+ url: true
+ }
+ },
description: true,
textRequired: true,
pictureRequired: true,
@@ -90,7 +103,11 @@ export default defineEventHandler(async (event) => {
select: {
id: true,
text: true,
- picture: true,
+ picture: {
+ select: {
+ url: true
+ }
+ },
score: true,
review: true,
member: {
@@ -159,7 +176,7 @@ export default defineEventHandler(async (event) => {
ownTeam,
loggedIn: !!user.user,
totalScore: ownTeam !== null ? totalScore : undefined,
- quests: mappedQuests,
+ quests: hunt.revealQuests ? mappedQuests : null,
...hunt
};
});
diff --git a/server/api/quest/[questId]/index.get.ts b/server/api/quest/[questId]/index.get.ts
index 76bc164..aa7e3ec 100644
--- a/server/api/quest/[questId]/index.get.ts
+++ b/server/api/quest/[questId]/index.get.ts
@@ -20,6 +20,11 @@ export default defineEventHandler(async (event) => {
id: true,
title: true,
description: true,
+ picture: {
+ select: {
+ url: true
+ }
+ },
question: true,
optional: true,
pictureRequired: true,
@@ -32,14 +37,19 @@ export default defineEventHandler(async (event) => {
hunt: {
select: {
id: true,
- name: true
+ name: true,
+ revealQuests: true
}
},
answers: {
select: {
id: true,
text: true,
- picture: true,
+ picture: {
+ select: {
+ url: true
+ }
+ },
score: true,
review: true,
member: {
@@ -79,6 +89,10 @@ export default defineEventHandler(async (event) => {
throw createError({ status: 404, statusText: 'Not found!' });
}
+ if (!_quest.hunt.revealQuests) {
+ throw createError({ status: 404, statusText: 'Not found!' });
+ }
+
const { answers, ...quest } = _quest;
const answer = answers.length > 0 ? answers[0] : null;
diff --git a/server/api/quest/[questId]/submit.post.ts b/server/api/quest/[questId]/submit.post.ts
index fb299ab..a6a0cb0 100644
--- a/server/api/quest/[questId]/submit.post.ts
+++ b/server/api/quest/[questId]/submit.post.ts
@@ -115,9 +115,9 @@ export default defineEventHandler(async (event) => {
// TODO: prevent updating reviewed answers
// TODO: respect end of hunt
- // TODO: keep old images
- let picture: string | undefined = undefined;
+ let picture: { creatorId: number; url: string; uuid: string } | undefined =
+ undefined;
if (data.image) {
const uuid = randomUUID();
@@ -131,30 +131,75 @@ export default defineEventHandler(async (event) => {
'Content-Type': data.image.type
}
);
- picture = `https://${s3Bucket}.${s3Host}/${uuid}`;
+ picture = {
+ creatorId: userId,
+ url: `https://${s3Bucket}.${s3Host}/${uuid}`,
+ uuid
+ };
}
- if (prevAnswer.id) {
- await prisma.questAnswer.update({
- where: {
- id: prevAnswer.id
- },
- data: {
- memberId: userId,
- text: data.answer,
- picture
+ if (picture) {
+ await prisma.$transaction(async (tx) => {
+ if (prevAnswer.id) {
+ const file = await tx.file.create({
+ data: {
+ ...picture,
+ answerId: prevAnswer.id
+ },
+ select: { id: true }
+ });
+ await tx.questAnswer.update({
+ where: {
+ id: prevAnswer.id
+ },
+ data: {
+ memberId: userId,
+ text: data.answer,
+ pictureId: file.id
+ }
+ });
+ } else {
+ const answer = await tx.questAnswer.create({
+ data: {
+ teamId: team.id,
+ memberId: userId,
+ questId: quest.id,
+ text: data.answer
+ },
+ select: { id: true }
+ });
+ await tx.file.create({
+ data: {
+ ...picture,
+ answerId: answer.id,
+ answers: {
+ connect: answer
+ }
+ }
+ });
}
});
} else {
- await prisma.questAnswer.create({
- data: {
- teamId: team.id,
- memberId: userId,
- questId: quest.id,
- text: data.answer,
- picture
- }
- });
+ if (prevAnswer.id) {
+ await prisma.questAnswer.update({
+ where: {
+ id: prevAnswer.id
+ },
+ data: {
+ memberId: userId,
+ text: data.answer
+ }
+ });
+ } else {
+ await prisma.questAnswer.create({
+ data: {
+ teamId: team.id,
+ memberId: userId,
+ questId: quest.id,
+ text: data.answer
+ }
+ });
+ }
}
return { ok: true };
diff --git a/shared/utils/error.ts b/shared/utils/error.ts
index 6e69697..30ac522 100644
--- a/shared/utils/error.ts
+++ b/shared/utils/error.ts
@@ -1,5 +1,5 @@
export function parseError(e: unknown): string {
- if (e instanceof Error && e.name === 'FetchError' && 'statusCode' in e) {
+ if (e instanceof Error && 'statusCode' in e) {
switch (e.statusCode) {
case 401:
return 'error.401';