Barazo AppView backend
barazo.forum
1import { z } from 'zod/v4'
2
3// ---------------------------------------------------------------------------
4// Self-label schemas (com.atproto.label.defs#selfLabels)
5// ---------------------------------------------------------------------------
6
7const selfLabelSchema = z.object({
8 val: z.string().max(128),
9})
10
11const selfLabelsSchema = z.object({
12 values: z.array(selfLabelSchema).max(10),
13})
14
15// ---------------------------------------------------------------------------
16// Request schemas
17// ---------------------------------------------------------------------------
18
19/** Schema for creating a new topic. */
20export const createTopicSchema = z.object({
21 title: z
22 .string()
23 .trim()
24 .min(1, 'Title is required')
25 .max(200, 'Title must be at most 200 characters'),
26 content: z
27 .string()
28 .min(1, 'Content is required')
29 .max(100000, 'Content must be at most 100,000 characters'),
30 category: z.string().trim().min(1, 'Category is required'),
31 tags: z
32 .array(z.string().trim().min(1).max(30, 'Tag must be at most 30 characters'))
33 .max(5, 'At most 5 tags allowed')
34 .optional(),
35 labels: selfLabelsSchema.optional(),
36})
37
38export type CreateTopicInput = z.infer<typeof createTopicSchema>
39
40/** Schema for updating an existing topic (all fields optional). */
41export const updateTopicSchema = z.object({
42 title: z
43 .string()
44 .trim()
45 .min(1, 'Title must not be empty')
46 .max(200, 'Title must be at most 200 characters')
47 .optional(),
48 content: z
49 .string()
50 .min(1, 'Content must not be empty')
51 .max(100000, 'Content must be at most 100,000 characters')
52 .optional(),
53 category: z.string().trim().min(1, 'Category must not be empty').optional(),
54 tags: z
55 .array(z.string().trim().min(1).max(30, 'Tag must be at most 30 characters'))
56 .max(5, 'At most 5 tags allowed')
57 .optional(),
58 labels: selfLabelsSchema.optional(),
59})
60
61export type UpdateTopicInput = z.infer<typeof updateTopicSchema>
62
63// ---------------------------------------------------------------------------
64// Query schemas
65// ---------------------------------------------------------------------------
66
67/** Schema for listing topics with pagination and optional filtering. */
68export const topicQuerySchema = z.object({
69 cursor: z.string().optional(),
70 limit: z
71 .string()
72 .transform((val) => Number(val))
73 .pipe(z.number().int().min(1).max(100))
74 .optional()
75 .default(25),
76 category: z.string().optional(),
77 tag: z.string().optional(),
78 sort: z.enum(['latest', 'popular']).optional().default('latest'),
79})
80
81export type TopicQueryInput = z.infer<typeof topicQuerySchema>
82
83// ---------------------------------------------------------------------------
84// Response schemas (for OpenAPI documentation)
85// ---------------------------------------------------------------------------
86
87/** Schema describing a single topic in API responses. */
88export const topicResponseSchema = z.object({
89 uri: z.string(),
90 rkey: z.string(),
91 authorDid: z.string(),
92 title: z.string(),
93 content: z.string(),
94 category: z.string(),
95 site: z.string().nullable(),
96 tags: z.array(z.string()).nullable(),
97 labels: z.object({ values: z.array(z.object({ val: z.string() })) }).nullable(),
98 communityDid: z.string(),
99 cid: z.string(),
100 replyCount: z.number(),
101 reactionCount: z.number(),
102 lastActivityAt: z.string(),
103 publishedAt: z.string(),
104 indexedAt: z.string(),
105})
106
107export type TopicResponse = z.infer<typeof topicResponseSchema>
108
109/** Schema for a paginated topic list response. */
110export const topicListResponseSchema = z.object({
111 topics: z.array(topicResponseSchema),
112 cursor: z.string().nullable(),
113})
114
115export type TopicListResponse = z.infer<typeof topicListResponseSchema>