Add pic upload for hunt admins
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { createImageURL } from '#shared/utils/image';
|
||||||
import ClickableImage from '~/components/ClickableImage.vue';
|
import ClickableImage from '~/components/ClickableImage.vue';
|
||||||
import MultilineString from '~/components/MultilineString.vue';
|
import MultilineString from '~/components/MultilineString.vue';
|
||||||
import { useI18nKey } from '~/composables/useI18nKey';
|
import { useI18nKey } from '~/composables/useI18nKey';
|
||||||
@@ -102,8 +103,6 @@ async function submit() {
|
|||||||
|
|
||||||
uploading.value = false;
|
uploading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createImageURL = (file: File) => URL.createObjectURL(file);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { createImageURL } from '#shared/utils/image';
|
||||||
import superjson, { type SuperJSONResult } from 'superjson';
|
import superjson, { type SuperJSONResult } from 'superjson';
|
||||||
import { useI18nKey } from '~/composables/useI18nKey';
|
import { useI18nKey } from '~/composables/useI18nKey';
|
||||||
import { useTitleStore } from '~/stores/title';
|
import { useTitleStore } from '~/stores/title';
|
||||||
@@ -153,6 +154,52 @@ async function addUser() {
|
|||||||
newUser.value = undefined;
|
newUser.value = undefined;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newImage = ref<File>();
|
||||||
|
const uploading = ref(false);
|
||||||
|
const imageError = ref('');
|
||||||
|
|
||||||
|
async function updateImage() {
|
||||||
|
if (!newImage.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uploading.value = true;
|
||||||
|
imageError.value = '';
|
||||||
|
|
||||||
|
const body = new FormData();
|
||||||
|
body.set('image', newImage.value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $fetch(`/api/admin/hunt/${id}/image`, {
|
||||||
|
method: 'POST',
|
||||||
|
body
|
||||||
|
});
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
newImage.value = undefined;
|
||||||
|
} catch (e) {
|
||||||
|
imageError.value = parseError(e);
|
||||||
|
}
|
||||||
|
uploading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteImage() {
|
||||||
|
uploading.value = true;
|
||||||
|
imageError.value = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $fetch(`/api/admin/hunt/${id}/image`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
newImage.value = undefined;
|
||||||
|
} catch (e) {
|
||||||
|
imageError.value = parseError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -267,6 +314,50 @@ async function addUser() {
|
|||||||
>
|
>
|
||||||
</VaButtonGroup>
|
</VaButtonGroup>
|
||||||
</VaForm>
|
</VaForm>
|
||||||
|
<VaDivider />
|
||||||
|
<h2 class="text-2xl">Image</h2>
|
||||||
|
<div>
|
||||||
|
<VaImage
|
||||||
|
v-if="newImage"
|
||||||
|
:src="createImageURL(newImage)"
|
||||||
|
class="max-h-60 w-full"
|
||||||
|
fit="contain"
|
||||||
|
lazy
|
||||||
|
/>
|
||||||
|
<VaImage
|
||||||
|
v-else-if="data?.picture"
|
||||||
|
:src="data?.picture?.url"
|
||||||
|
class="max-h-60 w-full"
|
||||||
|
fit="contain"
|
||||||
|
lazy
|
||||||
|
>
|
||||||
|
<template #loader> <VaProgressCircle indeterminate /> </template
|
||||||
|
></VaImage>
|
||||||
|
<VaFileUpload
|
||||||
|
v-model="newImage"
|
||||||
|
:disabled="uploading || pending"
|
||||||
|
type="single"
|
||||||
|
file-types="jpg,png,jpeg"
|
||||||
|
/>
|
||||||
|
<VaAlert
|
||||||
|
v-if="imageError.length > 0"
|
||||||
|
color="danger"
|
||||||
|
class="w-full text-center"
|
||||||
|
>{{ t(imageError) }}</VaAlert
|
||||||
|
>
|
||||||
|
<VaButtonGroup :disabled="uploading || pending">
|
||||||
|
<VaButton color="success" :disabled="!newImage" @click="updateImage">
|
||||||
|
Upload new image
|
||||||
|
</VaButton>
|
||||||
|
<VaButton
|
||||||
|
color="danger"
|
||||||
|
icon="delete"
|
||||||
|
:disabled="!data?.picture"
|
||||||
|
@click="deleteImage"
|
||||||
|
>Delete image</VaButton
|
||||||
|
>
|
||||||
|
</VaButtonGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
<VaDivider />
|
<VaDivider />
|
||||||
<h2 class="text-2xl">Hunt admin members:</h2>
|
<h2 class="text-2xl">Hunt admin members:</h2>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { useValidatedParams, z, zh } from 'h3-zod';
|
||||||
|
import prisma from '~~/lib/prisma';
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const user = await requireUserSession(event);
|
||||||
|
|
||||||
|
if (user.user.role !== 'ADMIN') {
|
||||||
|
throw createError({
|
||||||
|
status: 403,
|
||||||
|
statusText: 'Not allowed!'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const userId = user.user.id;
|
||||||
|
const { huntId } = await useValidatedParams(
|
||||||
|
event,
|
||||||
|
z.object({
|
||||||
|
huntId: zh.numAsString
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const hunt = await prisma.hunt.findUnique({
|
||||||
|
where: {
|
||||||
|
id: huntId,
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
creatorId: userId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
memberId: userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!hunt) {
|
||||||
|
throw createError({ status: 404, statusText: 'Not found!' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.hunt.update({
|
||||||
|
where: {
|
||||||
|
id: huntId
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
pictureId: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {success: true};
|
||||||
|
});
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { useValidatedParams, z, zh } from 'h3-zod';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import prisma from '~~/lib/prisma';
|
||||||
|
import { minioClient, s3Bucket, s3Host } from '~~/lib/s3';
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const user = await requireUserSession(event);
|
||||||
|
|
||||||
|
if (user.user.role !== 'ADMIN') {
|
||||||
|
throw createError({
|
||||||
|
status: 403,
|
||||||
|
statusText: 'Not allowed!'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const userId = user.user.id;
|
||||||
|
const { huntId } = await useValidatedParams(
|
||||||
|
event,
|
||||||
|
z.object({
|
||||||
|
huntId: zh.numAsString
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const hunt = await prisma.hunt.findUnique({
|
||||||
|
where: {
|
||||||
|
id: huntId,
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
creatorId: userId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
memberId: userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!hunt) {
|
||||||
|
throw createError({ status: 404, statusText: 'Not found!' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await readFormData(event);
|
||||||
|
const formDataObj = Object.fromEntries(body.entries());
|
||||||
|
|
||||||
|
const { image } = z
|
||||||
|
.object({
|
||||||
|
image: z
|
||||||
|
.file()
|
||||||
|
.mime(['image/png', 'image/jpeg', 'image/jpg'])
|
||||||
|
.max(10_000_000)
|
||||||
|
})
|
||||||
|
.parse(formDataObj);
|
||||||
|
|
||||||
|
const uuid = randomUUID();
|
||||||
|
await minioClient.putObject(
|
||||||
|
s3Bucket,
|
||||||
|
uuid,
|
||||||
|
Buffer.from(await image.arrayBuffer()),
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||||
|
'Content-Type': image.type
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await prisma.hunt.update({
|
||||||
|
where: {
|
||||||
|
id: huntId
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
picture: {
|
||||||
|
create: {
|
||||||
|
creatorId: userId,
|
||||||
|
url: `https://${s3Bucket}.${s3Host}/${uuid}`,
|
||||||
|
uuid,
|
||||||
|
huntId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
});
|
||||||
@@ -48,6 +48,12 @@ export default defineEventHandler(async (event) => {
|
|||||||
revealQuests: true,
|
revealQuests: true,
|
||||||
revealAnswers: true,
|
revealAnswers: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
|
picture: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
url: true
|
||||||
|
}
|
||||||
|
},
|
||||||
creator: {
|
creator: {
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const createImageURL = (file: File) => URL.createObjectURL(file);
|
||||||
Reference in New Issue
Block a user