refactor: improve code readability and consistency in api.ts and common.json
This commit is contained in:
@@ -1,17 +1,65 @@
|
|||||||
import {
|
import {
|
||||||
CreditCard,
|
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Settings,
|
Settings,
|
||||||
Users,
|
UserCog,
|
||||||
Database,
|
CreditCard,
|
||||||
|
// People (Members + Access)
|
||||||
UserCheck,
|
UserCheck,
|
||||||
|
UserPlus,
|
||||||
|
IdCard,
|
||||||
|
ClipboardList,
|
||||||
|
KeyRound,
|
||||||
|
// Courses
|
||||||
GraduationCap,
|
GraduationCap,
|
||||||
|
CalendarDays,
|
||||||
|
MapPin,
|
||||||
|
UserRound,
|
||||||
|
// Events
|
||||||
|
CalendarHeart,
|
||||||
|
Ticket,
|
||||||
|
PartyPopper,
|
||||||
|
// Bookings
|
||||||
Hotel,
|
Hotel,
|
||||||
Calendar,
|
BedDouble,
|
||||||
|
Contact,
|
||||||
|
CalendarRange,
|
||||||
|
// Finance
|
||||||
Wallet,
|
Wallet,
|
||||||
|
Receipt,
|
||||||
|
Landmark,
|
||||||
|
BarChart3,
|
||||||
|
// Documents
|
||||||
FileText,
|
FileText,
|
||||||
|
FilePlus,
|
||||||
|
FileStack,
|
||||||
|
// Newsletter
|
||||||
Mail,
|
Mail,
|
||||||
Globe,
|
MailPlus,
|
||||||
|
FileCode,
|
||||||
|
// Site Builder
|
||||||
|
PanelTop,
|
||||||
|
Newspaper,
|
||||||
|
Palette,
|
||||||
|
// Fisheries
|
||||||
|
Fish,
|
||||||
|
Waves,
|
||||||
|
Anchor,
|
||||||
|
BookOpen,
|
||||||
|
ShieldCheck,
|
||||||
|
Trophy,
|
||||||
|
// Meetings
|
||||||
|
BookMarked,
|
||||||
|
ListChecks,
|
||||||
|
ScrollText,
|
||||||
|
// Association (Verband)
|
||||||
|
Building2,
|
||||||
|
Network,
|
||||||
|
SearchCheck,
|
||||||
|
Share2,
|
||||||
|
PieChart,
|
||||||
|
LayoutTemplate,
|
||||||
|
// Modules
|
||||||
|
Database,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
|
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
|
||||||
@@ -21,101 +69,499 @@ import pathsConfig from '~/config/paths.config';
|
|||||||
|
|
||||||
const iconClasses = 'w-4';
|
const iconClasses = 'w-4';
|
||||||
|
|
||||||
const getRoutes = (account: string) => [
|
const getRoutes = (account: string) => {
|
||||||
|
const routes: Array<
|
||||||
|
| {
|
||||||
|
label: string;
|
||||||
|
collapsible?: boolean;
|
||||||
|
collapsed?: boolean;
|
||||||
|
children: Array<
|
||||||
|
| {
|
||||||
|
label: string;
|
||||||
|
path: string;
|
||||||
|
Icon: React.ReactNode;
|
||||||
|
highlightMatch?: string;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
| { divider: true }
|
||||||
|
> = [
|
||||||
|
// ── Dashboard ──
|
||||||
{
|
{
|
||||||
label: 'common.routes.application',
|
label: 'common:routes.dashboard',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: 'common.routes.dashboard',
|
label: 'common:routes.dashboard',
|
||||||
path: pathsConfig.app.accountHome.replace('[account]', account),
|
path: pathsConfig.app.accountHome.replace('[account]', account),
|
||||||
Icon: <LayoutDashboard className={iconClasses} />,
|
Icon: <LayoutDashboard className={iconClasses} />,
|
||||||
highlightMatch: `${pathsConfig.app.home}$`,
|
highlightMatch: `${pathsConfig.app.home}$`,
|
||||||
},
|
},
|
||||||
featureFlagsConfig.enableModuleBuilder
|
],
|
||||||
? {
|
},
|
||||||
label: 'common.routes.modules',
|
];
|
||||||
path: createPath(pathsConfig.app.accountModules, account),
|
|
||||||
Icon: <Database className={iconClasses} />,
|
// ── People (Members + Access) ──
|
||||||
}
|
{
|
||||||
: undefined,
|
const peopleChildren: Array<{
|
||||||
featureFlagsConfig.enableMemberManagement
|
label: string;
|
||||||
? {
|
path: string;
|
||||||
label: 'common.routes.cmsMembers',
|
Icon: React.ReactNode;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
if (featureFlagsConfig.enableMemberManagement) {
|
||||||
|
peopleChildren.push(
|
||||||
|
{
|
||||||
|
label: 'common:routes.clubMembers',
|
||||||
path: createPath(pathsConfig.app.accountCmsMembers, account),
|
path: createPath(pathsConfig.app.accountCmsMembers, account),
|
||||||
Icon: <UserCheck className={iconClasses} />,
|
Icon: <UserCheck className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.memberApplications',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountCmsMembers + '/applications',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <UserPlus className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.memberPortal',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountCmsMembers + '/portal',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <KeyRound className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.memberCards',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountCmsMembers + '/cards',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <IdCard className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.memberDues',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountCmsMembers + '/dues',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <ClipboardList className={iconClasses} />,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
: undefined,
|
|
||||||
featureFlagsConfig.enableCourseManagement
|
// Admin users who can log in — always visible
|
||||||
? {
|
peopleChildren.push({
|
||||||
label: 'common.routes.courses',
|
label: 'common:routes.accessAndRoles',
|
||||||
|
path: createPath(pathsConfig.app.accountMembers, account),
|
||||||
|
Icon: <UserCog className={iconClasses} />,
|
||||||
|
});
|
||||||
|
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.people',
|
||||||
|
collapsible: true,
|
||||||
|
children: peopleChildren,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Courses ──
|
||||||
|
if (featureFlagsConfig.enableCourseManagement) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.courseManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.courseList',
|
||||||
path: createPath(pathsConfig.app.accountCourses, account),
|
path: createPath(pathsConfig.app.accountCourses, account),
|
||||||
Icon: <GraduationCap className={iconClasses} />,
|
Icon: <GraduationCap className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.courseCalendar',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountCourses + '/calendar',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <CalendarDays className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.courseInstructors',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountCourses + '/instructors',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <UserRound className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.courseLocations',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountCourses + '/locations',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <MapPin className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
: undefined,
|
|
||||||
featureFlagsConfig.enableBookingManagement
|
// ── Events ──
|
||||||
? {
|
routes.push({
|
||||||
label: 'common.routes.bookings',
|
label: 'common:routes.eventManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.eventList',
|
||||||
|
path: createPath('/home/[account]/events', account),
|
||||||
|
Icon: <CalendarHeart className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.eventRegistrations',
|
||||||
|
path: createPath('/home/[account]/events/registrations', account),
|
||||||
|
Icon: <Ticket className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.holidayPasses',
|
||||||
|
path: createPath('/home/[account]/events/holiday-passes', account),
|
||||||
|
Icon: <PartyPopper className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Bookings ──
|
||||||
|
if (featureFlagsConfig.enableBookingManagement) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.bookingManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.bookingList',
|
||||||
path: createPath(pathsConfig.app.accountBookings, account),
|
path: createPath(pathsConfig.app.accountBookings, account),
|
||||||
Icon: <Hotel className={iconClasses} />,
|
Icon: <Hotel className={iconClasses} />,
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
{
|
|
||||||
label: 'common.routes.events',
|
|
||||||
path: createPath(`/home/[account]/events`, account),
|
|
||||||
Icon: <Calendar className={iconClasses} />,
|
|
||||||
},
|
},
|
||||||
featureFlagsConfig.enableSepaPayments
|
{
|
||||||
? {
|
label: 'common:routes.bookingCalendar',
|
||||||
label: 'common.routes.finance',
|
path: createPath(
|
||||||
|
pathsConfig.app.accountBookings + '/calendar',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <CalendarRange className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.bookingRooms',
|
||||||
|
path: createPath(pathsConfig.app.accountBookings + '/rooms', account),
|
||||||
|
Icon: <BedDouble className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.bookingGuests',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountBookings + '/guests',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Contact className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Finance ──
|
||||||
|
if (featureFlagsConfig.enableSepaPayments) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.financeManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.financeOverview',
|
||||||
path: createPath(pathsConfig.app.accountFinance, account),
|
path: createPath(pathsConfig.app.accountFinance, account),
|
||||||
Icon: <Wallet className={iconClasses} />,
|
Icon: <Wallet className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.financeInvoices',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountFinance + '/invoices',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Receipt className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.financeSepa',
|
||||||
|
path: createPath(pathsConfig.app.accountFinance + '/sepa', account),
|
||||||
|
Icon: <Landmark className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.financePayments',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountFinance + '/payments',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <BarChart3 className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
: undefined,
|
|
||||||
featureFlagsConfig.enableDocumentGeneration
|
// ── Documents ──
|
||||||
? {
|
if (featureFlagsConfig.enableDocumentGeneration) {
|
||||||
label: 'common.routes.documents',
|
routes.push({
|
||||||
|
label: 'common:routes.documentManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.documentOverview',
|
||||||
path: createPath(pathsConfig.app.accountDocuments, account),
|
path: createPath(pathsConfig.app.accountDocuments, account),
|
||||||
Icon: <FileText className={iconClasses} />,
|
Icon: <FileText className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.documentGenerate',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountDocuments + '/generate',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <FilePlus className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.documentTemplates',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountDocuments + '/templates',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <FileStack className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
: undefined,
|
|
||||||
featureFlagsConfig.enableNewsletter
|
// ── Newsletter ──
|
||||||
? {
|
if (featureFlagsConfig.enableNewsletter) {
|
||||||
label: 'common.routes.newsletter',
|
routes.push({
|
||||||
|
label: 'common:routes.newsletterManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.newsletterCampaigns',
|
||||||
path: createPath(pathsConfig.app.accountNewsletter, account),
|
path: createPath(pathsConfig.app.accountNewsletter, account),
|
||||||
Icon: <Mail className={iconClasses} />,
|
Icon: <Mail className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.newsletterNew',
|
||||||
|
path: createPath(pathsConfig.app.accountNewsletter + '/new', account),
|
||||||
|
Icon: <MailPlus className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.newsletterTemplates',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountNewsletter + '/templates',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <FileCode className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
: undefined,
|
|
||||||
|
// ── Site Builder ──
|
||||||
|
if (featureFlagsConfig.enableSiteBuilder) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.siteBuilder',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
label: 'common.routes.siteBuilder',
|
label: 'common:routes.sitePages',
|
||||||
path: createPath(`/home/[account]/site-builder`, account),
|
path: createPath(pathsConfig.app.accountSiteBuilder, account),
|
||||||
Icon: <Globe className={iconClasses} />,
|
Icon: <PanelTop className={iconClasses} />,
|
||||||
},
|
|
||||||
].filter(Boolean),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'common.routes.settings',
|
label: 'common:routes.sitePosts',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountSiteBuilder + '/posts',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Newspaper className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.siteSettings',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountSiteBuilder + '/settings',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Palette className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Custom Modules ──
|
||||||
|
if (featureFlagsConfig.enableModuleBuilder) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.customModules',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.moduleList',
|
||||||
|
path: createPath(pathsConfig.app.accountModules, account),
|
||||||
|
Icon: <Database className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Fisheries ──
|
||||||
|
if (featureFlagsConfig.enableFischerei) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.fisheriesManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.fisheriesOverview',
|
||||||
|
path: createPath(pathsConfig.app.accountFischerei, account),
|
||||||
|
Icon: <Fish className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.fisheriesWaters',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountFischerei + '/waters',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Waves className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.fisheriesLeases',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountFischerei + '/leases',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Anchor className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.fisheriesCatchBooks',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountFischerei + '/catch-books',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <BookOpen className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.fisheriesPermits',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountFischerei + '/permits',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <ShieldCheck className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.fisheriesCompetitions',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountFischerei + '/competitions',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Trophy className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Meeting Protocols ──
|
||||||
|
if (featureFlagsConfig.enableMeetingProtocols) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.meetingProtocols',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.meetingsOverview',
|
||||||
|
path: createPath(pathsConfig.app.accountMeetings, account),
|
||||||
|
Icon: <BookMarked className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.meetingsProtocols',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountMeetings + '/protocols',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <ScrollText className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.meetingsTasks',
|
||||||
|
path: createPath(pathsConfig.app.accountMeetings + '/tasks', account),
|
||||||
|
Icon: <ListChecks className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Association Management (Verband) ──
|
||||||
|
if (featureFlagsConfig.enableVerbandsverwaltung) {
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.associationManagement',
|
||||||
|
collapsible: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'common:routes.associationOverview',
|
||||||
|
path: createPath(pathsConfig.app.accountVerband, account),
|
||||||
|
Icon: <Building2 className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.associationHierarchy',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountVerband + '/hierarchy',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <Network className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.associationMemberSearch',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountVerband + '/members',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <SearchCheck className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.associationEvents',
|
||||||
|
path: createPath(pathsConfig.app.accountVerband + '/events', account),
|
||||||
|
Icon: <Share2 className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.associationReporting',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountVerband + '/reporting',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <PieChart className={iconClasses} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common:routes.associationTemplates',
|
||||||
|
path: createPath(
|
||||||
|
pathsConfig.app.accountVerband + '/templates',
|
||||||
|
account,
|
||||||
|
),
|
||||||
|
Icon: <LayoutTemplate className={iconClasses} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Administration ──
|
||||||
|
routes.push({
|
||||||
|
label: 'common:routes.administration',
|
||||||
collapsible: false,
|
collapsible: false,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: 'common.routes.settings',
|
label: 'common:routes.accountSettings',
|
||||||
path: createPath(pathsConfig.app.accountSettings, account),
|
path: createPath(pathsConfig.app.accountSettings, account),
|
||||||
Icon: <Settings className={iconClasses} />,
|
Icon: <Settings className={iconClasses} />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'common.routes.members',
|
|
||||||
path: createPath(pathsConfig.app.accountMembers, account),
|
|
||||||
Icon: <Users className={iconClasses} />,
|
|
||||||
},
|
|
||||||
featureFlagsConfig.enableTeamAccountBilling
|
featureFlagsConfig.enableTeamAccountBilling
|
||||||
? {
|
? {
|
||||||
label: 'common.routes.billing',
|
label: 'common:routes.billing',
|
||||||
path: createPath(pathsConfig.app.accountBilling, account),
|
path: createPath(pathsConfig.app.accountBilling, account),
|
||||||
Icon: <CreditCard className={iconClasses} />,
|
Icon: <CreditCard className={iconClasses} />,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
].filter(Boolean),
|
],
|
||||||
},
|
});
|
||||||
];
|
|
||||||
|
return routes;
|
||||||
|
};
|
||||||
|
|
||||||
export function getTeamAccountSidebarConfig(account: string) {
|
export function getTeamAccountSidebarConfig(account: string) {
|
||||||
return NavigationConfigSchema.parse({
|
return NavigationConfigSchema.parse({
|
||||||
|
|||||||
@@ -61,24 +61,83 @@
|
|||||||
"routes": {
|
"routes": {
|
||||||
"home": "Startseite",
|
"home": "Startseite",
|
||||||
"account": "Konto",
|
"account": "Konto",
|
||||||
"members": "Mitglieder",
|
|
||||||
"billing": "Abrechnung",
|
"billing": "Abrechnung",
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"application": "Anwendung",
|
|
||||||
"modules": "Module",
|
"people": "Personen",
|
||||||
"cmsMembers": "Mitglieder",
|
"clubMembers": "Vereinsmitglieder",
|
||||||
"courses": "Kurse",
|
"memberApplications": "Aufnahmeanträge",
|
||||||
"bookings": "Buchungen",
|
"memberPortal": "Mitgliederportal",
|
||||||
"finance": "Finanzen",
|
"memberCards": "Mitgliedsausweise",
|
||||||
"documents": "Dokumente",
|
"memberDues": "Beitragskategorien",
|
||||||
"newsletter": "Newsletter",
|
"accessAndRoles": "Zugänge & Rollen",
|
||||||
"events": "Veranstaltungen",
|
|
||||||
|
"courseManagement": "Kursverwaltung",
|
||||||
|
"courseList": "Alle Kurse",
|
||||||
|
"courseCalendar": "Kurskalender",
|
||||||
|
"courseInstructors": "Kursleiter",
|
||||||
|
"courseLocations": "Standorte",
|
||||||
|
|
||||||
|
"eventManagement": "Veranstaltungen",
|
||||||
|
"eventList": "Alle Veranstaltungen",
|
||||||
|
"eventRegistrations": "Anmeldungen",
|
||||||
|
"holidayPasses": "Ferienpässe",
|
||||||
|
|
||||||
|
"bookingManagement": "Buchungsverwaltung",
|
||||||
|
"bookingList": "Alle Buchungen",
|
||||||
|
"bookingCalendar": "Belegungskalender",
|
||||||
|
"bookingRooms": "Zimmer",
|
||||||
|
"bookingGuests": "Gäste",
|
||||||
|
|
||||||
|
"financeManagement": "Finanzen",
|
||||||
|
"financeOverview": "Übersicht",
|
||||||
|
"financeInvoices": "Rechnungen",
|
||||||
|
"financeSepa": "SEPA-Einzüge",
|
||||||
|
"financePayments": "Zahlungen",
|
||||||
|
|
||||||
|
"documentManagement": "Dokumente",
|
||||||
|
"documentOverview": "Übersicht",
|
||||||
|
"documentGenerate": "Generieren",
|
||||||
|
"documentTemplates": "Vorlagen",
|
||||||
|
|
||||||
|
"newsletterManagement": "Newsletter",
|
||||||
|
"newsletterCampaigns": "Kampagnen",
|
||||||
|
"newsletterNew": "Neuer Newsletter",
|
||||||
|
"newsletterTemplates": "Vorlagen",
|
||||||
|
|
||||||
"siteBuilder": "Website",
|
"siteBuilder": "Website",
|
||||||
"fischerei": "Fischerei",
|
"sitePages": "Seiten",
|
||||||
"meetings": "Sitzungsprotokolle",
|
"sitePosts": "Beiträge",
|
||||||
"verband": "Verbandsverwaltung"
|
"siteSettings": "Einstellungen",
|
||||||
|
|
||||||
|
"customModules": "Benutzerdefinierte Module",
|
||||||
|
"moduleList": "Alle Module",
|
||||||
|
|
||||||
|
"fisheriesManagement": "Fischerei",
|
||||||
|
"fisheriesOverview": "Übersicht",
|
||||||
|
"fisheriesWaters": "Gewässer",
|
||||||
|
"fisheriesLeases": "Pachten",
|
||||||
|
"fisheriesCatchBooks": "Fangbücher",
|
||||||
|
"fisheriesPermits": "Erlaubnisscheine",
|
||||||
|
"fisheriesCompetitions": "Wettbewerbe",
|
||||||
|
|
||||||
|
"meetingProtocols": "Sitzungsprotokolle",
|
||||||
|
"meetingsOverview": "Übersicht",
|
||||||
|
"meetingsProtocols": "Protokolle",
|
||||||
|
"meetingsTasks": "Offene Aufgaben",
|
||||||
|
|
||||||
|
"associationManagement": "Verbandsverwaltung",
|
||||||
|
"associationOverview": "Übersicht",
|
||||||
|
"associationHierarchy": "Organisationsstruktur",
|
||||||
|
"associationMemberSearch": "Verbandsweite Suche",
|
||||||
|
"associationEvents": "Geteilte Veranstaltungen",
|
||||||
|
"associationReporting": "Berichte",
|
||||||
|
"associationTemplates": "Geteilte Vorlagen",
|
||||||
|
|
||||||
|
"administration": "Administration",
|
||||||
|
"accountSettings": "Kontoeinstellungen"
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"owner": {
|
"owner": {
|
||||||
|
|||||||
@@ -61,24 +61,83 @@
|
|||||||
"routes": {
|
"routes": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"members": "Members",
|
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"application": "Application",
|
|
||||||
"modules": "Modules",
|
"people": "People",
|
||||||
"cmsMembers": "Members",
|
"clubMembers": "Club Members",
|
||||||
"courses": "Courses",
|
"memberApplications": "Applications",
|
||||||
"bookings": "Bookings",
|
"memberPortal": "Member Portal",
|
||||||
"events": "Events",
|
"memberCards": "Member Cards",
|
||||||
|
"memberDues": "Dues Categories",
|
||||||
|
"accessAndRoles": "Access & Roles",
|
||||||
|
|
||||||
|
"courseManagement": "Courses",
|
||||||
|
"courseList": "All Courses",
|
||||||
|
"courseCalendar": "Calendar",
|
||||||
|
"courseInstructors": "Instructors",
|
||||||
|
"courseLocations": "Locations",
|
||||||
|
|
||||||
|
"eventManagement": "Events",
|
||||||
|
"eventList": "All Events",
|
||||||
|
"eventRegistrations": "Registrations",
|
||||||
|
"holidayPasses": "Holiday Passes",
|
||||||
|
|
||||||
|
"bookingManagement": "Bookings",
|
||||||
|
"bookingList": "All Bookings",
|
||||||
|
"bookingCalendar": "Availability Calendar",
|
||||||
|
"bookingRooms": "Rooms",
|
||||||
|
"bookingGuests": "Guests",
|
||||||
|
|
||||||
|
"financeManagement": "Finance",
|
||||||
|
"financeOverview": "Overview",
|
||||||
|
"financeInvoices": "Invoices",
|
||||||
|
"financeSepa": "SEPA Batches",
|
||||||
|
"financePayments": "Payments",
|
||||||
|
|
||||||
|
"documentManagement": "Documents",
|
||||||
|
"documentOverview": "Overview",
|
||||||
|
"documentGenerate": "Generate",
|
||||||
|
"documentTemplates": "Templates",
|
||||||
|
|
||||||
|
"newsletterManagement": "Newsletter",
|
||||||
|
"newsletterCampaigns": "Campaigns",
|
||||||
|
"newsletterNew": "New Newsletter",
|
||||||
|
"newsletterTemplates": "Templates",
|
||||||
|
|
||||||
"siteBuilder": "Website",
|
"siteBuilder": "Website",
|
||||||
"finance": "Finance",
|
"sitePages": "Pages",
|
||||||
"documents": "Documents",
|
"sitePosts": "Posts",
|
||||||
"newsletter": "Newsletter",
|
"siteSettings": "Settings",
|
||||||
"fischerei": "Fisheries",
|
|
||||||
"meetings": "Meeting Protocols",
|
"customModules": "Custom Modules",
|
||||||
"verband": "Federation Management"
|
"moduleList": "All Modules",
|
||||||
|
|
||||||
|
"fisheriesManagement": "Fisheries",
|
||||||
|
"fisheriesOverview": "Overview",
|
||||||
|
"fisheriesWaters": "Waters",
|
||||||
|
"fisheriesLeases": "Leases",
|
||||||
|
"fisheriesCatchBooks": "Catch Books",
|
||||||
|
"fisheriesPermits": "Permits",
|
||||||
|
"fisheriesCompetitions": "Competitions",
|
||||||
|
|
||||||
|
"meetingProtocols": "Meeting Protocols",
|
||||||
|
"meetingsOverview": "Overview",
|
||||||
|
"meetingsProtocols": "Protocols",
|
||||||
|
"meetingsTasks": "Open Tasks",
|
||||||
|
|
||||||
|
"associationManagement": "Association Management",
|
||||||
|
"associationOverview": "Overview",
|
||||||
|
"associationHierarchy": "Organization Structure",
|
||||||
|
"associationMemberSearch": "Association-wide Search",
|
||||||
|
"associationEvents": "Shared Events",
|
||||||
|
"associationReporting": "Reports",
|
||||||
|
"associationTemplates": "Shared Templates",
|
||||||
|
|
||||||
|
"administration": "Administration",
|
||||||
|
"accountSettings": "Account Settings"
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"owner": {
|
"owner": {
|
||||||
|
|||||||
@@ -165,7 +165,9 @@ async function getPatterns() {
|
|||||||
} catch {
|
} catch {
|
||||||
// Supabase unreachable — redirect to sign in
|
// Supabase unreachable — redirect to sign in
|
||||||
const signIn = pathsConfig.auth.signIn;
|
const signIn = pathsConfig.auth.signIn;
|
||||||
return NextResponse.redirect(new URL(signIn, req.nextUrl.origin).href);
|
return NextResponse.redirect(
|
||||||
|
new URL(signIn, req.nextUrl.origin).href,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { origin, pathname: next } = req.nextUrl;
|
const { origin, pathname: next } = req.nextUrl;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_DB: postgres
|
POSTGRES_DB: postgres
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
test: ['CMD-SHELL', 'pg_isready -U postgres -d postgres']
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -49,7 +49,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PGPASSWORD: ${POSTGRES_PASSWORD}
|
PGPASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
entrypoint: ["/bin/sh", "-c"]
|
entrypoint: ['/bin/sh', '-c']
|
||||||
command:
|
command:
|
||||||
- |
|
- |
|
||||||
echo "🔑 Ensuring role passwords are set (idempotent)..."
|
echo "🔑 Ensuring role passwords are set (idempotent)..."
|
||||||
@@ -63,7 +63,7 @@ services:
|
|||||||
echo "✅ App migrations complete."
|
echo "✅ App migrations complete."
|
||||||
echo ""
|
echo ""
|
||||||
sh /app-seed/dev-bootstrap.sh
|
sh /app-seed/dev-bootstrap.sh
|
||||||
restart: "no"
|
restart: 'no'
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# Supabase Auth (GoTrue)
|
# Supabase Auth (GoTrue)
|
||||||
@@ -102,7 +102,15 @@ services:
|
|||||||
GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/v1/verify
|
GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/v1/verify
|
||||||
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
|
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health"]
|
test:
|
||||||
|
[
|
||||||
|
'CMD',
|
||||||
|
'wget',
|
||||||
|
'--no-verbose',
|
||||||
|
'--tries=1',
|
||||||
|
'--spider',
|
||||||
|
'http://localhost:9999/health',
|
||||||
|
]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -123,9 +131,9 @@ services:
|
|||||||
PGRST_DB_SCHEMAS: public,storage,graphql_public
|
PGRST_DB_SCHEMAS: public,storage,graphql_public
|
||||||
PGRST_DB_ANON_ROLE: anon
|
PGRST_DB_ANON_ROLE: anon
|
||||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||||
PGRST_DB_USE_LEGACY_GUCS: "false"
|
PGRST_DB_USE_LEGACY_GUCS: 'false'
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "head -c0 </dev/tcp/localhost/3000 || exit 1"]
|
test: ['CMD-SHELL', 'head -c0 </dev/tcp/localhost/3000 || exit 1']
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -149,20 +157,20 @@ services:
|
|||||||
DB_USER: supabase_admin
|
DB_USER: supabase_admin
|
||||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
DB_NAME: postgres
|
DB_NAME: postgres
|
||||||
DB_AFTER_CONNECT_QUERY: "SET search_path TO _realtime"
|
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
|
||||||
DB_ENC_KEY: supabaserealtime
|
DB_ENC_KEY: supabaserealtime
|
||||||
API_JWT_SECRET: ${JWT_SECRET}
|
API_JWT_SECRET: ${JWT_SECRET}
|
||||||
SECRET_KEY_BASE: ${SECRET_KEY_BASE:-UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq}
|
SECRET_KEY_BASE: ${SECRET_KEY_BASE:-UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq}
|
||||||
ERL_AFLAGS: "-proto_dist inet_tcp"
|
ERL_AFLAGS: '-proto_dist inet_tcp'
|
||||||
DNS_NODES: "''"
|
DNS_NODES: "''"
|
||||||
RLIMIT_NOFILE: "10000"
|
RLIMIT_NOFILE: '10000'
|
||||||
APP_NAME: realtime
|
APP_NAME: realtime
|
||||||
SEED_SELF_HOST: "true"
|
SEED_SELF_HOST: 'true'
|
||||||
REPLICATION_MODE: RLS
|
REPLICATION_MODE: RLS
|
||||||
REPLICATION_POLL_INTERVAL: 100
|
REPLICATION_POLL_INTERVAL: 100
|
||||||
SECURE_CHANNELS: "true"
|
SECURE_CHANNELS: 'true'
|
||||||
SLOT_NAME: supabase_realtime_rls
|
SLOT_NAME: supabase_realtime_rls
|
||||||
TEMPORARY_SLOT: "true"
|
TEMPORARY_SLOT: 'true'
|
||||||
MAX_RECORD_BYTES: 1048576
|
MAX_RECORD_BYTES: 1048576
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
@@ -196,7 +204,11 @@ services:
|
|||||||
GLOBAL_S3_BUCKET: stub
|
GLOBAL_S3_BUCKET: stub
|
||||||
IMGPROXY_URL: http://supabase-imgproxy:8080
|
IMGPROXY_URL: http://supabase-imgproxy:8080
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:5000/status || exit 1"]
|
test:
|
||||||
|
[
|
||||||
|
'CMD-SHELL',
|
||||||
|
'wget --no-verbose --tries=1 --spider http://localhost:5000/status || exit 1',
|
||||||
|
]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -209,10 +221,10 @@ services:
|
|||||||
image: darthsim/imgproxy:v3.8.0
|
image: darthsim/imgproxy:v3.8.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
IMGPROXY_BIND: ":8080"
|
IMGPROXY_BIND: ':8080'
|
||||||
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
|
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
|
||||||
IMGPROXY_USE_ETAG: "true"
|
IMGPROXY_USE_ETAG: 'true'
|
||||||
IMGPROXY_ENABLE_WEBP_DETECTION: "true"
|
IMGPROXY_ENABLE_WEBP_DETECTION: 'true'
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# Supabase pg_meta (DB introspection for Studio)
|
# Supabase pg_meta (DB introspection for Studio)
|
||||||
@@ -252,10 +264,16 @@ services:
|
|||||||
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
|
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
|
||||||
SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
|
SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
|
||||||
AUTH_JWT_SECRET: ${JWT_SECRET}
|
AUTH_JWT_SECRET: ${JWT_SECRET}
|
||||||
NEXT_PUBLIC_ENABLE_LOGS: "true"
|
NEXT_PUBLIC_ENABLE_LOGS: 'true'
|
||||||
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
|
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"]
|
test:
|
||||||
|
[
|
||||||
|
'CMD',
|
||||||
|
'node',
|
||||||
|
'-e',
|
||||||
|
"require('http').get('http://localhost:3000/api/profile', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))",
|
||||||
|
]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -284,7 +302,7 @@ services:
|
|||||||
entrypoint: >
|
entrypoint: >
|
||||||
sh -c "sed 's|\$${SUPABASE_ANON_KEY}|'\"$$SUPABASE_ANON_KEY\"'|g; s|\$${SUPABASE_SERVICE_KEY}|'\"$$SUPABASE_SERVICE_KEY\"'|g' /var/lib/kong/kong.yml.tpl > /tmp/kong.yml && KONG_DECLARATIVE_CONFIG=/tmp/kong.yml /docker-entrypoint.sh kong docker-start"
|
sh -c "sed 's|\$${SUPABASE_ANON_KEY}|'\"$$SUPABASE_ANON_KEY\"'|g; s|\$${SUPABASE_SERVICE_KEY}|'\"$$SUPABASE_SERVICE_KEY\"'|g' /var/lib/kong/kong.yml.tpl > /tmp/kong.yml && KONG_DECLARATIVE_CONFIG=/tmp/kong.yml /docker-entrypoint.sh kong docker-start"
|
||||||
environment:
|
environment:
|
||||||
KONG_DATABASE: "off"
|
KONG_DATABASE: 'off'
|
||||||
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
|
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
|
||||||
KONG_DNS_ORDER: LAST,A,CNAME
|
KONG_DNS_ORDER: LAST,A,CNAME
|
||||||
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
|
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
|
||||||
@@ -295,7 +313,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./docker/kong.yml:/var/lib/kong/kong.yml.tpl:ro
|
- ./docker/kong.yml:/var/lib/kong/kong.yml.tpl:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "kong", "health"]
|
test: ['CMD', 'kong', 'health']
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -329,15 +347,15 @@ services:
|
|||||||
SUPABASE_DB_WEBHOOK_SECRET: ${DB_WEBHOOK_SECRET:-webhooksecret}
|
SUPABASE_DB_WEBHOOK_SECRET: ${DB_WEBHOOK_SECRET:-webhooksecret}
|
||||||
EMAIL_SENDER: ${EMAIL_SENDER:-noreply@myeasycms.de}
|
EMAIL_SENDER: ${EMAIL_SENDER:-noreply@myeasycms.de}
|
||||||
NEXT_PUBLIC_PRODUCT_NAME: MyEasyCMS
|
NEXT_PUBLIC_PRODUCT_NAME: MyEasyCMS
|
||||||
NEXT_PUBLIC_ENABLE_THEME_TOGGLE: "true"
|
NEXT_PUBLIC_ENABLE_THEME_TOGGLE: 'true'
|
||||||
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS: "true"
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS: 'true'
|
||||||
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION: "true"
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION: 'true'
|
||||||
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING: "false"
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING: 'false'
|
||||||
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING: "false"
|
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING: 'false'
|
||||||
NEXT_PUBLIC_ENABLE_NOTIFICATIONS: "true"
|
NEXT_PUBLIC_ENABLE_NOTIFICATIONS: 'true'
|
||||||
NEXT_PUBLIC_ENABLE_FISCHEREI: "true"
|
NEXT_PUBLIC_ENABLE_FISCHEREI: 'true'
|
||||||
NEXT_PUBLIC_ENABLE_MEETING_PROTOCOLS: "true"
|
NEXT_PUBLIC_ENABLE_MEETING_PROTOCOLS: 'true'
|
||||||
NEXT_PUBLIC_ENABLE_VERBANDSVERWALTUNG: "true"
|
NEXT_PUBLIC_ENABLE_VERBANDSVERWALTUNG: 'true'
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
supabase-db-data:
|
supabase-db-data:
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import type { Database } from '@kit/supabase/database';
|
|
||||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
import type { CreateCourseInput, EnrollParticipantInput } from '../schema/course.schema';
|
import type { Database } from '@kit/supabase/database';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CreateCourseInput,
|
||||||
|
EnrollParticipantInput,
|
||||||
|
} from '../schema/course.schema';
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
@@ -10,11 +14,25 @@ export function createCourseManagementApi(client: SupabaseClient<Database>) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
// --- Courses ---
|
// --- Courses ---
|
||||||
async listCourses(accountId: string, opts?: { status?: string; search?: string; page?: number; pageSize?: number }) {
|
async listCourses(
|
||||||
let query = client.from('courses').select('*', { count: 'exact' })
|
accountId: string,
|
||||||
.eq('account_id', accountId).order('start_date', { ascending: false });
|
opts?: {
|
||||||
|
status?: string;
|
||||||
|
search?: string;
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
let query = client
|
||||||
|
.from('courses')
|
||||||
|
.select('*', { count: 'exact' })
|
||||||
|
.eq('account_id', accountId)
|
||||||
|
.order('start_date', { ascending: false });
|
||||||
if (opts?.status) query = query.eq('status', opts.status);
|
if (opts?.status) query = query.eq('status', opts.status);
|
||||||
if (opts?.search) query = query.or(`name.ilike.%${opts.search}%,course_number.ilike.%${opts.search}%`);
|
if (opts?.search)
|
||||||
|
query = query.or(
|
||||||
|
`name.ilike.%${opts.search}%,course_number.ilike.%${opts.search}%`,
|
||||||
|
);
|
||||||
const page = opts?.page ?? 1;
|
const page = opts?.page ?? 1;
|
||||||
const pageSize = opts?.pageSize ?? 25;
|
const pageSize = opts?.pageSize ?? 25;
|
||||||
query = query.range((page - 1) * pageSize, page * pageSize - 1);
|
query = query.range((page - 1) * pageSize, page * pageSize - 1);
|
||||||
@@ -24,20 +42,38 @@ export function createCourseManagementApi(client: SupabaseClient<Database>) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async getCourse(courseId: string) {
|
async getCourse(courseId: string) {
|
||||||
const { data, error } = await client.from('courses').select('*').eq('id', courseId).single();
|
const { data, error } = await client
|
||||||
|
.from('courses')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', courseId)
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async createCourse(input: CreateCourseInput) {
|
async createCourse(input: CreateCourseInput) {
|
||||||
const { data, error } = await client.from('courses').insert({
|
const { data, error } = await client
|
||||||
account_id: input.accountId, course_number: input.courseNumber || null, name: input.name,
|
.from('courses')
|
||||||
description: input.description || null, category_id: input.categoryId || null, instructor_id: input.instructorId || null,
|
.insert({
|
||||||
location_id: input.locationId || null, start_date: input.startDate || null, end_date: input.endDate || null,
|
account_id: input.accountId,
|
||||||
fee: input.fee, reduced_fee: input.reducedFee ?? null, capacity: input.capacity,
|
course_number: input.courseNumber || null,
|
||||||
min_participants: input.minParticipants, status: input.status,
|
name: input.name,
|
||||||
registration_deadline: input.registrationDeadline || null, notes: input.notes || null,
|
description: input.description || null,
|
||||||
}).select().single();
|
category_id: input.categoryId || null,
|
||||||
|
instructor_id: input.instructorId || null,
|
||||||
|
location_id: input.locationId || null,
|
||||||
|
start_date: input.startDate || null,
|
||||||
|
end_date: input.endDate || null,
|
||||||
|
fee: input.fee,
|
||||||
|
reduced_fee: input.reducedFee ?? null,
|
||||||
|
capacity: input.capacity,
|
||||||
|
min_participants: input.minParticipants,
|
||||||
|
status: input.status,
|
||||||
|
registration_deadline: input.registrationDeadline || null,
|
||||||
|
notes: input.notes || null,
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
@@ -45,96 +81,161 @@ export function createCourseManagementApi(client: SupabaseClient<Database>) {
|
|||||||
// --- Enrollment ---
|
// --- Enrollment ---
|
||||||
async enrollParticipant(input: EnrollParticipantInput) {
|
async enrollParticipant(input: EnrollParticipantInput) {
|
||||||
// Check capacity
|
// Check capacity
|
||||||
const { count } = await client.from('course_participants').select('*', { count: 'exact', head: true })
|
const { count } = await client
|
||||||
.eq('course_id', input.courseId).in('status', ['enrolled']);
|
.from('course_participants')
|
||||||
|
.select('*', { count: 'exact', head: true })
|
||||||
|
.eq('course_id', input.courseId)
|
||||||
|
.in('status', ['enrolled']);
|
||||||
const course = await this.getCourse(input.courseId);
|
const course = await this.getCourse(input.courseId);
|
||||||
const status = (count ?? 0) >= course.capacity ? 'waitlisted' : 'enrolled';
|
const status =
|
||||||
|
(count ?? 0) >= course.capacity ? 'waitlisted' : 'enrolled';
|
||||||
|
|
||||||
const { data, error } = await client.from('course_participants').insert({
|
const { data, error } = await client
|
||||||
course_id: input.courseId, member_id: input.memberId,
|
.from('course_participants')
|
||||||
first_name: input.firstName, last_name: input.lastName,
|
.insert({
|
||||||
email: input.email, phone: input.phone, status,
|
course_id: input.courseId,
|
||||||
}).select().single();
|
member_id: input.memberId,
|
||||||
|
first_name: input.firstName,
|
||||||
|
last_name: input.lastName,
|
||||||
|
email: input.email,
|
||||||
|
phone: input.phone,
|
||||||
|
status,
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async cancelEnrollment(participantId: string) {
|
async cancelEnrollment(participantId: string) {
|
||||||
const { error } = await client.from('course_participants')
|
const { error } = await client
|
||||||
|
.from('course_participants')
|
||||||
.update({ status: 'cancelled', cancelled_at: new Date().toISOString() })
|
.update({ status: 'cancelled', cancelled_at: new Date().toISOString() })
|
||||||
.eq('id', participantId);
|
.eq('id', participantId);
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getParticipants(courseId: string) {
|
async getParticipants(courseId: string) {
|
||||||
const { data, error } = await client.from('course_participants').select('*')
|
const { data, error } = await client
|
||||||
.eq('course_id', courseId).order('enrolled_at');
|
.from('course_participants')
|
||||||
|
.select('*')
|
||||||
|
.eq('course_id', courseId)
|
||||||
|
.order('enrolled_at');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- Sessions ---
|
// --- Sessions ---
|
||||||
async getSessions(courseId: string) {
|
async getSessions(courseId: string) {
|
||||||
const { data, error } = await client.from('course_sessions').select('*')
|
const { data, error } = await client
|
||||||
.eq('course_id', courseId).order('session_date');
|
.from('course_sessions')
|
||||||
|
.select('*')
|
||||||
|
.eq('course_id', courseId)
|
||||||
|
.order('session_date');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async createSession(input: { courseId: string; sessionDate: string; startTime: string; endTime: string; locationId?: string }) {
|
async createSession(input: {
|
||||||
const { data, error } = await client.from('course_sessions').insert({
|
courseId: string;
|
||||||
course_id: input.courseId, session_date: input.sessionDate,
|
sessionDate: string;
|
||||||
start_time: input.startTime, end_time: input.endTime, location_id: input.locationId,
|
startTime: string;
|
||||||
}).select().single();
|
endTime: string;
|
||||||
|
locationId?: string;
|
||||||
|
}) {
|
||||||
|
const { data, error } = await client
|
||||||
|
.from('course_sessions')
|
||||||
|
.insert({
|
||||||
|
course_id: input.courseId,
|
||||||
|
session_date: input.sessionDate,
|
||||||
|
start_time: input.startTime,
|
||||||
|
end_time: input.endTime,
|
||||||
|
location_id: input.locationId,
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- Attendance ---
|
// --- Attendance ---
|
||||||
async getAttendance(sessionId: string) {
|
async getAttendance(sessionId: string) {
|
||||||
const { data, error } = await client.from('course_attendance').select('*').eq('session_id', sessionId);
|
const { data, error } = await client
|
||||||
|
.from('course_attendance')
|
||||||
|
.select('*')
|
||||||
|
.eq('session_id', sessionId);
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async markAttendance(sessionId: string, participantId: string, present: boolean) {
|
async markAttendance(
|
||||||
const { error } = await client.from('course_attendance').upsert({
|
sessionId: string,
|
||||||
session_id: sessionId, participant_id: participantId, present,
|
participantId: string,
|
||||||
}, { onConflict: 'session_id,participant_id' });
|
present: boolean,
|
||||||
|
) {
|
||||||
|
const { error } = await client.from('course_attendance').upsert(
|
||||||
|
{
|
||||||
|
session_id: sessionId,
|
||||||
|
participant_id: participantId,
|
||||||
|
present,
|
||||||
|
},
|
||||||
|
{ onConflict: 'session_id,participant_id' },
|
||||||
|
);
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- Categories, Instructors, Locations ---
|
// --- Categories, Instructors, Locations ---
|
||||||
async listCategories(accountId: string) {
|
async listCategories(accountId: string) {
|
||||||
const { data, error } = await client.from('course_categories').select('*')
|
const { data, error } = await client
|
||||||
.eq('account_id', accountId).order('sort_order');
|
.from('course_categories')
|
||||||
|
.select('*')
|
||||||
|
.eq('account_id', accountId)
|
||||||
|
.order('sort_order');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async listInstructors(accountId: string) {
|
async listInstructors(accountId: string) {
|
||||||
const { data, error } = await client.from('course_instructors').select('*')
|
const { data, error } = await client
|
||||||
.eq('account_id', accountId).order('last_name');
|
.from('course_instructors')
|
||||||
|
.select('*')
|
||||||
|
.eq('account_id', accountId)
|
||||||
|
.order('last_name');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async listLocations(accountId: string) {
|
async listLocations(accountId: string) {
|
||||||
const { data, error } = await client.from('course_locations').select('*')
|
const { data, error } = await client
|
||||||
.eq('account_id', accountId).order('name');
|
.from('course_locations')
|
||||||
|
.select('*')
|
||||||
|
.eq('account_id', accountId)
|
||||||
|
.order('name');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- Statistics ---
|
// --- Statistics ---
|
||||||
async getStatistics(accountId: string) {
|
async getStatistics(accountId: string) {
|
||||||
const { data: courses } = await client.from('courses').select('status').eq('account_id', accountId);
|
const { data: courses } = await client
|
||||||
const { count: totalParticipants } = await client.from('course_participants')
|
.from('courses')
|
||||||
|
.select('status')
|
||||||
|
.eq('account_id', accountId);
|
||||||
|
const { count: totalParticipants } = await client
|
||||||
|
.from('course_participants')
|
||||||
.select('*', { count: 'exact', head: true })
|
.select('*', { count: 'exact', head: true })
|
||||||
.in('course_id', (courses ?? []).map((c: any) => c.id));
|
.in(
|
||||||
|
'course_id',
|
||||||
|
(courses ?? []).map((c: any) => c.id),
|
||||||
|
);
|
||||||
|
|
||||||
const stats = { totalCourses: 0, openCourses: 0, completedCourses: 0, totalParticipants: totalParticipants ?? 0 };
|
const stats = {
|
||||||
for (const c of (courses ?? [])) {
|
totalCourses: 0,
|
||||||
|
openCourses: 0,
|
||||||
|
completedCourses: 0,
|
||||||
|
totalParticipants: totalParticipants ?? 0,
|
||||||
|
};
|
||||||
|
for (const c of courses ?? []) {
|
||||||
stats.totalCourses++;
|
stats.totalCourses++;
|
||||||
if (c.status === 'open' || c.status === 'running') stats.openCourses++;
|
if (c.status === 'open' || c.status === 'running') stats.openCourses++;
|
||||||
if (c.status === 'completed') stats.completedCourses++;
|
if (c.status === 'completed') stats.completedCourses++;
|
||||||
@@ -143,30 +244,70 @@ export function createCourseManagementApi(client: SupabaseClient<Database>) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// --- Create methods for CRUD ---
|
// --- Create methods for CRUD ---
|
||||||
async createCategory(input: { accountId: string; name: string; description?: string; parentId?: string }) {
|
async createCategory(input: {
|
||||||
const { data, error } = await client.from('course_categories').insert({
|
accountId: string;
|
||||||
account_id: input.accountId, name: input.name, description: input.description,
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
parentId?: string;
|
||||||
|
}) {
|
||||||
|
const { data, error } = await client
|
||||||
|
.from('course_categories')
|
||||||
|
.insert({
|
||||||
|
account_id: input.accountId,
|
||||||
|
name: input.name,
|
||||||
|
description: input.description,
|
||||||
parent_id: input.parentId,
|
parent_id: input.parentId,
|
||||||
}).select().single();
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async createInstructor(input: { accountId: string; firstName: string; lastName: string; email?: string; phone?: string; qualifications?: string; hourlyRate?: number }) {
|
async createInstructor(input: {
|
||||||
const { data, error } = await client.from('course_instructors').insert({
|
accountId: string;
|
||||||
account_id: input.accountId, first_name: input.firstName, last_name: input.lastName,
|
firstName: string;
|
||||||
email: input.email, phone: input.phone, qualifications: input.qualifications,
|
lastName: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
qualifications?: string;
|
||||||
|
hourlyRate?: number;
|
||||||
|
}) {
|
||||||
|
const { data, error } = await client
|
||||||
|
.from('course_instructors')
|
||||||
|
.insert({
|
||||||
|
account_id: input.accountId,
|
||||||
|
first_name: input.firstName,
|
||||||
|
last_name: input.lastName,
|
||||||
|
email: input.email,
|
||||||
|
phone: input.phone,
|
||||||
|
qualifications: input.qualifications,
|
||||||
hourly_rate: input.hourlyRate,
|
hourly_rate: input.hourlyRate,
|
||||||
}).select().single();
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async createLocation(input: { accountId: string; name: string; address?: string; room?: string; capacity?: number }) {
|
async createLocation(input: {
|
||||||
const { data, error } = await client.from('course_locations').insert({
|
accountId: string;
|
||||||
account_id: input.accountId, name: input.name, address: input.address,
|
name: string;
|
||||||
room: input.room, capacity: input.capacity,
|
address?: string;
|
||||||
}).select().single();
|
room?: string;
|
||||||
|
capacity?: number;
|
||||||
|
}) {
|
||||||
|
const { data, error } = await client
|
||||||
|
.from('course_locations')
|
||||||
|
.insert({
|
||||||
|
account_id: input.accountId,
|
||||||
|
name: input.name,
|
||||||
|
address: input.address,
|
||||||
|
room: input.room,
|
||||||
|
capacity: input.capacity,
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Database } from '@kit/supabase/database';
|
|
||||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
import type { Database } from '@kit/supabase/database';
|
||||||
|
|
||||||
import type { CreateEventInput } from '../schema/event.schema';
|
import type { CreateEventInput } from '../schema/event.schema';
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
@@ -10,16 +11,28 @@ export function createEventManagementApi(client: SupabaseClient<Database>) {
|
|||||||
const db = client;
|
const db = client;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async listEvents(accountId: string, opts?: { status?: string; page?: number }) {
|
async listEvents(
|
||||||
let query = client.from('events').select('*', { count: 'exact' })
|
accountId: string,
|
||||||
.eq('account_id', accountId).order('event_date', { ascending: false });
|
opts?: { status?: string; page?: number },
|
||||||
|
) {
|
||||||
|
let query = client
|
||||||
|
.from('events')
|
||||||
|
.select('*', { count: 'exact' })
|
||||||
|
.eq('account_id', accountId)
|
||||||
|
.order('event_date', { ascending: false });
|
||||||
if (opts?.status) query = query.eq('status', opts.status);
|
if (opts?.status) query = query.eq('status', opts.status);
|
||||||
const page = opts?.page ?? 1;
|
const page = opts?.page ?? 1;
|
||||||
query = query.range((page - 1) * PAGE_SIZE, page * PAGE_SIZE - 1);
|
query = query.range((page - 1) * PAGE_SIZE, page * PAGE_SIZE - 1);
|
||||||
const { data, error, count } = await query;
|
const { data, error, count } = await query;
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
const total = count ?? 0;
|
const total = count ?? 0;
|
||||||
return { data: data ?? [], total, page, pageSize: PAGE_SIZE, totalPages: Math.max(1, Math.ceil(total / PAGE_SIZE)) };
|
return {
|
||||||
|
data: data ?? [],
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
totalPages: Math.max(1, Math.ceil(total / PAGE_SIZE)),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
async getRegistrationCounts(eventIds: string[]) {
|
async getRegistrationCounts(eventIds: string[]) {
|
||||||
@@ -40,71 +53,131 @@ export function createEventManagementApi(client: SupabaseClient<Database>) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async getEvent(eventId: string) {
|
async getEvent(eventId: string) {
|
||||||
const { data, error } = await client.from('events').select('*').eq('id', eventId).single();
|
const { data, error } = await client
|
||||||
|
.from('events')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', eventId)
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async createEvent(input: CreateEventInput) {
|
async createEvent(input: CreateEventInput) {
|
||||||
const { data, error } = await client.from('events').insert({
|
const { data, error } = await client
|
||||||
account_id: input.accountId, name: input.name, description: input.description || null,
|
.from('events')
|
||||||
event_date: input.eventDate || null, event_time: input.eventTime || null, end_date: input.endDate || null,
|
.insert({
|
||||||
location: input.location || null, capacity: input.capacity, min_age: input.minAge ?? null,
|
account_id: input.accountId,
|
||||||
max_age: input.maxAge ?? null, fee: input.fee, status: input.status,
|
name: input.name,
|
||||||
|
description: input.description || null,
|
||||||
|
event_date: input.eventDate || null,
|
||||||
|
event_time: input.eventTime || null,
|
||||||
|
end_date: input.endDate || null,
|
||||||
|
location: input.location || null,
|
||||||
|
capacity: input.capacity,
|
||||||
|
min_age: input.minAge ?? null,
|
||||||
|
max_age: input.maxAge ?? null,
|
||||||
|
fee: input.fee,
|
||||||
|
status: input.status,
|
||||||
registration_deadline: input.registrationDeadline || null,
|
registration_deadline: input.registrationDeadline || null,
|
||||||
contact_name: input.contactName || null, contact_email: input.contactEmail || null, contact_phone: input.contactPhone || null,
|
contact_name: input.contactName || null,
|
||||||
}).select().single();
|
contact_email: input.contactEmail || null,
|
||||||
|
contact_phone: input.contactPhone || null,
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async registerForEvent(input: { eventId: string; firstName: string; lastName: string; email?: string; parentName?: string }) {
|
async registerForEvent(input: {
|
||||||
|
eventId: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email?: string;
|
||||||
|
parentName?: string;
|
||||||
|
}) {
|
||||||
// Check capacity
|
// Check capacity
|
||||||
const event = await this.getEvent(input.eventId);
|
const event = await this.getEvent(input.eventId);
|
||||||
if (event.capacity) {
|
if (event.capacity) {
|
||||||
const { count } = await client.from('event_registrations').select('*', { count: 'exact', head: true })
|
const { count } = await client
|
||||||
.eq('event_id', input.eventId).in('status', ['pending', 'confirmed']);
|
.from('event_registrations')
|
||||||
|
.select('*', { count: 'exact', head: true })
|
||||||
|
.eq('event_id', input.eventId)
|
||||||
|
.in('status', ['pending', 'confirmed']);
|
||||||
if ((count ?? 0) >= event.capacity) {
|
if ((count ?? 0) >= event.capacity) {
|
||||||
throw new Error('Event is full');
|
throw new Error('Event is full');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await client.from('event_registrations').insert({
|
const { data, error } = await client
|
||||||
event_id: input.eventId, first_name: input.firstName, last_name: input.lastName,
|
.from('event_registrations')
|
||||||
email: input.email, parent_name: input.parentName, status: 'confirmed',
|
.insert({
|
||||||
}).select().single();
|
event_id: input.eventId,
|
||||||
|
first_name: input.firstName,
|
||||||
|
last_name: input.lastName,
|
||||||
|
email: input.email,
|
||||||
|
parent_name: input.parentName,
|
||||||
|
status: 'confirmed',
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getRegistrations(eventId: string) {
|
async getRegistrations(eventId: string) {
|
||||||
const { data, error } = await client.from('event_registrations').select('*')
|
const { data, error } = await client
|
||||||
.eq('event_id', eventId).order('created_at');
|
.from('event_registrations')
|
||||||
|
.select('*')
|
||||||
|
.eq('event_id', eventId)
|
||||||
|
.order('created_at');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
// Holiday passes
|
// Holiday passes
|
||||||
async listHolidayPasses(accountId: string) {
|
async listHolidayPasses(accountId: string) {
|
||||||
const { data, error } = await client.from('holiday_passes').select('*')
|
const { data, error } = await client
|
||||||
.eq('account_id', accountId).order('year', { ascending: false });
|
.from('holiday_passes')
|
||||||
|
.select('*')
|
||||||
|
.eq('account_id', accountId)
|
||||||
|
.order('year', { ascending: false });
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async getPassActivities(passId: string) {
|
async getPassActivities(passId: string) {
|
||||||
const { data, error } = await client.from('holiday_pass_activities').select('*')
|
const { data, error } = await client
|
||||||
.eq('pass_id', passId).order('activity_date');
|
.from('holiday_pass_activities')
|
||||||
|
.select('*')
|
||||||
|
.eq('pass_id', passId)
|
||||||
|
.order('activity_date');
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async createHolidayPass(input: { accountId: string; name: string; year: number; description?: string; price?: number; validFrom?: string; validUntil?: string }) {
|
async createHolidayPass(input: {
|
||||||
const { data, error } = await client.from('holiday_passes').insert({
|
accountId: string;
|
||||||
account_id: input.accountId, name: input.name, year: input.year,
|
name: string;
|
||||||
description: input.description, price: input.price ?? 0,
|
year: number;
|
||||||
valid_from: input.validFrom, valid_until: input.validUntil,
|
description?: string;
|
||||||
}).select().single();
|
price?: number;
|
||||||
|
validFrom?: string;
|
||||||
|
validUntil?: string;
|
||||||
|
}) {
|
||||||
|
const { data, error } = await client
|
||||||
|
.from('holiday_passes')
|
||||||
|
.insert({
|
||||||
|
account_id: input.accountId,
|
||||||
|
name: input.name,
|
||||||
|
year: input.year,
|
||||||
|
description: input.description,
|
||||||
|
price: input.price ?? 0,
|
||||||
|
valid_from: input.validFrom,
|
||||||
|
valid_until: input.validUntil,
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user