changemaker.lite/api/src/modules/settings/settings.schemas.ts
bunker-admin 902adce646 Add Straw Polls feature: quick opinion polling with public landers, MkDocs widgets, and social integration
Full-stack implementation across 7 sprints:
- Backend: 5 Prisma models (StrawPoll, Option, Vote, Comment, Challenge), 4 enums, POLLS_ADMIN role,
  admin CRUD routes, public voting/SSE/widget endpoints, BullMQ auto-close queue, rate limiting
- Admin: StrawPollsPage with inline drawers (campaigns pattern), PollResults bar chart, sidebar under Advocacy
- Public: dedicated poll lander with real-time SSE updates, browse page, anonymous voting with token dedup
- MkDocs: straw-poll-widget.js hydration (inline vote + card link modes), GrapesJS block types
- Social: feed activity (poll_voted), friend badge integration, challenge notifications, notification preferences
- Feature flag: enablePolls toggle in Settings, FeatureGate, Zod schema

Bunker Admin
2026-03-31 10:16:56 -06:00

142 lines
5.3 KiB
TypeScript

import { z } from 'zod';
const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/, 'Must be a hex color (e.g. #ff00ff)');
export const updateSiteSettingsSchema = z.object({
// Organization
organizationName: z.string().min(1).max(100).optional(),
organizationShortName: z.string().min(1).max(10).optional(),
organizationLogoUrl: z.string().url().nullable().optional().or(z.literal('')),
organizationFaviconUrl: z.string().url().nullable().optional().or(z.literal('')),
// Admin theme
adminColorPrimary: hexColor.optional(),
adminColorBgBase: hexColor.optional(),
// Public theme
publicColorPrimary: hexColor.optional(),
publicColorBgBase: hexColor.optional(),
publicColorBgContainer: hexColor.optional(),
publicHeaderGradient: z.string().max(500).optional(),
// Text
footerText: z.string().max(200).optional(),
loginSubtitle: z.string().max(50).optional(),
// Email branding
emailFromName: z.string().min(1).max(100).optional(),
// SMTP configuration
smtpHost: z.string().max(255).optional(),
smtpPort: z.number().int().min(0).max(65535).optional(),
smtpUser: z.string().max(255).optional(),
smtpPass: z.string().max(500).optional(),
smtpFromAddress: z.string().max(255).optional(),
smtpActiveProvider: z.enum(['mailhog', 'production']).optional(),
emailTestMode: z.boolean().optional(),
testEmailRecipient: z.string().max(255).optional(),
// Registration settings
enablePublicRegistration: z.boolean().optional(),
enableEmailVerification: z.boolean().optional(),
autoApproveVerifiedUsers: z.boolean().optional(),
// Feature toggles
enableInfluence: z.boolean().optional(),
enableMap: z.boolean().optional(),
enableNewsletter: z.boolean().optional(),
enableLandingPages: z.boolean().optional(),
enableMediaFeatures: z.boolean().optional(),
enablePayments: z.boolean().optional(),
enableGalleryAds: z.boolean().optional(),
enableChat: z.boolean().optional(),
enableEvents: z.boolean().optional(),
enableDocsComments: z.boolean().optional(),
enableSms: z.boolean().optional(),
enablePeople: z.boolean().optional(),
enableSocial: z.boolean().optional(),
enableMeet: z.boolean().optional(),
enableMeetingPlanner: z.boolean().optional(),
enableTicketedEvents: z.boolean().optional(),
enablePolls: z.boolean().optional(),
enableSocialCalendar: z.boolean().optional(),
enableDocsCollaboration: z.boolean().optional(),
requireEventApproval: z.boolean().optional(),
autoSyncPeopleToMap: z.boolean().optional(),
// SMS connection config
smsTermuxApiUrl: z.string().max(500).optional(),
smsTermuxApiKey: z.string().max(500).optional(),
smsTailscaleApiKey: z.string().max(500).optional(),
smsTailscaleTailnet: z.string().max(200).optional(),
smsTailscaleDeviceId: z.string().max(200).optional(),
smsTailscaleDeviceName: z.string().max(200).optional(),
// Gitea Docs Comments
giteaApiToken: z.string().max(500).optional(),
giteaCommentsRepoOwner: z.string().max(100).optional(),
giteaCommentsRepoName: z.string().max(100).optional(),
giteaOauthClientId: z.string().max(500).optional(),
giteaOauthClientSecret: z.string().max(500).optional(),
giteaSetupComplete: z.boolean().optional(),
// User Provisioning
enableUserProvisioning: z.boolean().optional(),
provisionGitea: z.boolean().optional(),
provisionGiteaTiming: z.enum(['lazy', 'eager']).optional(),
provisionVaultwarden: z.boolean().optional(),
provisionVaultwardenTiming: z.enum(['lazy', 'eager']).optional(),
provisionListmonk: z.boolean().optional(),
provisionListmonkTiming: z.enum(['lazy', 'eager']).optional(),
// Auto-upgrade settings
enableAutoUpgrade: z.boolean().optional(),
autoUpgradeSchedule: z.enum([
'daily-3am', 'daily-4am', 'daily-5am',
'weekly-sun-3am', 'weekly-mon-3am', '12h', '24h',
]).optional(),
autoUpgradePullServices: z.boolean().optional(),
notifyAdminAutoUpgrade: z.boolean().optional(),
// Navigation configuration (supports one level of nesting via groups)
navConfig: z.object({
items: z.array(z.object({
id: z.string(),
label: z.string(),
path: z.string(),
icon: z.string(),
enabled: z.boolean(),
order: z.number(),
type: z.enum(['builtin', 'custom', 'group']),
featureFlag: z.string().optional(),
external: z.boolean().optional(),
children: z.array(z.object({
id: z.string(),
label: z.string(),
path: z.string(),
icon: z.string(),
enabled: z.boolean(),
order: z.number(),
type: z.enum(['builtin', 'custom']),
featureFlag: z.string().optional(),
external: z.boolean().optional(),
})).optional(),
})),
}).optional(),
// Notification settings
notifyAdminShiftSignup: z.boolean().optional(),
notifyAdminResponseSubmitted: z.boolean().optional(),
notifyAdminSignRequested: z.boolean().optional(),
notifyAdminShiftCancellation: z.boolean().optional(),
notifyVolunteerSessionSummary: z.boolean().optional(),
notifyVolunteerCancellation: z.boolean().optional(),
notifyVolunteerShiftReminder: z.boolean().optional(),
notifyVolunteerShiftThankYou: z.boolean().optional(),
notifyVolunteerReengagement: z.boolean().optional(),
reengagementInactiveDays: z.number().int().min(1).max(365).optional(),
reengagementCooldownDays: z.number().int().min(1).max(365).optional(),
});
export type UpdateSiteSettingsInput = z.infer<typeof updateSiteSettingsSchema>;