From e1fe63c3f5b5fcade62c00688e6c382b3f1ba9c3 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sat, 28 Feb 2026 21:29:44 +0100
Subject: [PATCH 01/11] fix: select gliederung domain
---
apps/api/src/services/gliederung/gliederungVerwaltungGet.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/api/src/services/gliederung/gliederungVerwaltungGet.ts b/apps/api/src/services/gliederung/gliederungVerwaltungGet.ts
index c5c42340..376af311 100644
--- a/apps/api/src/services/gliederung/gliederungVerwaltungGet.ts
+++ b/apps/api/src/services/gliederung/gliederungVerwaltungGet.ts
@@ -19,6 +19,7 @@ export const gliederungVerwaltungGetProcedure = defineProtectedQueryProcedure({
id: true,
name: true,
edv: true,
+ domain: true,
},
})
},
From a2fdc7216f5b326aa89ee9f65e6ec84b5039e317 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Sun, 1 Mar 2026 09:48:51 +0100
Subject: [PATCH 02/11] refactor: show privacy in it's own drawer
---
.../forms/person/FormPersonGeneral.vue | 3 ++-
apps/frontend/src/helpers/sanitizeHtml.ts | 6 ++++-
.../views/Anmeldung/AnmeldungFormGeneral.vue | 26 ++++++++++++++-----
3 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/apps/frontend/src/components/forms/person/FormPersonGeneral.vue b/apps/frontend/src/components/forms/person/FormPersonGeneral.vue
index 39b4204a..799d19ef 100644
--- a/apps/frontend/src/components/forms/person/FormPersonGeneral.vue
+++ b/apps/frontend/src/components/forms/person/FormPersonGeneral.vue
@@ -45,6 +45,7 @@ const props = defineProps<{
const emit = defineEmits<{
submit: [data: FormPersonGeneralSubmit]
showTerms: []
+ showPrivacy: []
}>()
async function queryGliederungen(searchTerm: string) {
@@ -237,7 +238,7 @@ const submit = () => {
Ich habe die gesonderten
Datenschutzerklärung
diff --git a/apps/frontend/src/helpers/sanitizeHtml.ts b/apps/frontend/src/helpers/sanitizeHtml.ts
index 1d978ad5..41c7157b 100644
--- a/apps/frontend/src/helpers/sanitizeHtml.ts
+++ b/apps/frontend/src/helpers/sanitizeHtml.ts
@@ -1,6 +1,10 @@
import doSanitizeHtml from 'sanitize-html'
-export function sanitizeHtml(html: string): string {
+export function sanitizeHtml(html?: string | null): string {
+ if (!html) {
+ return ''
+ }
+
return doSanitizeHtml(html, {
allowedTags: [
'h2',
diff --git a/apps/frontend/src/views/Anmeldung/AnmeldungFormGeneral.vue b/apps/frontend/src/views/Anmeldung/AnmeldungFormGeneral.vue
index 1cc44901..20fda7b4 100644
--- a/apps/frontend/src/views/Anmeldung/AnmeldungFormGeneral.vue
+++ b/apps/frontend/src/views/Anmeldung/AnmeldungFormGeneral.vue
@@ -10,6 +10,7 @@ import FormPersonGeneral, { type FormPersonGeneralSubmit } from '@/components/fo
import Drawer from '@/components/LayoutComponents/Drawer.vue'
import { injectUnterveranstaltung } from '@/layouts/AnmeldungLayout.vue'
import { type NahrungsmittelIntoleranz } from '@codeanker/api'
+import { sanitizeHtml } from '@/helpers/sanitizeHtml'
const props = withDefaults(
defineProps<{
@@ -42,6 +43,7 @@ watch(unterveranstaltung, () => loadCustomFields())
const customFieldValues = ref>({})
const showBedingungen = ref(false)
+const showDatenschutz = ref(false)
const {
execute: createAnmeldung,
@@ -109,22 +111,33 @@ const {
>
-
Teilnahmebedingungen
-
+
Teilnahmebedingungen der Gliederung
+
+
+
+
Allgemeine Teilnahmebedingungen
-
+
+
+
+
+
+
@@ -145,6 +158,7 @@ const {
:is-public-anmeldung="props.isPublic"
@submit="(value) => createAnmeldung(undefined, value)"
@show-terms="showBedingungen = true"
+ @show-privacy="showDatenschutz = true"
>
Date: Sun, 1 Mar 2026 22:27:03 +0100
Subject: [PATCH 03/11] fix: redirect based on request host
---
apps/api/src/routes/oidc/index.ts | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/apps/api/src/routes/oidc/index.ts b/apps/api/src/routes/oidc/index.ts
index fe904366..76355987 100644
--- a/apps/api/src/routes/oidc/index.ts
+++ b/apps/api/src/routes/oidc/index.ts
@@ -136,7 +136,16 @@ oidcRouter.get('/dlrg/login', async (c) => {
const as = await oauth.processDiscoveryResponse(issuer, discoveryRequestResponse)
const authorizationUrl = new URL(as.authorization_endpoint!)
- const redirectUri = new URL('/api/connect/dlrg/callback', config.clientUrl)
+ const host = await prisma.hostname.findFirst({
+ where: {
+ hostname: c.req.header('Host'),
+ }
+ })
+ if (host === null) {
+ return c.text('Invalid domain', 400)
+ }
+
+ const redirectUri = new URL('/api/connect/dlrg/callback', host.hostname)
const registerAs = c.req.query('as')?.trim()
if (registerAs !== undefined && registerAs?.length > 0) {
redirectUri.searchParams.set('as', registerAs)
From 8a4b39e87c9ff42ad273f67bcaa6560eb32a83b0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 4 Mar 2026 20:49:43 +0000
Subject: [PATCH 04/11] chore(deps): bump hono from 4.12.0 to 4.12.4
Bumps [hono](https://github.com/honojs/hono) from 4.12.0 to 4.12.4.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.0...v4.12.4)
---
updated-dependencies:
- dependency-name: hono
dependency-version: 4.12.4
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
apps/api/package.json | 2 +-
pnpm-lock.yaml | 29 +++++++++++++++++------------
2 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/apps/api/package.json b/apps/api/package.json
index 03168c28..f3d74cd6 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -45,7 +45,7 @@
"dot-prop": "^9.0.0",
"fast-csv": "^5.0.1",
"handlebars": "^4.7.8",
- "hono": "^4.12.0",
+ "hono": "^4.12.4",
"http-errors": "^2.0.1",
"jsonwebtoken": "^9.0.2",
"meilisearch": "^0.37.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 94e82143..2726fdbc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -118,10 +118,10 @@ importers:
version: 10.1.0
'@hono/node-server':
specifier: ^1.19.6
- version: 1.19.7(hono@4.12.0)
+ version: 1.19.7(hono@4.12.4)
'@hono/trpc-server':
specifier: ^0.4.0
- version: 0.4.1(@trpc/server@11.0.0-rc.682(typescript@5.7.2))(hono@4.12.0)
+ version: 0.4.1(@trpc/server@11.0.0-rc.682(typescript@5.7.2))(hono@4.12.4)
'@prisma/client':
specifier: ^5.19.1
version: 5.22.0(prisma@5.22.0)
@@ -153,8 +153,8 @@ importers:
specifier: ^4.7.8
version: 4.7.8
hono:
- specifier: ^4.12.0
- version: 4.12.0
+ specifier: ^4.12.4
+ version: 4.12.4
http-errors:
specifier: ^2.0.1
version: 2.0.1
@@ -3625,8 +3625,8 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
- hono@4.12.0:
- resolution: {integrity: sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==}
+ hono@4.12.4:
+ resolution: {integrity: sha512-ooiZW1Xy8rQ4oELQ++otI2T9DsKpV0M6c6cO6JGx4RTfav9poFFLlet9UMXHZnoM1yG0HWGlQLswBGX3RZmHtg==}
engines: {node: '>=16.9.0'}
hookable@5.5.3:
@@ -3917,6 +3917,9 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ lodash@4.17.23:
+ resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
+
logform@2.7.0:
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
engines: {node: '>= 12.0.0'}
@@ -6047,14 +6050,14 @@ snapshots:
dependencies:
vue: 3.5.13(typescript@5.7.2)
- '@hono/node-server@1.19.7(hono@4.12.0)':
+ '@hono/node-server@1.19.7(hono@4.12.4)':
dependencies:
- hono: 4.12.0
+ hono: 4.12.4
- '@hono/trpc-server@0.4.1(@trpc/server@11.0.0-rc.682(typescript@5.7.2))(hono@4.12.0)':
+ '@hono/trpc-server@0.4.1(@trpc/server@11.0.0-rc.682(typescript@5.7.2))(hono@4.12.4)':
dependencies:
'@trpc/server': 11.0.0-rc.682(typescript@5.7.2)
- hono: 4.12.0
+ hono: 4.12.4
'@humanfs/core@0.19.1': {}
@@ -8292,7 +8295,7 @@ snapshots:
he@1.2.0: {}
- hono@4.12.0: {}
+ hono@4.12.4: {}
hookable@5.5.3: {}
@@ -8564,6 +8567,8 @@ snapshots:
lodash@4.17.21: {}
+ lodash@4.17.23: {}
+
logform@2.7.0:
dependencies:
'@colors/colors': 1.6.0
@@ -10127,7 +10132,7 @@ snapshots:
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.7.0
- lodash: 4.17.21
+ lodash: 4.17.23
semver: 7.7.4
transitivePeerDependencies:
- supports-color
From 24821fde6e1acf9f53842ac6cc76df769923bc99 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Wed, 4 Mar 2026 21:55:06 +0100
Subject: [PATCH 05/11] feat: custom field ordering
---
.../migration.sql | 2 +
apps/api/prisma/schema/CustomField.prisma | 1 +
.../customFields/customFields.router.ts | 6 +-
.../services/customFields/customFieldsList.ts | 73 +++++---
.../customFields/schema/customField.schema.ts | 26 ++-
.../customFieldsUnterveranstaltungOrder.ts | 54 ++++++
.../schema/customFieldsVeranstaltungOrder.ts | 44 +++++
.../CustomFields/CustomFieldsTable.vue | 13 ++
.../CustomFieldUnterveranstaltungOrder.vue | 168 ++++++++++++++++++
.../UnterveranstaltungDetail.vue | 11 ++
.../src/views/Unterveranstaltung/routes.ts | 13 ++
.../CustomFieldVeranstaltungOrder.vue | 126 +++++++++++++
.../Veranstaltungen/VeranstaltungDetail.vue | 26 ++-
.../Verwaltung/Veranstaltungen/routes.ts | 13 ++
14 files changed, 536 insertions(+), 40 deletions(-)
create mode 100644 apps/api/prisma/migrations/20260304182124_custom_field_order/migration.sql
create mode 100644 apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts
create mode 100644 apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts
create mode 100644 apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
create mode 100644 apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
diff --git a/apps/api/prisma/migrations/20260304182124_custom_field_order/migration.sql b/apps/api/prisma/migrations/20260304182124_custom_field_order/migration.sql
new file mode 100644
index 00000000..79c1d2b8
--- /dev/null
+++ b/apps/api/prisma/migrations/20260304182124_custom_field_order/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "CustomField" ADD COLUMN "order" INTEGER;
diff --git a/apps/api/prisma/schema/CustomField.prisma b/apps/api/prisma/schema/CustomField.prisma
index 0a1415df..ac05f0a7 100644
--- a/apps/api/prisma/schema/CustomField.prisma
+++ b/apps/api/prisma/schema/CustomField.prisma
@@ -19,6 +19,7 @@ model CustomField {
id String @id @default(uuid(7))
name String
description String?
+ order Int?
type CustomFieldType
required Boolean @default(false)
options String[]
diff --git a/apps/api/src/services/customFields/customFields.router.ts b/apps/api/src/services/customFields/customFields.router.ts
index 3e38d8cc..5f861fa8 100644
--- a/apps/api/src/services/customFields/customFields.router.ts
+++ b/apps/api/src/services/customFields/customFields.router.ts
@@ -9,6 +9,8 @@ import { customFieldsVeranstaltungCreate } from './customFieldsVeranstaltungCrea
import { customFieldsVeranstaltungDelete, customFieldsUnterveranstaltungDelete } from './customFieldsDelete.js'
import { customFieldsUpdate } from './customFieldsUpdate.js'
import { customFieldValuesUpdate } from './customFieldValuesUpdate.js'
+import { customFieldUnterveranstaltungOrder } from './schema/customFieldsUnterveranstaltungOrder.js'
+import { customFieldVeranstaltungOrder } from './schema/customFieldsVeranstaltungOrder.js'
// Import Routes here - do not delete this line
export const customFieldsRouter = mergeRouters(
@@ -21,6 +23,8 @@ export const customFieldsRouter = mergeRouters(
customFieldsUnterveranstaltungDelete,
customFieldsUnterveranstaltungCreate,
customFieldValuesUpdate,
- customFieldsTemplates
+ customFieldsTemplates,
+ customFieldVeranstaltungOrder,
+ customFieldUnterveranstaltungOrder
// Add Routes here - do not delete this line
)
diff --git a/apps/api/src/services/customFields/customFieldsList.ts b/apps/api/src/services/customFields/customFieldsList.ts
index f7d9a09c..35fbd0f9 100644
--- a/apps/api/src/services/customFields/customFieldsList.ts
+++ b/apps/api/src/services/customFields/customFieldsList.ts
@@ -2,7 +2,7 @@ import { z } from 'zod'
import { CustomFieldPosition, CustomFieldType, Prisma } from '@prisma/client'
import prisma from '../../prisma.js'
-import { definePublicQueryProcedure } from '../../types/defineProcedure.js'
+import { defineProtectedQueryProcedure, definePublicQueryProcedure } from '../../types/defineProcedure.js'
import {
calculatePagination,
defineEmptyQueryResponse,
@@ -10,6 +10,7 @@ import {
defineTableInput,
} from '../../types/defineTableProcedure.js'
import { boolish } from '../../util/zod.js'
+import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js'
const baseFilter = z.strictObject({
entity: z.enum(['veranstaltung', 'unterveranstaltung']),
@@ -23,6 +24,7 @@ export const customFieldsTable = definePublicQueryProcedure({
async handler({ input: { entity, entityId, position } }) {
if (entity === 'veranstaltung') {
return await prisma.customField.findMany({
+ orderBy: [{ order: { sort: 'asc', nulls: 'last' } }],
where: {
veranstaltungId: entityId,
positions:
@@ -35,6 +37,14 @@ export const customFieldsTable = definePublicQueryProcedure({
})
} else if (entity === 'unterveranstaltung') {
return await prisma.customField.findMany({
+ orderBy: [
+ {
+ unterveranstaltungId: { sort: 'asc', nulls: 'first' },
+ },
+ {
+ order: { sort: 'asc', nulls: 'last' },
+ },
+ ],
where: {
positions:
position === undefined
@@ -64,8 +74,9 @@ export const customFieldsTable = definePublicQueryProcedure({
},
})
-export const customFieldsList = definePublicQueryProcedure({
+export const customFieldsList = defineProtectedQueryProcedure({
key: 'table',
+ roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'],
inputSchema: baseFilter.extend({
table: defineTableInput({
filter: {
@@ -74,10 +85,14 @@ export const customFieldsList = definePublicQueryProcedure({
required: boolish,
position: z.nativeEnum(CustomFieldPosition),
},
- orderBy: ['name'],
+ orderBy: ['name', 'order'],
}),
}),
- async handler({ input }) {
+ async handler({ ctx, input }) {
+ if (ctx.account.role === 'GLIEDERUNG_ADMIN') {
+ await getGliederungRequireAdmin(ctx.accountId)
+ }
+
const where: Prisma.CustomFieldWhereInput = {
name: {
contains: input.table?.filter?.name,
@@ -93,6 +108,25 @@ export const customFieldsList = definePublicQueryProcedure({
},
}
+ if (input.entity === 'veranstaltung') {
+ where.veranstaltungId = input.entityId
+ } else if (input.entity === 'unterveranstaltung') {
+ where.OR = [
+ {
+ unterveranstaltungId: input.entityId,
+ },
+ {
+ veranstaltung: {
+ unterveranstaltungen: {
+ some: {
+ id: input.entityId,
+ },
+ },
+ },
+ },
+ ]
+ }
+
const total = await prisma.customField.count({ where })
const { pageIndex, pageSize, pages } = calculatePagination(total, input.table?.pagination)
@@ -100,15 +134,13 @@ export const customFieldsList = definePublicQueryProcedure({
const customFields = await prisma.customField.findMany({
take: pageSize,
skip: pageSize * pageIndex,
- where: {
- ...where,
- veranstaltungId: input.entityId,
- },
+ where,
orderBy: input.table?.orderBy,
select: {
id: true,
name: true,
description: true,
+ order: true,
type: true,
positions: true,
required: true,
@@ -122,27 +154,18 @@ export const customFieldsList = definePublicQueryProcedure({
const customFields = await prisma.customField.findMany({
take: pageSize,
skip: pageSize * pageIndex,
- where: {
- ...where,
- OR: [
- {
- unterveranstaltungId: input.entityId,
- },
- {
- veranstaltung: {
- unterveranstaltungen: {
- some: {
- id: input.entityId,
- },
- },
- },
- },
- ],
- },
+ orderBy: [
+ {
+ unterveranstaltungId: { sort: 'asc', nulls: 'first' },
+ },
+ ...(input.table?.orderBy ?? []),
+ ],
+ where,
select: {
id: true,
name: true,
description: true,
+ order: true,
type: true,
positions: true,
required: true,
diff --git a/apps/api/src/services/customFields/schema/customField.schema.ts b/apps/api/src/services/customFields/schema/customField.schema.ts
index 8172e451..af4585c1 100644
--- a/apps/api/src/services/customFields/schema/customField.schema.ts
+++ b/apps/api/src/services/customFields/schema/customField.schema.ts
@@ -1,11 +1,21 @@
import { CustomFieldPosition, CustomFieldType } from '@prisma/client'
import { z } from 'zod'
-export const customFieldSchema = z.strictObject({
- name: z.string().min(1),
- description: z.string().nullable(),
- type: z.nativeEnum(CustomFieldType),
- required: z.boolean(),
- options: z.array(z.string()),
- positions: z.nativeEnum(CustomFieldPosition).array(),
-})
+export const customFieldSchema = z
+ .strictObject({
+ name: z.string().min(1),
+ description: z.string().nullable(),
+ type: z.nativeEnum(CustomFieldType),
+ required: z.boolean(),
+ options: z.array(z.string()),
+ positions: z.nativeEnum(CustomFieldPosition).array().min(1),
+ })
+ .superRefine((values, ctx) => {
+ const optionTypes = ['BASIC_DROPDOWN', 'BASIC_RADIO', 'BASIC_SWITCH'] as CustomFieldType[]
+ if (optionTypes.includes(values.type) && values.options.length === 0) {
+ ctx.addIssue({
+ code: 'custom',
+ message: 'Es muss mindestens eine Auswahlmöglichkeit angegeben werden',
+ })
+ }
+ })
diff --git a/apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts b/apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts
new file mode 100644
index 00000000..8f672d27
--- /dev/null
+++ b/apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts
@@ -0,0 +1,54 @@
+import z from 'zod'
+import { defineProtectedMutateProcedure } from '../../../types/defineProcedure.js'
+import { getGliederungRequireAdmin } from '../../../util/getGliederungRequireAdmin.js'
+import { TRPCError } from '@trpc/server'
+import prisma from '../../../prisma.js'
+
+export const customFieldUnterveranstaltungOrder = defineProtectedMutateProcedure({
+ key: 'unterveranstaltungOrder',
+ roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'],
+ inputSchema: z.strictObject({
+ unterveranstaltungId: z.uuid(),
+ fields: z.array(z.uuid()),
+ }),
+ handler: async ({ ctx, input }) => {
+ if (ctx.account.role === 'GLIEDERUNG_ADMIN') {
+ const gliederung = await getGliederungRequireAdmin(ctx.accountId)
+ if (gliederung.id !== input.unterveranstaltungId) {
+ throw new TRPCError({
+ code: 'FORBIDDEN',
+ })
+ }
+ }
+
+ const fields = await prisma.customField.findMany({
+ where: {
+ unterveranstaltungId: input.unterveranstaltungId,
+ id: {
+ in: input.fields,
+ },
+ },
+ })
+
+ if (fields.length !== input.fields.length) {
+ throw new TRPCError({
+ code: 'BAD_REQUEST',
+ message: 'Some supplied fields do not belong to the given unterveranstaltung!',
+ })
+ }
+
+ await prisma.$transaction(
+ input.fields.map((field, index) =>
+ prisma.customField.update({
+ where: {
+ unterveranstaltungId: input.unterveranstaltungId,
+ id: field,
+ },
+ data: {
+ order: index + 1,
+ },
+ })
+ )
+ )
+ },
+})
diff --git a/apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts b/apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts
new file mode 100644
index 00000000..ac435865
--- /dev/null
+++ b/apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts
@@ -0,0 +1,44 @@
+import { TRPCError } from '@trpc/server'
+import z from 'zod'
+import prisma from '../../../prisma.js'
+import { defineProtectedMutateProcedure } from '../../../types/defineProcedure.js'
+
+export const customFieldVeranstaltungOrder = defineProtectedMutateProcedure({
+ key: 'veranstaltungOrder',
+ roleIds: ['ADMIN'],
+ inputSchema: z.strictObject({
+ veranstaltungId: z.uuid(),
+ fields: z.array(z.uuid()),
+ }),
+ handler: async ({ input }) => {
+ const fields = await prisma.customField.findMany({
+ where: {
+ veranstaltungId: input.veranstaltungId,
+ id: {
+ in: input.fields,
+ },
+ },
+ })
+
+ if (fields.length !== input.fields.length) {
+ throw new TRPCError({
+ code: 'BAD_REQUEST',
+ message: 'Some supplied fields do not belong to the given veranstaltung!',
+ })
+ }
+
+ await prisma.$transaction(
+ input.fields.map((field, index) =>
+ prisma.customField.update({
+ where: {
+ veranstaltungId: input.veranstaltungId,
+ id: field,
+ },
+ data: {
+ order: index + 1,
+ },
+ })
+ )
+ )
+ },
+})
diff --git a/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue b/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue
index f9bcf945..2ec38a34 100644
--- a/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue
+++ b/apps/frontend/src/components/CustomFields/CustomFieldsTable.vue
@@ -32,6 +32,18 @@ const router = useRouter()
const column = createColumnHelper()
const columns = [
+ column.accessor('order', {
+ header: 'Reihenfolge',
+ enableSorting: true,
+ cell({ getValue }) {
+ const order = getValue()
+ if (!order) {
+ return '-'
+ }
+
+ return order
+ },
+ }),
column.accessor('name', {
header: 'Name',
enableColumnFilter: true,
@@ -174,6 +186,7 @@ function onClick(field: CustomField) {
diff --git a/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue b/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
new file mode 100644
index 00000000..d324a12e
--- /dev/null
+++ b/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+ Reihenfolge
+ Name
+ Typ
+ Quelle
+
+
+
+
+
+
+
+ {{ field.order || '-' }}
+ ( {{ index + 1 }} )
+
+
+
+
+
+
+
+
+
+
+ {{ field.name }}
+ {{ formatType(field) }}
+ {{ formatSource(field) }}
+
+
+
+
+
+
+ Hinweis : Die Spalte Reihenfolge gliedert sich wie folgt: Die erste Zahl zeigt die aktuelle Position in
+ der Reihenfolge wohingegen die Zahl in der Klammer die neue Position anzeigt.
+
+
+
+
+ Speichern
+
+ Abbrechen
+
+
diff --git a/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue b/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue
index 8c8f7e8a..6a8eecbf 100644
--- a/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue
+++ b/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue
@@ -371,6 +371,7 @@ const anmeldeLinkCreateModal = useTemplateRef('anmeldeLinkCreateModal')
Hier können benutzerdefinierte Felder erstellt werden, welche für alle Unterveranstaltungen gelten.
+
+ Reihenfolge ändern
+
+
import('./CustomFields/CustomFieldUnterveranstaltungOrder.vue'),
+ meta: {
+ breadcrumbs: [
+ detailCrumb,
+ {
+ text: 'Benutzerdefinierte Felder sortieren',
+ },
+ ],
+ },
+ },
{
name: 'Unterveranstaltung Custom Field bearbeiten',
path: ':unterveranstaltungId/fields/:fieldId',
diff --git a/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue b/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
new file mode 100644
index 00000000..347fd5f3
--- /dev/null
+++ b/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+ Reihenfolge
+ Name
+ Typ
+ Quelle
+
+
+
+
+
+
+ {{ field.order || '-' }}
+ ( {{ index + 1 }} )
+
+
+
+
+
+
+
+
+
+
+ {{ field.name }}
+ {{ formatType(field) }}
+ Veranstaltung
+
+
+
+
+
+ Hinweis : Die Spalte Reihenfolge gliedert sich wie folgt: Die erste Zahl zeigt die aktuelle Position in
+ der Reihenfolge wohingegen die Zahl in der Klammer die neue Position anzeigt.
+
+
+
+
+ Speichern
+
+ Abbrechen
+
+
diff --git a/apps/frontend/src/views/Verwaltung/Veranstaltungen/VeranstaltungDetail.vue b/apps/frontend/src/views/Verwaltung/Veranstaltungen/VeranstaltungDetail.vue
index 7730e943..74d2d789 100644
--- a/apps/frontend/src/views/Verwaltung/Veranstaltungen/VeranstaltungDetail.vue
+++ b/apps/frontend/src/views/Verwaltung/Veranstaltungen/VeranstaltungDetail.vue
@@ -10,6 +10,7 @@ import {
RocketLaunchIcon,
DocumentDuplicateIcon,
LinkIcon,
+ ChevronUpDownIcon,
} from '@heroicons/vue/24/outline'
import { useAsyncState } from '@vueuse/core'
import { computed } from 'vue'
@@ -352,12 +353,25 @@ function copyProgramLink() {
Hier können benutzerdefinierte Felder erstellt werden, welche für alle Unterveranstaltungen gelten.
-
- Neues Feld
-
+
+
+
+ Neues Feld
+
+
+
+ Reihenfolge ändern
+
+
import('./CustomFields/CustomFieldVeranstaltungOrder.vue'),
+ meta: {
+ breadcrumbs: [
+ detailCrumb,
+ {
+ text: 'Benutzerdefinierte Felder sortieren',
+ },
+ ],
+ },
+ },
{
name: 'Verwaltung Custom Field bearbeiten',
path: ':veranstaltungId/fields/:fieldId',
From 82b0b30c1c640ab5ca58705f0c15b14e6387ebbe Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Wed, 4 Mar 2026 22:50:52 +0100
Subject: [PATCH 06/11] chore: remove unused import
---
.../CustomFields/CustomFieldVeranstaltungOrder.vue | 1 -
1 file changed, 1 deletion(-)
diff --git a/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue b/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
index 347fd5f3..0e0d5c20 100644
--- a/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
+++ b/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
@@ -2,7 +2,6 @@
import { apiClient } from '@/api'
import Button from '@/components/UIComponents/Button.vue'
import { useRouteTitle } from '@/composables/useRouteTitle'
-import cn from '@/helpers/cn'
import { CustomFieldTypeMapping, type CustomField } from '@codeanker/api'
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'
import { useMutation, useQuery } from '@tanstack/vue-query'
From 0ab0d2aa4679307b7ea42bd9b2820b09ba7def51 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Wed, 4 Mar 2026 23:00:42 +0100
Subject: [PATCH 07/11] fix: zod schema
---
.../schema/customFieldsUnterveranstaltungOrder.ts | 4 ++--
.../customFields/schema/customFieldsVeranstaltungOrder.ts | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts b/apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts
index 8f672d27..2b0f974a 100644
--- a/apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts
+++ b/apps/api/src/services/customFields/schema/customFieldsUnterveranstaltungOrder.ts
@@ -8,8 +8,8 @@ export const customFieldUnterveranstaltungOrder = defineProtectedMutateProcedure
key: 'unterveranstaltungOrder',
roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'],
inputSchema: z.strictObject({
- unterveranstaltungId: z.uuid(),
- fields: z.array(z.uuid()),
+ unterveranstaltungId: z.string().uuid(),
+ fields: z.array(z.string().uuid()),
}),
handler: async ({ ctx, input }) => {
if (ctx.account.role === 'GLIEDERUNG_ADMIN') {
diff --git a/apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts b/apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts
index ac435865..3cc63732 100644
--- a/apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts
+++ b/apps/api/src/services/customFields/schema/customFieldsVeranstaltungOrder.ts
@@ -7,8 +7,8 @@ export const customFieldVeranstaltungOrder = defineProtectedMutateProcedure({
key: 'veranstaltungOrder',
roleIds: ['ADMIN'],
inputSchema: z.strictObject({
- veranstaltungId: z.uuid(),
- fields: z.array(z.uuid()),
+ veranstaltungId: z.string().uuid(),
+ fields: z.array(z.string().uuid()),
}),
handler: async ({ input }) => {
const fields = await prisma.customField.findMany({
From 21c880347e8e1745a263290542a397ab905ae410 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Wed, 4 Mar 2026 23:17:45 +0100
Subject: [PATCH 08/11] chore: ui updates
---
.../CustomFieldUnterveranstaltungOrder.vue | 28 ++++++++++++++++---
.../UnterveranstaltungDetail.vue | 12 +++++---
.../CustomFieldVeranstaltungOrder.vue | 25 +++++++++++++++--
3 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue b/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
index d324a12e..ae0387af 100644
--- a/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
+++ b/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
@@ -5,10 +5,11 @@ import { useRouteTitle } from '@/composables/useRouteTitle'
import cn from '@/helpers/cn'
import { CustomFieldTypeMapping, type CustomField } from '@codeanker/api'
import { groupBy } from '@codeanker/helpers'
-import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'
+import { ArrowLeftIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'
import { useMutation, useQuery } from '@tanstack/vue-query'
import { useRouteParams } from '@vueuse/router'
import { computed, ref, watch } from 'vue'
+import { RouterLink } from 'vue-router'
const { setTitle } = useRouteTitle()
setTitle('Benutzerdefinierte Felder sortieren')
@@ -93,6 +94,23 @@ const { mutate, isPending } = useMutation({
+
+
+
+ Zurück zur Ausschreibung
+
+
+
+
+
Reihenfolge ändern
+
+ Hier kann die Reihenfolge der benutzerdefinierten Felder deiner Ausschreibung verändert werden.
+
+
+
@@ -110,7 +128,7 @@ const { mutate, isPending } = useMutation({
:class="
cn({
'even:bg-slate-50 dark:even:bg-slate-800': !!field.unterveranstaltungId,
- 'text-gray-400 italic': !!field.veranstaltungId,
+ 'text-gray-400 italic cursor-not-allowed': !!field.veranstaltungId,
})
"
>
@@ -161,8 +179,10 @@ const { mutate, isPending } = useMutation({
Abbrechen
+ Abbrechen
+
diff --git a/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue b/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue
index 6a8eecbf..83b18296 100644
--- a/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue
+++ b/apps/frontend/src/views/Unterveranstaltung/UnterveranstaltungDetail.vue
@@ -2,12 +2,14 @@
import {
CameraIcon,
ChatBubbleLeftRightIcon,
+ ChevronUpDownIcon,
CodeBracketIcon,
DocumentDuplicateIcon,
DocumentIcon,
HandRaisedIcon,
LinkIcon,
MegaphoneIcon,
+ PlusIcon,
RocketLaunchIcon,
SquaresPlusIcon,
UserGroupIcon,
@@ -373,22 +375,24 @@ const anmeldeLinkCreateModal = useTemplateRef('anmeldeLinkCreateModal')
- Neues Feld
+
+ Neues Feld
- Reihenfolge ändern
+
+ Reihenfolge ändern
diff --git a/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue b/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
index 0e0d5c20..df07c4a2 100644
--- a/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
+++ b/apps/frontend/src/views/Verwaltung/Veranstaltungen/CustomFields/CustomFieldVeranstaltungOrder.vue
@@ -3,7 +3,7 @@ import { apiClient } from '@/api'
import Button from '@/components/UIComponents/Button.vue'
import { useRouteTitle } from '@/composables/useRouteTitle'
import { CustomFieldTypeMapping, type CustomField } from '@codeanker/api'
-import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'
+import { ArrowLeftIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'
import { useMutation, useQuery } from '@tanstack/vue-query'
import { useRouteParams } from '@vueuse/router'
import { ref, watch } from 'vue'
@@ -60,6 +60,23 @@ const { mutate, isPending } = useMutation({
+
+
+
+ Zurück zur Veranstaltung
+
+
+
+
+
Reihenfolge ändern
+
+ Hier kann die Reihenfolge benutzerdefinierter Felder der Veranstaltung verändert werden.
+
+
+
@@ -118,8 +135,10 @@ const { mutate, isPending } = useMutation({
Abbrechen
+ Abbrechen
+
From ed57005b6049788a3e836e9b51c44f3975117696 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 4 Mar 2026 22:23:07 +0000
Subject: [PATCH 09/11] chore(deps): bump @hono/node-server from 1.19.7 to
1.19.10
Bumps [@hono/node-server](https://github.com/honojs/node-server) from 1.19.7 to 1.19.10.
- [Release notes](https://github.com/honojs/node-server/releases)
- [Commits](https://github.com/honojs/node-server/compare/v1.19.7...v1.19.10)
---
updated-dependencies:
- dependency-name: "@hono/node-server"
dependency-version: 1.19.10
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
apps/api/package.json | 2 +-
pnpm-lock.yaml | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/apps/api/package.json b/apps/api/package.json
index f3d74cd6..26eb35cf 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -33,7 +33,7 @@
"@codeanker/service-sms": "file:../../vendor/codeanker-service-sms-0.0.2.tar.gz",
"@e965/xlsx": "^0.20.3",
"@faker-js/faker": "catalog:",
- "@hono/node-server": "^1.19.6",
+ "@hono/node-server": "^1.19.10",
"@hono/trpc-server": "^0.4.0",
"@prisma/client": "^5.19.1",
"@prisma/extension-accelerate": "^1.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2726fdbc..e3f779a0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -117,8 +117,8 @@ importers:
specifier: 'catalog:'
version: 10.1.0
'@hono/node-server':
- specifier: ^1.19.6
- version: 1.19.7(hono@4.12.4)
+ specifier: ^1.19.10
+ version: 1.19.10(hono@4.12.4)
'@hono/trpc-server':
specifier: ^0.4.0
version: 0.4.1(@trpc/server@11.0.0-rc.682(typescript@5.7.2))(hono@4.12.4)
@@ -1430,8 +1430,8 @@ packages:
peerDependencies:
vue: '>= 3'
- '@hono/node-server@1.19.7':
- resolution: {integrity: sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==}
+ '@hono/node-server@1.19.10':
+ resolution: {integrity: sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
@@ -6050,7 +6050,7 @@ snapshots:
dependencies:
vue: 3.5.13(typescript@5.7.2)
- '@hono/node-server@1.19.7(hono@4.12.4)':
+ '@hono/node-server@1.19.10(hono@4.12.4)':
dependencies:
hono: 4.12.4
From 7a0779c3932d75cd84134b7bf6e3076ea828b1a3 Mon Sep 17 00:00:00 2001
From: Axel Rindle
Date: Thu, 5 Mar 2026 09:18:21 +0100
Subject: [PATCH 10/11] feat: show notification
---
.../CustomFieldUnterveranstaltungOrder.vue | 15 +++++++++++++++
.../CustomFieldVeranstaltungOrder.vue | 15 +++++++++++++++
2 files changed, 30 insertions(+)
diff --git a/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue b/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
index ae0387af..8cb874a9 100644
--- a/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
+++ b/apps/frontend/src/views/Unterveranstaltung/CustomFields/CustomFieldUnterveranstaltungOrder.vue
@@ -1,5 +1,6 @@