220 lines
4.9 KiB
TypeScript
220 lines
4.9 KiB
TypeScript
import { useValidatedParams, zh } from 'h3-zod';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { z } from 'zod';
|
|
import prisma from '~~/lib/prisma';
|
|
import { minioClient, s3Bucket, s3Host } from '~~/lib/s3';
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const { questId } = await useValidatedParams(
|
|
event,
|
|
z.object({
|
|
questId: zh.numAsString
|
|
})
|
|
);
|
|
|
|
const user = await requireUserSession(event);
|
|
const userId = user.user.id;
|
|
|
|
const quest = await prisma.huntQuest.findUnique({
|
|
where: {
|
|
id: questId
|
|
},
|
|
select: {
|
|
id: true,
|
|
hunt: {
|
|
select: {
|
|
teams: {
|
|
where: {
|
|
OR: [
|
|
{
|
|
ownerId: userId
|
|
},
|
|
{
|
|
members: {
|
|
some: {
|
|
memberId: userId
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
answers: {
|
|
select: {
|
|
id: true,
|
|
text: true,
|
|
picture: true,
|
|
updatedAt: true,
|
|
final: true
|
|
},
|
|
where: {
|
|
team: {
|
|
OR: [
|
|
{
|
|
ownerId: userId
|
|
},
|
|
{
|
|
members: {
|
|
some: {
|
|
memberId: userId
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!quest) {
|
|
throw createError({ status: 404, statusText: 'Not found!' });
|
|
}
|
|
|
|
if (quest.hunt.teams.length <= 0) {
|
|
throw createError({ status: 403, statusText: 'No team yet!' });
|
|
}
|
|
if (quest.answers.length > 0 && quest.answers![0]?.final) {
|
|
throw createError({ status: 418, statusText: 'Already marked as final!' });
|
|
}
|
|
|
|
const team = quest.hunt.teams![0]!;
|
|
|
|
const body = await readFormData(event);
|
|
const formDataObj = Object.fromEntries(body.entries());
|
|
|
|
const data = z
|
|
.object({
|
|
updatedAt: z.iso.datetime().optional(),
|
|
answer: z.string().optional(),
|
|
lang: z.enum(['de', 'en']).default('de'),
|
|
image: z
|
|
.file()
|
|
.mime(['image/png', 'image/jpeg', 'image/jpg'])
|
|
.max(10_000_000)
|
|
.optional(),
|
|
final: zh.boolAsString.optional()
|
|
})
|
|
.parse(formDataObj);
|
|
|
|
const prevAnswer =
|
|
quest.answers.length > 0
|
|
? quest.answers![0]!
|
|
: {
|
|
updatedAt: null,
|
|
id: null
|
|
};
|
|
|
|
if (
|
|
prevAnswer.updatedAt &&
|
|
(!data.updatedAt ||
|
|
new Date(data.updatedAt).getTime() <
|
|
new Date(prevAnswer.updatedAt).getTime())
|
|
) {
|
|
throw createError({
|
|
status: 409,
|
|
statusText: 'Updating on old data, please refresh!'
|
|
});
|
|
}
|
|
|
|
// TODO: respect end of hunt
|
|
|
|
let picture: { creatorId: number; url: string; uuid: string } | undefined =
|
|
undefined;
|
|
|
|
if (data.image) {
|
|
const uuid = randomUUID();
|
|
await minioClient.putObject(
|
|
s3Bucket,
|
|
uuid,
|
|
Buffer.from(await data.image.arrayBuffer()),
|
|
undefined,
|
|
{
|
|
'Cache-Control': 'public, max-age=31536000, immutable',
|
|
'Content-Type': data.image.type
|
|
}
|
|
);
|
|
picture = {
|
|
creatorId: userId,
|
|
url: `https://${s3Bucket}.${s3Host}/${uuid}`,
|
|
uuid
|
|
};
|
|
}
|
|
|
|
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,
|
|
final: data.final,
|
|
lang: data.lang
|
|
}
|
|
});
|
|
} else {
|
|
const answer = await tx.questAnswer.create({
|
|
data: {
|
|
teamId: team.id,
|
|
memberId: userId,
|
|
questId: quest.id,
|
|
text: data.answer,
|
|
final: data.final,
|
|
lang: data.lang
|
|
},
|
|
select: { id: true }
|
|
});
|
|
await tx.file.create({
|
|
data: {
|
|
...picture,
|
|
answerId: answer.id,
|
|
answers: {
|
|
connect: answer
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
if (prevAnswer.id) {
|
|
await prisma.questAnswer.update({
|
|
where: {
|
|
id: prevAnswer.id
|
|
},
|
|
data: {
|
|
memberId: userId,
|
|
text: data.answer,
|
|
final: data.final,
|
|
lang: data.lang
|
|
}
|
|
});
|
|
} else {
|
|
await prisma.questAnswer.create({
|
|
data: {
|
|
teamId: team.id,
|
|
memberId: userId,
|
|
questId: quest.id,
|
|
text: data.answer,
|
|
final: data.final,
|
|
lang: data.lang
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return { ok: true };
|
|
});
|