From 6842251dd93fdbd27a7a1264f6f750df7e9ec64f Mon Sep 17 00:00:00 2001
From: danielswiatek
Date: Mon, 10 Feb 2025 22:21:47 +0100
Subject: [PATCH 1/9] fix: remove console.log statements from various
components
---
apps/api/src/services/person/personGet.ts | 2 --
apps/frontend/src/components/LayoutComponents/PublicFooter.vue | 1 -
.../FormUnterveranstaltungLandingSettings.vue | 1 -
apps/frontend/src/views/Anmeldung/FotoUpload.vue | 1 -
4 files changed, 5 deletions(-)
diff --git a/apps/api/src/services/person/personGet.ts b/apps/api/src/services/person/personGet.ts
index 11a645d0..a4f77bb0 100644
--- a/apps/api/src/services/person/personGet.ts
+++ b/apps/api/src/services/person/personGet.ts
@@ -17,8 +17,6 @@ export const personGetProcedure = defineProtectedQueryProcedure({
id: input.id,
}
- console.log(where)
-
return prisma.person.findUniqueOrThrow({
where,
select: {
diff --git a/apps/frontend/src/components/LayoutComponents/PublicFooter.vue b/apps/frontend/src/components/LayoutComponents/PublicFooter.vue
index 838e8505..430ea03b 100644
--- a/apps/frontend/src/components/LayoutComponents/PublicFooter.vue
+++ b/apps/frontend/src/components/LayoutComponents/PublicFooter.vue
@@ -5,7 +5,6 @@ const commitHash = import.meta.env.VITE_APP_COMMIT_HASH || '000000'
import { injectUnterveranstaltung } from '@/layouts/AnmeldungLayout.vue'
import { formatDate } from '@vueuse/core'
const unterveranstaltung = injectUnterveranstaltung()
-console.log(unterveranstaltung)
diff --git a/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungLandingSettings.vue b/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungLandingSettings.vue
index 0ba156ea..1f191f3f 100644
--- a/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungLandingSettings.vue
+++ b/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungLandingSettings.vue
@@ -152,7 +152,6 @@ async function onFileChanged($event: Event) {
async function deleteHeroImages(image, index) {
heroImages.value.splice(index, 1)
if (!image.added) {
- console.log('deleteHeroImages', image)
deletedHeroImagesIds.value.push(image.id)
}
}
diff --git a/apps/frontend/src/views/Anmeldung/FotoUpload.vue b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
index 83cddbca..20a54d18 100644
--- a/apps/frontend/src/views/Anmeldung/FotoUpload.vue
+++ b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
@@ -43,7 +43,6 @@ async function upload(toUploadFile: File) {
anmeldungId: parseInt(anmeldungId),
accessToken,
})
- console.log(res)
await apiClient.anmeldung.anmeldungFotoUpload.mutate({
unterveranstaltungId: unterveranstaltung.value.id,
anmeldungId: parseInt(anmeldungId),
From 74ece0d7f17256ece6279e5b42861ee30e053cd4 Mon Sep 17 00:00:00 2001
From: danielswiatek
Date: Tue, 4 Mar 2025 21:44:48 +0100
Subject: [PATCH 2/9] refactor: wrap GenericDataGrid in a div for improved
layout
---
.../views/Verwaltung/Accounts/AccountList.vue | 26 ++++++++++---------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/apps/frontend/src/views/Verwaltung/Accounts/AccountList.vue b/apps/frontend/src/views/Verwaltung/Accounts/AccountList.vue
index ab0fc6b6..0f4e3fdf 100644
--- a/apps/frontend/src/views/Verwaltung/Accounts/AccountList.vue
+++ b/apps/frontend/src/views/Verwaltung/Accounts/AccountList.vue
@@ -171,18 +171,20 @@ function changeTab(index: number) {
Account anlegen
- router.push({ name: 'Verwaltung Accountdetails', params: { accountId: account.id } })
- "
- />
+
+ router.push({ name: 'Verwaltung Accountdetails', params: { accountId: account.id } })
+ "
+ />
+
From 915728442754c2ea6882721c25aecafc7cc76a6a Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sat, 8 Mar 2025 17:16:51 +0100
Subject: [PATCH 3/9] feat: update file upload
---
apps/frontend/package.json | 1 +
.../BasicInputs/InputFileUploadArea.vue | 95 ++++++++++++++++---
.../src/views/Anmeldung/FotoUpload.vue | 27 ++++--
pnpm-lock.yaml | 17 ++--
4 files changed, 107 insertions(+), 33 deletions(-)
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 4ec38e0b..9fda9fa3 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -37,6 +37,7 @@
"@vueuse/router": "^12.3.0",
"clsx": "^2.1.1",
"http2-proxy": "^5.0.53",
+ "human-filetypes": "^1.1.3",
"intl-tel-input": "^24.4.0",
"primevue": "^4.2.5",
"radix-vue": "^1.9.5",
diff --git a/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue b/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
index 2d2f78e0..a761f499 100644
--- a/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
+++ b/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
@@ -2,6 +2,9 @@
import { computed, onMounted, ref, shallowRef } from 'vue'
import { formatBytes } from '@codeanker/helpers'
+import { TrashIcon } from '@heroicons/vue/24/solid'
+import { fromMime, type FileKind } from 'human-filetypes'
+import Button from '../UIComponents/Button.vue'
const props = defineProps<{
multiple?: Multiple
@@ -61,21 +64,28 @@ function dropZoneDrop(event) {
event.preventDefault()
event.stopPropagation()
dragCounter.value = 0
- return upload(event.dataTransfer.files)
+
+ files.value.push(...Array.from(event.dataTransfer.files))
}
+const files = ref([])
+
function uploadFileInput(event) {
- return upload(event.target.files)
+ files.value.push(...Array.from(event.target.files))
+}
+
+function fileUrl(file: File) {
+ return URL.createObjectURL(file)
}
-async function upload(files) {
- if (uploadPending.value || !files || !files[0]) {
+async function upload() {
+ if (uploadPending.value || !files.value || !files.value[0]) {
return
}
try {
uploadPending.value = true
error.value = null
- const result = await handleUpload(files, props.multiple, props.maxFileSize)
+ const result = await handleUpload(files.value, props.multiple, props.maxFileSize)
emit('uploaded', result)
} catch (uploadError: any) {
if (uploadError.message === 'file size too large' && props.maxFileSize) {
@@ -119,6 +129,20 @@ async function handleUpload(uploadFiles, multiple = false, maxFileSize) {
}
}
}
+
+const mimeMap: Record = {
+ image: 'Bild',
+ video: 'Video',
+ audio: 'Audio',
+ archive: 'Archiv',
+ document: 'Dokument',
+ spreadsheet: 'Tabelle',
+ presentation: 'Präsentation',
+ font: 'Schriftart',
+ text: 'Text',
+ application: 'Ausführbare Datei',
+ unknown: 'Unbekanntes Dateiformat',
+}
@@ -131,25 +155,66 @@ async function handleUpload(uploadFiles, multiple = false, maxFileSize) {
}"
@click="fileInput?.click()"
>
-
-
-
-
-
{{ uploadText ?? 'Datei hier hin ziehen oder klicken.' }}
-
-
-
+
+
+
+
{{ uploadText ?? 'Datei hier hin ziehen oder klicken.' }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ file.name }}
+
+
+ {{ mimeMap[fromMime(file.type)] }}
+
+
+
+
+
+
+
+
+
+
+
+
+ Hochladen
+
diff --git a/apps/frontend/src/views/Anmeldung/FotoUpload.vue b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
index 20a54d18..8dce1385 100644
--- a/apps/frontend/src/views/Anmeldung/FotoUpload.vue
+++ b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
@@ -80,9 +80,25 @@ async function upload(toUploadFile: File) {
Vielen Dank für die Anmeldung von {{ state.person.firstname }} {{ state.person.lastname }} zur
- Veranstaltung {{ state.unterveranstaltung.veranstaltung.name }}
+ Veranstaltung {{ state.unterveranstaltung.veranstaltung.name }} .
+
+
+ Lade hier ein Bild von {{ state.person.firstname }} {{ state.person.lastname }} hoch. Dieses wird dann
+ bspw. für Teilnehmendenausweise verwendet.
+
+
+ Wichtig: Bitte achte darauf, dass das Foto möglichst quadratisch ist (gleiche Höhe wie Breite).
+
+
+ dein Bild wird hochgeladen
+
+
-
-
- dein Bild wird hochgeladen
-
= 14'}
+ human-filetypes@1.1.3:
+ resolution: {integrity: sha512-vtGxjtt0pdKULyCD3juFr0gjxSU3tJb995/oZN84LHP45HGryg1/2xJCLvoKw6zEpWfVCZiFNaGLOF16u28Bbg==}
+
husky@8.0.3:
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
engines: {node: '>=14'}
@@ -7822,6 +7819,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ human-filetypes@1.1.3: {}
+
husky@8.0.3: {}
iconv-lite@0.4.24:
From 253b08abccad4ef3ca252e0b16a5d73a89719daa Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sat, 8 Mar 2025 17:48:39 +0100
Subject: [PATCH 4/9] fix: respect registration deadline
---
apps/api/email/registration-successful.mjml | 7 ++
.../services/anmeldung/anmeldungFotoUpload.ts | 41 ++++++++--
.../BasicInputs/InputFileUploadArea.vue | 17 ++--
.../src/views/Anmeldung/FotoUpload.vue | 81 +++++++++++--------
4 files changed, 101 insertions(+), 45 deletions(-)
diff --git a/apps/api/email/registration-successful.mjml b/apps/api/email/registration-successful.mjml
index 1e49badc..f61ffa83 100644
--- a/apps/api/email/registration-successful.mjml
+++ b/apps/api/email/registration-successful.mjml
@@ -18,6 +18,13 @@
>Hier Foto hochladen
+
+
+ Du kannst den Fotoupload beliebig oft machen, bis der Meldeschluss erreicht ist.
+
{{/if}}
{
- await prisma.anmeldung.update({
+ const unterveranstaltung = await prisma.unterveranstaltung.findFirst({
where: {
- unterveranstaltungId,
- id: anmeldungId,
- accessToken,
+ id: unterveranstaltungId,
+ meldeschluss: {
+ gt: new Date(),
+ },
},
+ })
+ if (unterveranstaltung === null) {
+ throw new TRPCError({
+ code: 'FORBIDDEN',
+ message: 'Der Meldeschluss ist bereits erreicht!',
+ })
+ }
+
+ const where: Prisma.AnmeldungWhereUniqueInput = {
+ unterveranstaltungId,
+ id: anmeldungId,
+ accessToken,
+ }
+ const anmeldung = await prisma.anmeldung.findUniqueOrThrow({
+ where,
+ select: {
+ person: {
+ select: {
+ photoId: true,
+ },
+ },
+ },
+ })
+ if (anmeldung.person.photoId !== null) {
+ // TODO: Delete old file
+ }
+
+ await prisma.anmeldung.update({
+ where,
data: {
- accessToken: null,
person: {
update: {
photoId: fileId,
diff --git a/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue b/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
index a761f499..65772b51 100644
--- a/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
+++ b/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
@@ -174,6 +174,14 @@ const mimeMap: Record = {
/>
+
+ Hochladen
+
+
= {
class="h-[384px] drop-shadow-lg"
/>
@@ -208,13 +217,5 @@ const mimeMap: Record = {
-
-
- Hochladen
-
diff --git a/apps/frontend/src/views/Anmeldung/FotoUpload.vue b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
index 8dce1385..6656f49b 100644
--- a/apps/frontend/src/views/Anmeldung/FotoUpload.vue
+++ b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
@@ -7,12 +7,15 @@ import Loading from '@/components/UIComponents/Loading.vue'
import { handlePublicPhotoUpload } from '@/helpers/handleUpload'
import { injectUnterveranstaltung } from '@/layouts/AnmeldungLayout.vue'
import { useAsyncState } from '@vueuse/core'
-import { ref, watch } from 'vue'
+import { computed, ref, watch } from 'vue'
+import { dayjs } from '@codeanker/helpers'
const route = useRoute()
const unterveranstaltung = injectUnterveranstaltung()
+const meldeschlussErreicht = computed(() => dayjs().isAfter(unterveranstaltung.value.meldeschluss))
+
const anmeldungId = route.params.anmeldungId as string
const accessToken = route.query.accessToken as string
@@ -35,6 +38,7 @@ watch(unterveranstaltung, (unterveranstaltung) => {
const uploadPending = ref(false)
const uploadSuccess = ref(false)
+const uploadError = ref()
async function upload(toUploadFile: File) {
uploadPending.value = true
try {
@@ -52,6 +56,9 @@ async function upload(toUploadFile: File) {
uploadSuccess.value = true
} catch (error) {
console.error(error)
+ if (error instanceof Error) {
+ uploadError.value = error
+ }
}
uploadPending.value = false
}
@@ -83,40 +90,50 @@ async function upload(toUploadFile: File) {
Veranstaltung {{ state.unterveranstaltung.veranstaltung.name }} .
-
- Lade hier ein Bild von {{ state.person.firstname }} {{ state.person.lastname }} hoch. Dieses wird dann
- bspw. für Teilnehmendenausweise verwendet.
-
-
- Wichtig: Bitte achte darauf, dass das Foto möglichst quadratisch ist (gleiche Höhe wie Breite).
-
-
-
- dein Bild wird hochgeladen
-
+
+ Leider ist der Meldeschluss bereits erreicht.
+
+
+
+ Lade hier ein Bild von {{ state.person.firstname }} {{ state.person.lastname }} hoch. Dieses wird dann
+ bspw. für Teilnehmendenausweise verwendet.
+
+
+ Wichtig: Bitte achte darauf, dass das Foto möglichst quadratisch ist (gleiche Höhe wie
+ Breite).
+
-
-
-
-
-
-
-
-
+
+ dein Bild wird hochgeladen
+
+
+
{{ uploadError.message }}
+
+
+
+
+
+
+
+
+
+
+
From 036e288a9add28702bca1e2c7ae73236061904bf Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sat, 8 Mar 2025 18:10:58 +0100
Subject: [PATCH 5/9] fix: invert condition
---
apps/frontend/src/views/Anmeldung/FotoUpload.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/frontend/src/views/Anmeldung/FotoUpload.vue b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
index 6656f49b..c9f7d567 100644
--- a/apps/frontend/src/views/Anmeldung/FotoUpload.vue
+++ b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
@@ -91,7 +91,7 @@ async function upload(toUploadFile: File) {
>.
-
+
Leider ist der Meldeschluss bereits erreicht.
From 97e333baa43c702180d2ae8fc3d78f533f10f27f Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sat, 8 Mar 2025 18:23:44 +0100
Subject: [PATCH 6/9] fix: allow Gliederungadmins to edit photos
---
apps/api/src/services/person/person.router.ts | 6 +-
.../services/person/personGliederungPatch.ts | 33 ----------
apps/api/src/services/person/personPatch.ts | 61 +++++++++++++++++++
.../services/person/personVerwaltungPatch.ts | 33 ----------
.../UIComponents/AvatarEditModal.vue | 4 +-
.../src/components/data/AnmeldungenTable.vue | 3 +-
.../forms/person/PersonPhotoUpload.vue | 4 +-
.../views/Verwaltung/Persons/PersonDetail.vue | 2 +-
8 files changed, 69 insertions(+), 77 deletions(-)
delete mode 100644 apps/api/src/services/person/personGliederungPatch.ts
create mode 100644 apps/api/src/services/person/personPatch.ts
delete mode 100644 apps/api/src/services/person/personVerwaltungPatch.ts
diff --git a/apps/api/src/services/person/person.router.ts b/apps/api/src/services/person/person.router.ts
index 31f0aace..6ae0554a 100644
--- a/apps/api/src/services/person/person.router.ts
+++ b/apps/api/src/services/person/person.router.ts
@@ -2,10 +2,9 @@
import { mergeRouters } from '../../trpc.js'
import { personAuthenticatedGetProcedure } from './personAuthenticatedGet.js'
import { personGetProcedure } from './personGet.js'
-import { personGliederungPatchProcedure } from './personGliederungPatch.js'
import { personCountProcedure, personListProcedure } from './personList.js'
import { personVerwaltungCreateProcedure } from './personVerwaltungCreate.js'
-import { personVerwaltungPatchProcedure } from './personVerwaltungPatch.js'
+import { personVerwaltungPatchProcedure } from './personPatch.js'
import { personVerwaltungRemoveProcedure } from './personVerwaltungRemove.js'
// Import Routes here - do not delete this line
@@ -16,7 +15,6 @@ export const personRouter = mergeRouters(
personCountProcedure.router,
personGetProcedure.router,
personVerwaltungPatchProcedure.router,
- personVerwaltungRemoveProcedure.router,
- personGliederungPatchProcedure.router
+ personVerwaltungRemoveProcedure.router
// Add Routes here - do not delete this line
)
diff --git a/apps/api/src/services/person/personGliederungPatch.ts b/apps/api/src/services/person/personGliederungPatch.ts
deleted file mode 100644
index 11f669de..00000000
--- a/apps/api/src/services/person/personGliederungPatch.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Role } from '@prisma/client'
-import z from 'zod'
-
-import prisma from '../../prisma.js'
-import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js'
-
-import { getPersonCreateData, personSchema } from './schema/person.schema.js'
-
-export const personGliederungPatchProcedure = defineProtectedMutateProcedure({
- key: 'gliederungPatch',
- roleIds: [Role.GLIEDERUNG_ADMIN],
- inputSchema: z.strictObject({
- id: z.number().int(),
- data: personSchema,
- }),
- async handler({ input }) {
- await prisma.notfallkontakt.deleteMany({
- where: {
- personId: input.id,
- },
- })
-
- return prisma.person.update({
- where: {
- id: input.id,
- },
- data: await getPersonCreateData(input.data),
- select: {
- id: true,
- },
- })
- },
-})
diff --git a/apps/api/src/services/person/personPatch.ts b/apps/api/src/services/person/personPatch.ts
new file mode 100644
index 00000000..6e690ce6
--- /dev/null
+++ b/apps/api/src/services/person/personPatch.ts
@@ -0,0 +1,61 @@
+import { Role } from '@prisma/client'
+import z from 'zod'
+
+import prisma from '../../prisma.js'
+import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js'
+
+import { getPersonCreateData, personSchemaOptional } from './schema/person.schema.js'
+import { TRPCError } from '@trpc/server'
+
+export const personVerwaltungPatchProcedure = defineProtectedMutateProcedure({
+ key: 'patch',
+ roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN],
+ inputSchema: z.strictObject({
+ id: z.number().int(),
+ data: personSchemaOptional,
+ }),
+ async handler({ ctx, input }) {
+ if (ctx.account.role === 'GLIEDERUNG_ADMIN') {
+ const { gliederungId: gliederungIdActor } = await prisma.person.findFirstOrThrow({
+ where: {
+ account: {
+ id: ctx.accountId,
+ },
+ },
+ select: {
+ gliederungId: true,
+ },
+ })
+ const { gliederungId: gliederungIdTarget } = await prisma.person.findUniqueOrThrow({
+ where: {
+ id: input.id,
+ },
+ select: {
+ gliederungId: true,
+ },
+ })
+
+ if (gliederungIdTarget !== gliederungIdActor) {
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ })
+ }
+ }
+
+ await prisma.notfallkontakt.deleteMany({
+ where: {
+ personId: input.id,
+ },
+ })
+
+ return prisma.person.update({
+ where: {
+ id: input.id,
+ },
+ data: await getPersonCreateData(input.data),
+ select: {
+ id: true,
+ },
+ })
+ },
+})
diff --git a/apps/api/src/services/person/personVerwaltungPatch.ts b/apps/api/src/services/person/personVerwaltungPatch.ts
deleted file mode 100644
index ecf3fa2a..00000000
--- a/apps/api/src/services/person/personVerwaltungPatch.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Role } from '@prisma/client'
-import z from 'zod'
-
-import prisma from '../../prisma.js'
-import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js'
-
-import { getPersonCreateData, personSchemaOptional } from './schema/person.schema.js'
-
-export const personVerwaltungPatchProcedure = defineProtectedMutateProcedure({
- key: 'verwaltungPatch',
- roleIds: [Role.ADMIN],
- inputSchema: z.strictObject({
- id: z.number().int(),
- data: personSchemaOptional,
- }),
- async handler({ input }) {
- await prisma.notfallkontakt.deleteMany({
- where: {
- personId: input.id,
- },
- })
-
- return prisma.person.update({
- where: {
- id: input.id,
- },
- data: await getPersonCreateData(input.data),
- select: {
- id: true,
- },
- })
- },
-})
diff --git a/apps/frontend/src/components/UIComponents/AvatarEditModal.vue b/apps/frontend/src/components/UIComponents/AvatarEditModal.vue
index da23843f..041876ac 100644
--- a/apps/frontend/src/components/UIComponents/AvatarEditModal.vue
+++ b/apps/frontend/src/components/UIComponents/AvatarEditModal.vue
@@ -40,7 +40,7 @@ async function upload(toUploadFile: File) {
uploadPending.value = true
try {
const res = await handleUpload(toUploadFile)
- await apiClient.person.verwaltungPatch.mutate({
+ await apiClient.person.patch.mutate({
id: props.personId,
data: {
photoId: res.id,
@@ -57,7 +57,7 @@ async function upload(toUploadFile: File) {
async function remove() {
uploadPending.value = true
try {
- await apiClient.person.verwaltungPatch.mutate({
+ await apiClient.person.patch.mutate({
id: props.personId,
data: {
photoId: null,
diff --git a/apps/frontend/src/components/data/AnmeldungenTable.vue b/apps/frontend/src/components/data/AnmeldungenTable.vue
index c916b2a2..93679c1f 100644
--- a/apps/frontend/src/components/data/AnmeldungenTable.vue
+++ b/apps/frontend/src/components/data/AnmeldungenTable.vue
@@ -141,8 +141,8 @@ const { execute: updateAnmeldung } = useAsyncState(
weitereIntoleranzen: anmeldung.essgewohnheiten.weitereIntoleranzen,
},
}
+ await apiClient.person.patch.mutate(data)
if (loggedInAccount.value?.role === 'ADMIN') {
- await apiClient.person.verwaltungPatch.mutate(data)
await apiClient.anmeldung.verwaltungPatch.mutate({
id: selectedAnmeldungId.value,
data: {
@@ -150,7 +150,6 @@ const { execute: updateAnmeldung } = useAsyncState(
},
})
} else {
- await apiClient.person.gliederungPatch.mutate(data)
await apiClient.anmeldung.gliederungPatch.mutate({
id: selectedAnmeldungId.value,
data: {
diff --git a/apps/frontend/src/components/forms/person/PersonPhotoUpload.vue b/apps/frontend/src/components/forms/person/PersonPhotoUpload.vue
index a7312edf..59f5e448 100644
--- a/apps/frontend/src/components/forms/person/PersonPhotoUpload.vue
+++ b/apps/frontend/src/components/forms/person/PersonPhotoUpload.vue
@@ -35,7 +35,7 @@ async function onFileChanged(event: Event) {
if (!file) return
const res = await handleUpload(file)
- await apiClient.person.verwaltungPatch.mutate({
+ await apiClient.person.patch.mutate({
id: props.person.id,
data: {
photoId: res.id,
@@ -46,7 +46,7 @@ async function onFileChanged(event: Event) {
}
async function handleFileDelete() {
- await apiClient.person.verwaltungPatch.mutate({
+ await apiClient.person.patch.mutate({
id: props.person.id,
data: {
photoId: null,
diff --git a/apps/frontend/src/views/Verwaltung/Persons/PersonDetail.vue b/apps/frontend/src/views/Verwaltung/Persons/PersonDetail.vue
index 2fc76b25..984620a6 100644
--- a/apps/frontend/src/views/Verwaltung/Persons/PersonDetail.vue
+++ b/apps/frontend/src/views/Verwaltung/Persons/PersonDetail.vue
@@ -41,7 +41,7 @@ const { execute: update } = useAsyncState(
})
.map((entry) => entry[0] as NahrungsmittelIntoleranz)
- await apiClient.person.verwaltungPatch.mutate({
+ await apiClient.person.patch.mutate({
id: person.value!.id,
data: {
gliederungId: anmeldung.gliederung.id,
From 42ff1e135e1c5cb8bb02735b03674c0dcaf93947 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sat, 8 Mar 2025 18:57:23 +0100
Subject: [PATCH 7/9] feat: display current photo
---
.../anmeldung/anmeldungAccessTokenValidate.ts | 8 +-
.../BasicInputs/InputFileUploadArea.vue | 14 +-
.../UIComponents/AvatarEditModal.vue | 2 +-
.../src/views/Anmeldung/FotoUpload.vue | 161 +++++++++++++-----
4 files changed, 128 insertions(+), 57 deletions(-)
diff --git a/apps/api/src/services/anmeldung/anmeldungAccessTokenValidate.ts b/apps/api/src/services/anmeldung/anmeldungAccessTokenValidate.ts
index a906cd4b..78b032da 100644
--- a/apps/api/src/services/anmeldung/anmeldungAccessTokenValidate.ts
+++ b/apps/api/src/services/anmeldung/anmeldungAccessTokenValidate.ts
@@ -10,8 +10,8 @@ export const anmeldungAccessTokenValidateProcedure = definePublicQueryProcedure(
anmeldungId: z.number().int(),
accessToken: z.string().uuid(),
}),
- handler: ({ input: { unterveranstaltungId, anmeldungId, accessToken } }) =>
- prisma.anmeldung.findFirst({
+ handler: ({ input: { unterveranstaltungId, anmeldungId, accessToken } }) => {
+ return prisma.anmeldung.findFirst({
where: {
unterveranstaltungId,
id: anmeldungId,
@@ -23,6 +23,7 @@ export const anmeldungAccessTokenValidateProcedure = definePublicQueryProcedure(
id: true,
firstname: true,
lastname: true,
+ photoId: true,
},
},
unterveranstaltung: {
@@ -40,5 +41,6 @@ export const anmeldungAccessTokenValidateProcedure = definePublicQueryProcedure(
},
},
},
- }),
+ })
+ },
})
diff --git a/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue b/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
index 65772b51..d32a7f0e 100644
--- a/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
+++ b/apps/frontend/src/components/BasicInputs/InputFileUploadArea.vue
@@ -13,10 +13,7 @@ const props = defineProps<{
maxFileSize?: number
fullWindowDropzone?: boolean
uploadText?: string
-}>()
-
-const emit = defineEmits<{
- uploaded: [Multiple extends true ? File[] : File]
+ onUpload: (files: Multiple extends true ? File[] : File) => Promise
}>()
const InputFileUploadAreaConf = {
@@ -85,8 +82,9 @@ async function upload() {
try {
uploadPending.value = true
error.value = null
- const result = await handleUpload(files.value, props.multiple, props.maxFileSize)
- emit('uploaded', result)
+ const result = await prepareUpload(files.value, props.multiple, props.maxFileSize)
+ await props.onUpload(result)
+ files.value = []
} catch (uploadError: any) {
if (uploadError.message === 'file size too large' && props.maxFileSize) {
error.value = `Die Dateigröße muss kleiner als ${formatBytes(props.maxFileSize)} sein`
@@ -102,7 +100,7 @@ async function upload() {
}
}
-async function handleUpload(uploadFiles, multiple = false, maxFileSize) {
+async function prepareUpload(uploadFiles, multiple = false, maxFileSize) {
if (multiple) {
const toUploadFiles: any[] = []
for (const key in uploadFiles) {
@@ -143,6 +141,8 @@ const mimeMap: Record = {
application: 'Ausführbare Datei',
unknown: 'Unbekanntes Dateiformat',
}
+
+defineExpose({ files })
diff --git a/apps/frontend/src/components/UIComponents/AvatarEditModal.vue b/apps/frontend/src/components/UIComponents/AvatarEditModal.vue
index 041876ac..cf5909b7 100644
--- a/apps/frontend/src/components/UIComponents/AvatarEditModal.vue
+++ b/apps/frontend/src/components/UIComponents/AvatarEditModal.vue
@@ -101,7 +101,7 @@ defineExpose({ open, close })
:multiple="false"
upload-text="Bild hier hin ziehen oder klicken."
class="w-full"
- @uploaded="upload"
+ :on-upload="upload"
/>
diff --git a/apps/frontend/src/views/Anmeldung/FotoUpload.vue b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
index c9f7d567..70dba903 100644
--- a/apps/frontend/src/views/Anmeldung/FotoUpload.vue
+++ b/apps/frontend/src/views/Anmeldung/FotoUpload.vue
@@ -7,11 +7,14 @@ import Loading from '@/components/UIComponents/Loading.vue'
import { handlePublicPhotoUpload } from '@/helpers/handleUpload'
import { injectUnterveranstaltung } from '@/layouts/AnmeldungLayout.vue'
import { useAsyncState } from '@vueuse/core'
-import { computed, ref, watch } from 'vue'
+import { computed, ref, useTemplateRef, watch } from 'vue'
import { dayjs } from '@codeanker/helpers'
+import cn from '@/helpers/cn'
const route = useRoute()
+const uploader = useTemplateRef('uploader')
+
const unterveranstaltung = injectUnterveranstaltung()
const meldeschlussErreicht = computed(() => dayjs().isAfter(unterveranstaltung.value.meldeschluss))
@@ -30,12 +33,29 @@ const { state, execute, error } = useAsyncState(
{ immediate: false }
)
+const { state: currentPhotoUrl, execute: loadPhoto } = useAsyncState(
+ async () => {
+ if (!state.value?.person.photoId) {
+ return null
+ }
+ return await apiClient.file.fileGetUrl.query({ id: state.value.person.photoId })
+ },
+ null,
+ { immediate: false }
+)
+
watch(unterveranstaltung, (unterveranstaltung) => {
if (unterveranstaltung) {
execute()
}
})
+watch(state, (state) => {
+ if (state?.person.photoId) {
+ loadPhoto()
+ }
+})
+
const uploadPending = ref(false)
const uploadSuccess = ref(false)
const uploadError = ref
()
@@ -53,6 +73,8 @@ async function upload(toUploadFile: File) {
accessToken,
fileId: res.id,
})
+ await execute()
+ await loadPhoto()
uploadSuccess.value = true
} catch (error) {
console.error(error)
@@ -66,17 +88,12 @@ async function upload(toUploadFile: File) {
-
Fotoupload
-
Ein Fehler ist aufgetreten
{{ error }}
-
- Das Bild wurde hochgeladen!
-
Lade Daten …
@@ -84,56 +101,108 @@ async function upload(toUploadFile: File) {
Die angegeben Daten konnten nicht verifiziert werden. Wahrscheinlich ist der Zugangstoken falsch!
-
-
- Vielen Dank für die Anmeldung von {{ state.person.firstname }} {{ state.person.lastname }} zur
- Veranstaltung {{ state.unterveranstaltung.veranstaltung.name }} .
-
-
-
- Leider ist der Meldeschluss bereits erreicht.
-
-
-
- Lade hier ein Bild von {{ state.person.firstname }} {{ state.person.lastname }} hoch. Dieses wird dann
- bspw. für Teilnehmendenausweise verwendet.
-
+
+
+
Fotoupload
- Wichtig: Bitte achte darauf, dass das Foto möglichst quadratisch ist (gleiche Höhe wie
- Breite).
+ Vielen Dank für die Anmeldung von {{ state.person.firstname }} {{ state.person.lastname }} zur
+ Veranstaltung {{ state.unterveranstaltung.veranstaltung.name }} .
-
-
- dein Bild wird hochgeladen
-
-
-
{{ uploadError.message }}
-
-
-
-
+ Leider ist der Meldeschluss bereits erreicht.
+
+
+
+ Lade hier ein Bild von {{ state.person.firstname }} {{ state.person.lastname }} hoch. Dieses wird
+ dann bspw. für Teilnehmendenausweise verwendet.
+
+
+ Wichtig: Bitte achte darauf, dass das Foto möglichst quadratisch ist (gleiche Höhe wie
+ Breite).
+
+
+
-
-
-
-
+
+ dein Bild wird hochgeladen
+
+
+
{{ uploadError.message }}
+
+
+ Das Bild wurde hochgeladen!
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
Aktuelles Foto
+
+
+
+
+
From 7c1a8ed02a25ecd8ddb03d31976a7805bbb6b193 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sat, 8 Mar 2025 19:27:05 +0100
Subject: [PATCH 8/9] fix: update permissions
---
.../customFields/customFields.router.ts | 8 +--
.../customFields/customFieldsDelete.ts | 66 +++++++++++++++++++
.../customFields/customFieldsUpdate.ts | 57 ++++++++++++++++
.../customFieldsVeranstaltungDelete.ts | 36 ----------
.../customFieldsVeranstaltungUpdate.ts | 26 --------
.../CustomFields/CustomFieldsFormEdit.vue | 4 +-
.../CustomFields/CustomFieldsTable.vue | 10 ++-
7 files changed, 138 insertions(+), 69 deletions(-)
create mode 100644 apps/api/src/services/customFields/customFieldsDelete.ts
create mode 100644 apps/api/src/services/customFields/customFieldsUpdate.ts
delete mode 100644 apps/api/src/services/customFields/customFieldsVeranstaltungDelete.ts
delete mode 100644 apps/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts
diff --git a/apps/api/src/services/customFields/customFields.router.ts b/apps/api/src/services/customFields/customFields.router.ts
index f7d75c3d..b3d6b8d6 100644
--- a/apps/api/src/services/customFields/customFields.router.ts
+++ b/apps/api/src/services/customFields/customFields.router.ts
@@ -6,8 +6,8 @@ import { customFieldsList } from './customFieldsList.js'
import { customFieldsTemplates } from './customFieldsTemplates.js'
import { customFieldsUnterveranstaltungCreate } from './customFieldsUnterveranstaltungCreate.js'
import { customFieldsVeranstaltungCreate } from './customFieldsVeranstaltungCreate.js'
-import { customFieldsVeranstaltungDelete } from './customFieldsVeranstaltungDelete.js'
-import { customFieldsVeranstaltungUpdate } from './customFieldsVeranstaltungUpdate.js'
+import { customFieldsDelete } from './customFieldsDelete.js'
+import { customFieldsUpdate } from './customFieldsUpdate.js'
import { customFieldValuesUpdate } from './customFieldValuesUpdate.js'
// Import Routes here - do not delete this line
@@ -15,8 +15,8 @@ export const customFieldsRouter = mergeRouters(
customFieldsList.router,
customFieldsGet.router,
customFieldsVeranstaltungCreate.router,
- customFieldsVeranstaltungUpdate.router,
- customFieldsVeranstaltungDelete.router,
+ customFieldsUpdate.router,
+ customFieldsDelete.router,
customFieldsUnterveranstaltungCreate.router,
customFieldValuesUpdate.router,
customFieldsTemplates.router
diff --git a/apps/api/src/services/customFields/customFieldsDelete.ts b/apps/api/src/services/customFields/customFieldsDelete.ts
new file mode 100644
index 00000000..8f7adccc
--- /dev/null
+++ b/apps/api/src/services/customFields/customFieldsDelete.ts
@@ -0,0 +1,66 @@
+import { Role } from '@prisma/client'
+import { TRPCError } from '@trpc/server'
+import { z } from 'zod'
+
+import prisma from '../../prisma.js'
+import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js'
+
+export const customFieldsDelete = defineProtectedMutateProcedure({
+ key: 'delete',
+ roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN],
+ inputSchema: z.strictObject({
+ veranstaltungId: z.number(),
+ fieldId: z.number(),
+ }),
+ async handler({ ctx, input }) {
+ if (ctx.account.role === Role.GLIEDERUNG_ADMIN) {
+ const { gliederungId: gliederungIdActor } = await prisma.person.findFirstOrThrow({
+ where: {
+ account: {
+ id: ctx.accountId,
+ },
+ },
+ select: {
+ gliederungId: true,
+ },
+ })
+ const customField = await prisma.customField.findUniqueOrThrow({
+ where: {
+ id: input.fieldId,
+ },
+ select: {
+ unterveranstaltung: {
+ select: {
+ gliederungId: true,
+ },
+ },
+ },
+ })
+ if (gliederungIdActor !== customField.unterveranstaltung?.gliederungId) {
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ })
+ }
+ }
+
+ const field = await prisma.customField.findUnique({
+ where: {
+ id: input.fieldId,
+ veranstaltungId: input.veranstaltungId,
+ },
+ })
+
+ if (field === null) {
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ })
+ }
+
+ await prisma.customField.delete({
+ where: {
+ id: input.fieldId,
+ veranstaltungId: input.veranstaltungId,
+ },
+ })
+ },
+})
diff --git a/apps/api/src/services/customFields/customFieldsUpdate.ts b/apps/api/src/services/customFields/customFieldsUpdate.ts
new file mode 100644
index 00000000..4ef8c3ea
--- /dev/null
+++ b/apps/api/src/services/customFields/customFieldsUpdate.ts
@@ -0,0 +1,57 @@
+import { Role } from '@prisma/client'
+import { z } from 'zod'
+
+import prisma from '../../prisma.js'
+import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js'
+
+import { TRPCError } from '@trpc/server'
+import { customFieldSchema } from './schema/customField.schema.js'
+
+export const customFieldsUpdate = defineProtectedMutateProcedure({
+ key: 'update',
+ roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN],
+ inputSchema: z.strictObject({
+ fieldId: z.number(),
+ data: customFieldSchema,
+ }),
+ async handler({ ctx, input }) {
+ if (ctx.account.role === Role.GLIEDERUNG_ADMIN) {
+ const { gliederungId: gliederungIdActor } = await prisma.person.findFirstOrThrow({
+ where: {
+ account: {
+ id: ctx.accountId,
+ },
+ },
+ select: {
+ gliederungId: true,
+ },
+ })
+ const customField = await prisma.customField.findUniqueOrThrow({
+ where: {
+ id: input.fieldId,
+ },
+ select: {
+ unterveranstaltung: {
+ select: {
+ gliederungId: true,
+ },
+ },
+ },
+ })
+ if (gliederungIdActor !== customField.unterveranstaltung?.gliederungId) {
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ })
+ }
+ }
+
+ return await prisma.customField.update({
+ where: {
+ id: input.fieldId,
+ },
+ data: {
+ ...input.data,
+ },
+ })
+ },
+})
diff --git a/apps/api/src/services/customFields/customFieldsVeranstaltungDelete.ts b/apps/api/src/services/customFields/customFieldsVeranstaltungDelete.ts
deleted file mode 100644
index f6876733..00000000
--- a/apps/api/src/services/customFields/customFieldsVeranstaltungDelete.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Role } from '@prisma/client'
-import { TRPCError } from '@trpc/server'
-import { z } from 'zod'
-
-import prisma from '../../prisma.js'
-import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js'
-
-export const customFieldsVeranstaltungDelete = defineProtectedMutateProcedure({
- key: 'veranstaltungDelete',
- roleIds: [Role.ADMIN],
- inputSchema: z.strictObject({
- veranstaltungId: z.number(),
- fieldId: z.number(),
- }),
- async handler({ input }) {
- const field = await prisma.customField.findUnique({
- where: {
- id: input.fieldId,
- veranstaltungId: input.veranstaltungId,
- },
- })
-
- if (field === null) {
- throw new TRPCError({
- code: 'NOT_FOUND',
- })
- }
-
- await prisma.customField.delete({
- where: {
- id: input.fieldId,
- veranstaltungId: input.veranstaltungId,
- },
- })
- },
-})
diff --git a/apps/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts b/apps/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts
deleted file mode 100644
index d8e0fd73..00000000
--- a/apps/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Role } from '@prisma/client'
-import { z } from 'zod'
-
-import prisma from '../../prisma.js'
-import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js'
-
-import { customFieldSchema } from './schema/customField.schema.js'
-
-export const customFieldsVeranstaltungUpdate = defineProtectedMutateProcedure({
- key: 'veranstaltungUpdate',
- roleIds: [Role.ADMIN],
- inputSchema: z.strictObject({
- fieldId: z.number(),
- data: customFieldSchema,
- }),
- async handler({ input }) {
- return await prisma.customField.update({
- where: {
- id: input.fieldId,
- },
- data: {
- ...input.data,
- },
- })
- },
-})
diff --git a/apps/frontend/src/components/CustomFields/CustomFieldsFormEdit.vue b/apps/frontend/src/components/CustomFields/CustomFieldsFormEdit.vue
index 1f356a0d..f57a3658 100644
--- a/apps/frontend/src/components/CustomFields/CustomFieldsFormEdit.vue
+++ b/apps/frontend/src/components/CustomFields/CustomFieldsFormEdit.vue
@@ -50,7 +50,7 @@ const {
const data = form.value
try {
- await apiClient.customFields.veranstaltungUpdate.mutate({
+ await apiClient.customFields.update.mutate({
fieldId: props.fieldId,
data: {
name: data.name,
@@ -79,7 +79,7 @@ const {
isLoading: isDeleting,
} = useAsyncState(
async () => {
- await apiClient.customFields.veranstaltungDelete.mutate({
+ await apiClient.customFields.delete.mutate({
fieldId: field.value!.id,
veranstaltungId: 0,
})
diff --git a/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue b/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue
index 85c761af..674aa71e 100644
--- a/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue
+++ b/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue
@@ -34,7 +34,15 @@ const router = useRouter()
type Field = (typeof fields.value)[number]
function canEdit(field: Field) {
- return loggedInAccount.value?.role === 'ADMIN' || field.unterveranstaltungId !== null
+ if (field.veranstaltungId !== null && loggedInAccount.value?.role === 'ADMIN') {
+ return true
+ }
+
+ if (field.unterveranstaltungId !== null && loggedInAccount.value?.role !== 'USER') {
+ return true
+ }
+
+ return false
}
function onClickRow(field: Field) {
From dfc67b94f8b5080c09fed09a349c1b11a52a2a3b Mon Sep 17 00:00:00 2001
From: Daniel Swiatek
Date: Tue, 11 Mar 2025 06:35:00 +0100
Subject: [PATCH 9/9] Update package.json
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 40ac46ed..6f338a97 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "codeanker-project",
"author": "CODEANKER GmbH",
- "version": "2.0.1",
+ "version": "2.1.1",
"description": "",
"license": "CC-BY-3.0-DE",
"workspaces": [