Atproto AMA app
1/**
2 * Shared schema definitions and constraints used by both Drizzle and Lexicon schemas
3 */
4
5// Source types for questions and answers
6export const SOURCE_TYPES = {
7 ASKIMUT: 'askimut',
8 BLUESKY: 'bsky',
9 STANDARD_SITE: 'standard.site',
10 MASTODON: 'mastodon',
11 NOSTR: 'nostr'
12} as const;
13
14export type SourceType = typeof SOURCE_TYPES[keyof typeof SOURCE_TYPES];
15
16// Shared field definitions and constraints
17export const FIELD_CONSTRAINTS = {
18 question: {
19 content: { minLength: 1, maxLength: 1000 },
20 tags: { maxItems: 10, itemMaxLength: 50 }
21 },
22 answer: {
23 content: { minLength: 1, maxLength: 5000 }
24 },
25 user: {
26 handle: { pattern: /^[a-zA-Z0-9.-]+$/, maxLength: 253 },
27 displayName: { maxLength: 64 }
28 },
29 source: {
30 validTypes: Object.values(SOURCE_TYPES)
31 }
32} as const;
33
34// Shared TypeScript interfaces
35export interface BaseQuestion {
36 content: string;
37 targetDid: string;
38 authorDid: string;
39 sourceType: SourceType;
40 anonymous: boolean;
41 createdAt: string;
42 tags?: string[];
43 sourceUri?: string; // Original URI from source platform
44 sourceData?: Record<string, unknown>; // Platform-specific metadata
45}
46
47export interface BaseAnswer {
48 content: string;
49 questionUri: string;
50 authorDid: string;
51 sourceType: SourceType;
52 createdAt: string;
53 sourceUri?: string; // Original URI from source platform
54 sourceData?: Record<string, unknown>; // Platform-specific metadata
55}
56
57export interface BaseProfile {
58 displayName?: string;
59 description?: string;
60 questionsOpen: boolean;
61}
62
63// Validation helpers
64export function validateQuestionContent(content: string): boolean {
65 return content.length >= FIELD_CONSTRAINTS.question.content.minLength &&
66 content.length <= FIELD_CONSTRAINTS.question.content.maxLength;
67}
68
69export function validateAnswerContent(content: string): boolean {
70 return content.length >= FIELD_CONSTRAINTS.answer.content.minLength &&
71 content.length <= FIELD_CONSTRAINTS.answer.content.maxLength;
72}
73
74export function validateHandle(handle: string): boolean {
75 return FIELD_CONSTRAINTS.user.handle.pattern.test(handle) &&
76 handle.length <= FIELD_CONSTRAINTS.user.handle.maxLength;
77}
78
79export function validateDisplayName(displayName: string): boolean {
80 return displayName.length <= FIELD_CONSTRAINTS.user.displayName.maxLength;
81}
82
83export function validateTags(tags: string[]): boolean {
84 if (tags.length > FIELD_CONSTRAINTS.question.tags.maxItems) {
85 return false;
86 }
87 return tags.every(tag =>
88 tag.length <= FIELD_CONSTRAINTS.question.tags.itemMaxLength
89 );
90}
91
92export function validateSourceType(sourceType: string): sourceType is SourceType {
93 return FIELD_CONSTRAINTS.source.validTypes.includes(sourceType as SourceType);
94}
95
96export function isAskimutNative(sourceType: SourceType): boolean {
97 return sourceType === SOURCE_TYPES.ASKIMUT;
98}
99
100export function isBlueSkySource(sourceType: SourceType): boolean {
101 return sourceType === SOURCE_TYPES.BLUESKY;
102}
103
104export function isStandardSiteSource(sourceType: SourceType): boolean {
105 return sourceType === SOURCE_TYPES.STANDARD_SITE;
106}
107
108// Common error messages
109export const VALIDATION_ERRORS = {
110 question: {
111 contentTooShort: `Question must be at least ${FIELD_CONSTRAINTS.question.content.minLength} character`,
112 contentTooLong: `Question cannot exceed ${FIELD_CONSTRAINTS.question.content.maxLength} characters`,
113 tooManyTags: `Cannot have more than ${FIELD_CONSTRAINTS.question.tags.maxItems} tags`,
114 tagTooLong: `Tag cannot exceed ${FIELD_CONSTRAINTS.question.tags.itemMaxLength} characters`
115 },
116 answer: {
117 contentTooShort: `Answer must be at least ${FIELD_CONSTRAINTS.answer.content.minLength} character`,
118 contentTooLong: `Answer cannot exceed ${FIELD_CONSTRAINTS.answer.content.maxLength} characters`
119 },
120 user: {
121 invalidHandle: 'Handle contains invalid characters',
122 handleTooLong: `Handle cannot exceed ${FIELD_CONSTRAINTS.user.handle.maxLength} characters`,
123 displayNameTooLong: `Display name cannot exceed ${FIELD_CONSTRAINTS.user.displayName.maxLength} characters`
124 },
125 source: {
126 invalidType: `Source type must be one of: ${FIELD_CONSTRAINTS.source.validTypes.join(', ')}`,
127 required: 'Source type is required'
128 }
129} as const;