/** * Shared schema definitions and constraints used by both Drizzle and Lexicon schemas */ // Source types for questions and answers export const SOURCE_TYPES = { ASKIMUT: 'askimut', BLUESKY: 'bsky', STANDARD_SITE: 'standard.site', MASTODON: 'mastodon', NOSTR: 'nostr' } as const; export type SourceType = typeof SOURCE_TYPES[keyof typeof SOURCE_TYPES]; // Shared field definitions and constraints export const FIELD_CONSTRAINTS = { question: { content: { minLength: 1, maxLength: 1000 }, tags: { maxItems: 10, itemMaxLength: 50 } }, answer: { content: { minLength: 1, maxLength: 5000 } }, user: { handle: { pattern: /^[a-zA-Z0-9.-]+$/, maxLength: 253 }, displayName: { maxLength: 64 } }, source: { validTypes: Object.values(SOURCE_TYPES) } } as const; // Shared TypeScript interfaces export interface BaseQuestion { content: string; targetDid: string; authorDid: string; sourceType: SourceType; anonymous: boolean; createdAt: string; tags?: string[]; sourceUri?: string; // Original URI from source platform sourceData?: Record; // Platform-specific metadata } export interface BaseAnswer { content: string; questionUri: string; authorDid: string; sourceType: SourceType; createdAt: string; sourceUri?: string; // Original URI from source platform sourceData?: Record; // Platform-specific metadata } export interface BaseProfile { displayName?: string; description?: string; questionsOpen: boolean; } // Validation helpers export function validateQuestionContent(content: string): boolean { return content.length >= FIELD_CONSTRAINTS.question.content.minLength && content.length <= FIELD_CONSTRAINTS.question.content.maxLength; } export function validateAnswerContent(content: string): boolean { return content.length >= FIELD_CONSTRAINTS.answer.content.minLength && content.length <= FIELD_CONSTRAINTS.answer.content.maxLength; } export function validateHandle(handle: string): boolean { return FIELD_CONSTRAINTS.user.handle.pattern.test(handle) && handle.length <= FIELD_CONSTRAINTS.user.handle.maxLength; } export function validateDisplayName(displayName: string): boolean { return displayName.length <= FIELD_CONSTRAINTS.user.displayName.maxLength; } export function validateTags(tags: string[]): boolean { if (tags.length > FIELD_CONSTRAINTS.question.tags.maxItems) { return false; } return tags.every(tag => tag.length <= FIELD_CONSTRAINTS.question.tags.itemMaxLength ); } export function validateSourceType(sourceType: string): sourceType is SourceType { return FIELD_CONSTRAINTS.source.validTypes.includes(sourceType as SourceType); } export function isAskimutNative(sourceType: SourceType): boolean { return sourceType === SOURCE_TYPES.ASKIMUT; } export function isBlueSkySource(sourceType: SourceType): boolean { return sourceType === SOURCE_TYPES.BLUESKY; } export function isStandardSiteSource(sourceType: SourceType): boolean { return sourceType === SOURCE_TYPES.STANDARD_SITE; } // Common error messages export const VALIDATION_ERRORS = { question: { contentTooShort: `Question must be at least ${FIELD_CONSTRAINTS.question.content.minLength} character`, contentTooLong: `Question cannot exceed ${FIELD_CONSTRAINTS.question.content.maxLength} characters`, tooManyTags: `Cannot have more than ${FIELD_CONSTRAINTS.question.tags.maxItems} tags`, tagTooLong: `Tag cannot exceed ${FIELD_CONSTRAINTS.question.tags.itemMaxLength} characters` }, answer: { contentTooShort: `Answer must be at least ${FIELD_CONSTRAINTS.answer.content.minLength} character`, contentTooLong: `Answer cannot exceed ${FIELD_CONSTRAINTS.answer.content.maxLength} characters` }, user: { invalidHandle: 'Handle contains invalid characters', handleTooLong: `Handle cannot exceed ${FIELD_CONSTRAINTS.user.handle.maxLength} characters`, displayNameTooLong: `Display name cannot exceed ${FIELD_CONSTRAINTS.user.displayName.maxLength} characters` }, source: { invalidType: `Source type must be one of: ${FIELD_CONSTRAINTS.source.validTypes.join(', ')}`, required: 'Source type is required' } } as const;