feat: complete CMS v2 with Docker, Fischerei, Meetings, Verband modules + UX audit fixes
Major changes: - Docker Compose: full Supabase stack (11 services) equivalent to supabase CLI - Fischerei module: 16 DB tables, waters/species/stocking/catch books/competitions - Sitzungsprotokolle module: meeting protocols, agenda items, task tracking - Verbandsverwaltung module: federation management, member clubs, contacts, fees - Per-account module activation via Modules page toggle - Site Builder: live CMS data in Puck blocks (courses, events, membership registration) - Public registration APIs: course signup, event registration, membership application - Document generation: PDF member cards, Excel reports, HTML labels - Landing page: real Com.BISS content (no filler text) - UX audit fixes: AccountNotFound component, shared status badges, confirm dialog, pagination, duplicate heading removal, emoji→badge replacement, a11y fixes - QA: healthcheck fix, API auth fix, enum mismatch fix, password required attribute
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
'use server';
|
||||
|
||||
import { z } from 'zod';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { authActionClient } from '@kit/next/safe-action';
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
import {
|
||||
CreateMeetingProtocolSchema,
|
||||
UpdateMeetingProtocolSchema,
|
||||
CreateProtocolItemSchema,
|
||||
UpdateProtocolItemSchema,
|
||||
UpdateItemStatusSchema,
|
||||
ReorderItemsSchema,
|
||||
} from '../../schema/meetings.schema';
|
||||
|
||||
import { createMeetingsApi } from '../api';
|
||||
|
||||
const REVALIDATION_PATH = '/home/[account]/meetings';
|
||||
|
||||
// =====================================================
|
||||
// Protocols
|
||||
// =====================================================
|
||||
|
||||
export const createProtocol = authActionClient
|
||||
.inputSchema(CreateMeetingProtocolSchema)
|
||||
.action(async ({ parsedInput: input, ctx }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
const userId = ctx.user.id;
|
||||
|
||||
logger.info({ name: 'meetings.protocol.create' }, 'Protokoll wird erstellt...');
|
||||
const result = await api.createProtocol(input, userId);
|
||||
logger.info({ name: 'meetings.protocol.create' }, 'Protokoll erstellt');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true, data: result };
|
||||
});
|
||||
|
||||
export const updateProtocol = authActionClient
|
||||
.inputSchema(UpdateMeetingProtocolSchema)
|
||||
.action(async ({ parsedInput: input, ctx }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
const userId = ctx.user.id;
|
||||
|
||||
logger.info({ name: 'meetings.protocol.update' }, 'Protokoll wird aktualisiert...');
|
||||
const result = await api.updateProtocol(input, userId);
|
||||
logger.info({ name: 'meetings.protocol.update' }, 'Protokoll aktualisiert');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true, data: result };
|
||||
});
|
||||
|
||||
export const deleteProtocol = authActionClient
|
||||
.inputSchema(
|
||||
z.object({
|
||||
protocolId: z.string().uuid(),
|
||||
accountId: z.string().uuid(),
|
||||
}),
|
||||
)
|
||||
.action(async ({ parsedInput: input }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
|
||||
logger.info({ name: 'meetings.protocol.delete' }, 'Protokoll wird gelöscht...');
|
||||
await api.deleteProtocol(input.protocolId);
|
||||
logger.info({ name: 'meetings.protocol.delete' }, 'Protokoll gelöscht');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// Protocol Items
|
||||
// =====================================================
|
||||
|
||||
export const createProtocolItem = authActionClient
|
||||
.inputSchema(CreateProtocolItemSchema)
|
||||
.action(async ({ parsedInput: input, ctx }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
const userId = ctx.user.id;
|
||||
|
||||
logger.info({ name: 'meetings.item.create' }, 'Tagesordnungspunkt wird erstellt...');
|
||||
const result = await api.createItem(input, userId);
|
||||
logger.info({ name: 'meetings.item.create' }, 'Tagesordnungspunkt erstellt');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true, data: result };
|
||||
});
|
||||
|
||||
export const updateProtocolItem = authActionClient
|
||||
.inputSchema(UpdateProtocolItemSchema)
|
||||
.action(async ({ parsedInput: input, ctx }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
const userId = ctx.user.id;
|
||||
|
||||
logger.info({ name: 'meetings.item.update' }, 'Tagesordnungspunkt wird aktualisiert...');
|
||||
const result = await api.updateItem(input, userId);
|
||||
logger.info({ name: 'meetings.item.update' }, 'Tagesordnungspunkt aktualisiert');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true, data: result };
|
||||
});
|
||||
|
||||
export const updateItemStatus = authActionClient
|
||||
.inputSchema(UpdateItemStatusSchema)
|
||||
.action(async ({ parsedInput: input, ctx }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
const userId = ctx.user.id;
|
||||
|
||||
logger.info({ name: 'meetings.item.status' }, 'Status wird geändert...');
|
||||
const result = await api.updateItemStatus(input, userId);
|
||||
logger.info({ name: 'meetings.item.status' }, 'Status geändert');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true, data: result };
|
||||
});
|
||||
|
||||
export const deleteProtocolItem = authActionClient
|
||||
.inputSchema(
|
||||
z.object({
|
||||
itemId: z.string().uuid(),
|
||||
}),
|
||||
)
|
||||
.action(async ({ parsedInput: input }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
|
||||
logger.info({ name: 'meetings.item.delete' }, 'Tagesordnungspunkt wird gelöscht...');
|
||||
await api.deleteItem(input.itemId);
|
||||
logger.info({ name: 'meetings.item.delete' }, 'Tagesordnungspunkt gelöscht');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
export const reorderProtocolItems = authActionClient
|
||||
.inputSchema(ReorderItemsSchema)
|
||||
.action(async ({ parsedInput: input }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
|
||||
logger.info({ name: 'meetings.items.reorder' }, 'Reihenfolge wird aktualisiert...');
|
||||
await api.reorderItems(input);
|
||||
logger.info({ name: 'meetings.items.reorder' }, 'Reihenfolge aktualisiert');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// Attachments
|
||||
// =====================================================
|
||||
|
||||
export const addProtocolAttachment = authActionClient
|
||||
.inputSchema(
|
||||
z.object({
|
||||
protocolId: z.string().uuid(),
|
||||
fileName: z.string().min(1),
|
||||
filePath: z.string().min(1),
|
||||
fileSize: z.number().int().positive(),
|
||||
mimeType: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.action(async ({ parsedInput: input, ctx }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
const userId = ctx.user.id;
|
||||
|
||||
logger.info({ name: 'meetings.attachment.add' }, 'Anhang wird hinzugefügt...');
|
||||
const result = await api.addAttachment(
|
||||
input.protocolId,
|
||||
input.fileName,
|
||||
input.filePath,
|
||||
input.fileSize,
|
||||
input.mimeType,
|
||||
userId,
|
||||
);
|
||||
logger.info({ name: 'meetings.attachment.add' }, 'Anhang hinzugefügt');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true, data: result };
|
||||
});
|
||||
|
||||
export const deleteProtocolAttachment = authActionClient
|
||||
.inputSchema(
|
||||
z.object({
|
||||
attachmentId: z.string().uuid(),
|
||||
}),
|
||||
)
|
||||
.action(async ({ parsedInput: input }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const logger = await getLogger();
|
||||
const api = createMeetingsApi(client);
|
||||
|
||||
logger.info({ name: 'meetings.attachment.delete' }, 'Anhang wird gelöscht...');
|
||||
await api.deleteAttachment(input.attachmentId);
|
||||
logger.info({ name: 'meetings.attachment.delete' }, 'Anhang gelöscht');
|
||||
revalidatePath(REVALIDATION_PATH, 'page');
|
||||
return { success: true };
|
||||
});
|
||||
Reference in New Issue
Block a user