Barazo AppView backend barazo.forum
at main 96 lines 3.5 kB view raw
1import { z } from 'zod/v4' 2 3// --------------------------------------------------------------------------- 4// Helpers 5// --------------------------------------------------------------------------- 6 7/** 8 * Count the number of grapheme clusters in a string using Intl.Segmenter. 9 * AT Protocol lexicons specify `maxGraphemes` which counts user-perceived 10 * characters (grapheme clusters), not UTF-16 code units. 11 */ 12function graphemeLength(str: string): number { 13 const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' }) 14 return [...segmenter.segment(str)].length 15} 16 17// --------------------------------------------------------------------------- 18// Request schemas 19// --------------------------------------------------------------------------- 20 21/** Schema for creating a reaction on a topic or reply. */ 22export const createReactionSchema = z.object({ 23 subjectUri: z.string().min(1, 'Subject URI is required'), 24 subjectCid: z.string().min(1, 'Subject CID is required'), 25 type: z 26 .string() 27 .trim() 28 .min(1, 'Reaction type is required') 29 .max(300, 'Reaction type exceeds maximum byte length') 30 .refine((val) => graphemeLength(val) <= 30, 'Reaction type must be at most 30 graphemes'), 31}) 32 33export type CreateReactionInput = z.infer<typeof createReactionSchema> 34 35// --------------------------------------------------------------------------- 36// Query schemas 37// --------------------------------------------------------------------------- 38 39/** Schema for listing reactions with pagination and optional type filter. */ 40export const reactionQuerySchema = z.object({ 41 subjectUri: z.string().min(1, 'Subject URI is required'), 42 type: z.string().optional(), 43 cursor: z.string().optional(), 44 limit: z 45 .string() 46 .transform((val) => Number(val)) 47 .pipe(z.number().int().min(1).max(100)) 48 .optional() 49 .default(25), 50}) 51 52export type ReactionQueryInput = z.infer<typeof reactionQuerySchema> 53 54// --------------------------------------------------------------------------- 55// Admin settings extension 56// --------------------------------------------------------------------------- 57 58/** Schema for validating reactionSet in admin settings updates. */ 59export const reactionSetSchema = z 60 .array( 61 z 62 .string() 63 .trim() 64 .min(1, 'Reaction type must not be empty') 65 .max(300, 'Reaction type exceeds maximum byte length') 66 .refine((val) => graphemeLength(val) <= 30, 'Reaction type must be at most 30 graphemes') 67 ) 68 .min(1, 'Reaction set must contain at least one reaction type') 69 .refine((arr) => new Set(arr).size === arr.length, 'Reaction set must contain unique values') 70 71export type ReactionSet = z.infer<typeof reactionSetSchema> 72 73// --------------------------------------------------------------------------- 74// Response schemas (for OpenAPI documentation) 75// --------------------------------------------------------------------------- 76 77/** Schema describing a single reaction in API responses. */ 78export const reactionResponseSchema = z.object({ 79 uri: z.string(), 80 rkey: z.string(), 81 authorDid: z.string(), 82 subjectUri: z.string(), 83 type: z.string(), 84 cid: z.string(), 85 createdAt: z.string(), 86}) 87 88export type ReactionResponse = z.infer<typeof reactionResponseSchema> 89 90/** Schema for a paginated reaction list response. */ 91export const reactionListResponseSchema = z.object({ 92 reactions: z.array(reactionResponseSchema), 93 cursor: z.string().nullable(), 94}) 95 96export type ReactionListResponse = z.infer<typeof reactionListResponseSchema>