diff --git a/TODO.md b/TODO.md index 98fd626..680c489 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ - **Auth** - [x] Flawless! Login (Email+Pw ohne bestätigung?) - [x] Default random name - - [ ] Change display name + - [x] Change display name - **Hunt Admin** - [x] Start/Stop - [x] Add Reviews/Score diff --git a/app/pages/profile.vue b/app/pages/profile.vue index aaea34e..1d20bc7 100644 --- a/app/pages/profile.vue +++ b/app/pages/profile.vue @@ -2,10 +2,270 @@ definePageMeta({ middleware: ['auth'] }); + +const router = useRouter(); +const { t } = useI18n(); +const { loggedIn, fetch } = useUserSession(); +const localePath = useLocalePath(); +const validation = useValidation(); +watch(loggedIn, (isLoggedIn) => { + if (!isLoggedIn) { + router.push(localePath('/login')); + } +}); +const { data, refresh, pending } = await useFetch('/api/auth/profile'); + +const form = reactive({ + email: data.value!.email, + name: data.value!.name +}); +const uploading = ref(false); +const changed = computed( + () => form.name !== data.value?.name || form.email !== data.value?.email +); + +const profileError = ref(''); +async function submitProfile() { + uploading.value = true; + profileError.value = ''; + try { + await $fetch(`/api/auth/profile`, { + method: 'POST', + body: form + }); + + await refresh(); + form.email = data.value!.email; + form.name = data.value!.name; + await fetch(); + } catch (e) { + if (e instanceof Error && 'statusCode' in e && e.statusCode === 409) { + // ToDo: i18n + profileError.value = 'Email already exists!'; + } else { + profileError.value = parseError(e); + } + } + + uploading.value = false; +} + +const password = reactive({ + password: '', + old: '' +}); + +const passwordError = ref(''); +async function submitPassword() { + uploading.value = true; + passwordError.value = ''; + try { + await $fetch(`/api/auth/password`, { + method: 'POST', + body: password + }); + + password.password = ''; + password.old = ''; + } catch (e) { + if (e instanceof Error && 'statusCode' in e && e.statusCode === 404) { + // ToDo: i18n + passwordError.value = 'Old password incorrect!'; + } else { + passwordError.value = parseError(e); + } + } + + uploading.value = false; +} - profile + + + Public profile + + + + + {{ t(profileError) }} + Update + + + + + Change password + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t(profileError) }} + Update + + + + + Uploaded pictures + + + + + {{ item.answer.quest.title }} — + {{ item.answer.quest.hunt.name }} + + + + + diff --git a/server/api/auth/password.post.ts b/server/api/auth/password.post.ts new file mode 100644 index 0000000..30f21e6 --- /dev/null +++ b/server/api/auth/password.post.ts @@ -0,0 +1,44 @@ +import { hash, verify } from 'argon2'; +import { useValidatedBody, z } from 'h3-zod'; +import prisma from '~~/lib/prisma'; + +export default defineEventHandler(async (event) => { + const { user } = await requireUserSession(event); + + const { old, password } = await useValidatedBody( + event, + z.object({ + password: z.string().min(8), + old: z.string().min(8) + }) + ); + + const dbUser = await prisma.user.findUniqueOrThrow({ + where: { + deletedAt: null, + id: user.id + }, + select: { + id: true, + password: true + } + }); + if (!(await verify(dbUser.password, old))) { + throw createError({ status: 404, statusText: 'Not found!' }); + } + + const passwordHash = await hash(password); + + await prisma.user.update({ + where: { + id: user.id + }, + data: { + password: passwordHash + } + }); + + console.warn(`Successful changed password for user ${user.id}`); + + return { ok: true }; +}); diff --git a/server/api/auth/profile.get.ts b/server/api/auth/profile.get.ts new file mode 100644 index 0000000..e640a76 --- /dev/null +++ b/server/api/auth/profile.get.ts @@ -0,0 +1,42 @@ +import prisma from '~~/lib/prisma'; + +export default defineEventHandler(async (event) => { + const user = await requireUserSession(event); + + return prisma.user.findUnique({ + where: { id: user.user.id }, + select: { + name: true, + email: true, + uploadedFiles: { + select: { + url: true, + createdAt: true, + answer: { + select: { + id: true, + quest: { + select: { + id: true, + title: true, + hunt: { + select: { + id: true, + name: true + } + } + } + } + } + } + }, + + where: { + NOT: { + answerId: null + } + } + } + } + }); +}); diff --git a/server/api/auth/profile.post.ts b/server/api/auth/profile.post.ts new file mode 100644 index 0000000..fcaf7a2 --- /dev/null +++ b/server/api/auth/profile.post.ts @@ -0,0 +1,38 @@ +import { useValidatedBody, z } from 'h3-zod'; +import prisma from '~~/lib/prisma'; + +export default defineEventHandler(async (event) => { + const user = await requireUserSession(event); + + const data = await useValidatedBody( + event, + z.object({ + name: z.string().min(3).max(256).trim(), + email: z.email().max(256).trim() + }) + ); + + try { + const dbUser = await prisma.user.update({ + where: { + id: user.user.id + }, + data, + select: { + id: true, + name: true, + role: true + } + }); + await setUserSession(event, { + user: dbUser, + loggedInAt: user.loggedInAt + }); + } catch (error) { + throw createError({ + status: 409, + statusText: 'User with that email already exists!' + }); + } + return { ok: true }; +});