Barazo AppView backend
barazo.forum
1import { z } from 'zod/v4'
2
3// ---------------------------------------------------------------------------
4// Shared enums
5// ---------------------------------------------------------------------------
6
7/** Valid maturity rating values for categories and communities. */
8export const maturityRatingSchema = z.enum(['safe', 'mature', 'adult'])
9
10export type MaturityRating = z.infer<typeof maturityRatingSchema>
11
12// ---------------------------------------------------------------------------
13// Request schemas
14// ---------------------------------------------------------------------------
15
16/** Slug pattern: lowercase alphanumeric segments separated by single hyphens. */
17const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
18
19/** Schema for creating a new category. */
20export const createCategorySchema = z.object({
21 name: z
22 .string()
23 .trim()
24 .min(1, 'Name is required')
25 .max(100, 'Name must be at most 100 characters'),
26 slug: z
27 .string()
28 .min(1, 'Slug is required')
29 .max(50, 'Slug must be at most 50 characters')
30 .regex(
31 slugPattern,
32 "Slug must be lowercase alphanumeric with single hyphens (e.g. 'general-discussion')"
33 ),
34 description: z.string().max(500, 'Description must be at most 500 characters').optional(),
35 parentId: z.string().optional(),
36 sortOrder: z
37 .number()
38 .int('Sort order must be an integer')
39 .min(0, 'Sort order must be non-negative')
40 .optional(),
41 maturityRating: maturityRatingSchema.optional(),
42})
43
44export type CreateCategoryInput = z.infer<typeof createCategorySchema>
45
46/** Schema for updating an existing category (all fields optional). */
47export const updateCategorySchema = z.object({
48 name: z
49 .string()
50 .trim()
51 .min(1, 'Name must not be empty')
52 .max(100, 'Name must be at most 100 characters')
53 .optional(),
54 slug: z
55 .string()
56 .min(1, 'Slug must not be empty')
57 .max(50, 'Slug must be at most 50 characters')
58 .regex(
59 slugPattern,
60 "Slug must be lowercase alphanumeric with single hyphens (e.g. 'general-discussion')"
61 )
62 .optional(),
63 description: z
64 .string()
65 .max(500, 'Description must be at most 500 characters')
66 .nullable()
67 .optional(),
68 parentId: z.string().nullable().optional(),
69 sortOrder: z
70 .number()
71 .int('Sort order must be an integer')
72 .min(0, 'Sort order must be non-negative')
73 .optional(),
74 maturityRating: maturityRatingSchema.optional(),
75})
76
77export type UpdateCategoryInput = z.infer<typeof updateCategorySchema>
78
79/** Schema for updating community/category maturity rating. */
80export const updateMaturitySchema = z.object({
81 maturityRating: maturityRatingSchema,
82})
83
84export type UpdateMaturityInput = z.infer<typeof updateMaturitySchema>
85
86// ---------------------------------------------------------------------------
87// Query schemas
88// ---------------------------------------------------------------------------
89
90/** Schema for listing categories with optional filtering. */
91export const categoryQuerySchema = z.object({
92 parentId: z.string().optional(),
93})
94
95export type CategoryQueryInput = z.infer<typeof categoryQuerySchema>
96
97// ---------------------------------------------------------------------------
98// Response schemas (for OpenAPI documentation)
99// ---------------------------------------------------------------------------
100
101/** Schema describing a single category in API responses. */
102export const categoryResponseSchema = z.object({
103 id: z.string(),
104 slug: z.string(),
105 name: z.string(),
106 description: z.string().nullable(),
107 parentId: z.string().nullable(),
108 sortOrder: z.number(),
109 communityDid: z.string(),
110 maturityRating: maturityRatingSchema,
111 createdAt: z.string(),
112 updatedAt: z.string(),
113})
114
115export type CategoryResponse = z.infer<typeof categoryResponseSchema>
116
117/** Schema describing a category with its children (tree structure). */
118export const categoryTreeResponseSchema: z.ZodType<CategoryTreeResponse> = z.lazy(() =>
119 z.object({
120 id: z.string(),
121 slug: z.string(),
122 name: z.string(),
123 description: z.string().nullable(),
124 parentId: z.string().nullable(),
125 sortOrder: z.number(),
126 communityDid: z.string(),
127 maturityRating: maturityRatingSchema,
128 createdAt: z.string(),
129 updatedAt: z.string(),
130 children: z.array(categoryTreeResponseSchema),
131 })
132)
133
134export interface CategoryTreeResponse {
135 id: string
136 slug: string
137 name: string
138 description: string | null
139 parentId: string | null
140 sortOrder: number
141 communityDid: string
142 maturityRating: 'safe' | 'mature' | 'adult'
143 createdAt: string
144 updatedAt: string
145 children: CategoryTreeResponse[]
146}