Files
pichunt/server/api/quest/[questId]/submit.post.ts
T

215 lines
4.7 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(),
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
}
});
} else {
const answer = await tx.questAnswer.create({
data: {
teamId: team.id,
memberId: userId,
questId: quest.id,
text: data.answer,
final: data.final
},
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
}
});
} else {
await prisma.questAnswer.create({
data: {
teamId: team.id,
memberId: userId,
questId: quest.id,
text: data.answer,
final: data.final
}
});
}
}
return { ok: true };
});