changemaker.lite/api/src/modules/polls/polls.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

68 lines
2.9 KiB
TypeScript

import { z } from 'zod';
import { StrawPollType, StrawPollStatus, StrawPollIdentityMode, StrawPollResultVisibility } from '@prisma/client';
export const createStrawPollSchema = z.object({
title: z.string().min(1, 'Title is required').max(200),
description: z.string().max(2000).optional(),
type: z.nativeEnum(StrawPollType),
identityMode: z.nativeEnum(StrawPollIdentityMode).optional().default('ANONYMOUS'),
resultVisibility: z.nativeEnum(StrawPollResultVisibility).optional().default('LIVE'),
allowComments: z.boolean().optional().default(true),
closesAt: z.string().datetime().optional(),
closeThreshold: z.number().int().min(1).max(100000).nullable().optional(),
isPrivate: z.boolean().optional().default(false),
options: z.array(z.object({
label: z.string().min(1, 'Option label is required').max(500),
})).min(2, 'At least 2 options required').max(20, 'Maximum 20 options').optional(),
});
export const updateStrawPollSchema = z.object({
title: z.string().min(1).max(200).optional(),
description: z.string().max(2000).nullable().optional(),
identityMode: z.nativeEnum(StrawPollIdentityMode).optional(),
resultVisibility: z.nativeEnum(StrawPollResultVisibility).optional(),
allowComments: z.boolean().optional(),
closesAt: z.string().datetime().nullable().optional(),
closeThreshold: z.number().int().min(1).max(100000).nullable().optional(),
isPrivate: z.boolean().optional(),
options: z.array(z.object({
id: z.string().optional(),
label: z.string().min(1).max(500),
})).min(2).max(20).optional(),
});
export const submitStrawPollVoteSchema = z.object({
optionId: z.string().min(1, 'Option is required'),
voterName: z.string().min(1).max(100).optional(),
voterToken: z.string().optional(),
});
export const submitStrawPollCommentSchema = z.object({
authorName: z.string().min(1, 'Name is required').max(100),
content: z.string().min(1, 'Comment is required').max(2000),
});
export const listStrawPollsSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
search: z.string().optional(),
status: z.nativeEnum(StrawPollStatus).optional(),
type: z.nativeEnum(StrawPollType).optional(),
});
export const challengeVoteSchema = z.object({
challengedUserId: z.string().min(1, 'User ID is required'),
});
export const generateLinksSchema = z.object({
count: z.number().int().min(1).max(500).default(10),
});
export type CreateStrawPollInput = z.infer<typeof createStrawPollSchema>;
export type UpdateStrawPollInput = z.infer<typeof updateStrawPollSchema>;
export type SubmitStrawPollVoteInput = z.infer<typeof submitStrawPollVoteSchema>;
export type SubmitStrawPollCommentInput = z.infer<typeof submitStrawPollCommentSchema>;
export type ListStrawPollsInput = z.infer<typeof listStrawPollsSchema>;
export type ChallengeVoteInput = z.infer<typeof challengeVoteSchema>;
export type GenerateLinksInput = z.infer<typeof generateLinksSchema>;