Spaces:
Running
Running
| /* ============================================================ | |
| GLASSGRID — CONTENT SCHEMA DEFINITIONS | |
| ⚠️ ARCHITECTURE RULE: | |
| All content is structured via these schemas. | |
| Components read schema-validated objects only. | |
| Raw HTML is NEVER stored or injected. | |
| ============================================================ */ | |
| ; | |
| const Schema = { | |
| // ── Post ────────────────────────────────────────── | |
| Post: { | |
| id: { type: 'string', required: true }, | |
| userId: { type: 'string', required: true }, | |
| mediaUrl: { type: 'string', required: true }, // Image URL only | |
| mediaType: { type: 'enum', values: ['image'], default: 'image' }, | |
| aspectRatio: { type: 'enum', values: ['square', 'landscape', 'portrait'], default: 'square' }, | |
| caption: { type: 'string', required: false, maxLength: 2200 }, | |
| altText: { type: 'string', required: false, maxLength: 500 }, | |
| tags: { type: 'array', items: 'string', default: [] }, | |
| location: { type: 'string', required: false, maxLength: 100 }, | |
| likeCount: { type: 'number', default: 0 }, | |
| commentCount:{ type: 'number', default: 0 }, | |
| saveCount: { type: 'number', default: 0 }, | |
| createdAt: { type: 'timestamp', required: true }, | |
| isArchived: { type: 'boolean', default: false }, | |
| isFeatured: { type: 'boolean', default: false }, | |
| }, | |
| // ── User ────────────────────────────────────────── | |
| User: { | |
| id: { type: 'string', required: true }, | |
| username: { type: 'string', required: true, pattern: /^[a-z0-9_.]{3,30}$/ }, | |
| displayName: { type: 'string', required: true, maxLength: 60 }, | |
| avatarUrl: { type: 'string', required: false }, | |
| bio: { type: 'string', required: false, maxLength: 160 }, | |
| location: { type: 'string', required: false, maxLength: 100 }, | |
| website: { type: 'string', required: false }, | |
| followersCount:{ type: 'number', default: 0 }, | |
| followingCount:{ type: 'number', default: 0 }, | |
| postsCount: { type: 'number', default: 0 }, | |
| isVerified: { type: 'boolean', default: false }, | |
| isPrivate: { type: 'boolean', default: false }, | |
| role: { type: 'enum', values: ['user', 'moderator', 'admin'], default: 'user' }, | |
| joinedAt: { type: 'timestamp', required: true }, | |
| isBanned: { type: 'boolean', default: false }, | |
| }, | |
| // ── Comment ─────────────────────────────────────── | |
| Comment: { | |
| id: { type: 'string', required: true }, | |
| postId: { type: 'string', required: true }, | |
| userId: { type: 'string', required: true }, | |
| text: { type: 'string', required: true, maxLength: 500 }, | |
| likeCount: { type: 'number', default: 0 }, | |
| parentId: { type: 'string', required: false }, // For replies | |
| createdAt: { type: 'timestamp', required: true }, | |
| isHidden: { type: 'boolean', default: false }, | |
| isFlagged: { type: 'boolean', default: false }, | |
| }, | |
| // ── Like ────────────────────────────────────────── | |
| Like: { | |
| id: { type: 'string', required: true }, | |
| userId: { type: 'string', required: true }, | |
| targetId: { type: 'string', required: true }, // Post or Comment ID | |
| targetType:{ type: 'enum', values: ['post', 'comment'], required: true }, | |
| createdAt: { type: 'timestamp', required: true }, | |
| }, | |
| // ── Story ───────────────────────────────────────── | |
| Story: { | |
| id: { type: 'string', required: true }, | |
| userId: { type: 'string', required: true }, | |
| mediaUrl: { type: 'string', required: true }, | |
| expiresAt: { type: 'timestamp', required: true }, | |
| viewCount: { type: 'number', default: 0 }, | |
| isHighlight: { type: 'boolean', default: false }, | |
| }, | |
| // ── SiteConfig ──────────────────────────────────── | |
| SiteConfig: { | |
| siteName: { type: 'string', default: 'GlassGrid' }, | |
| tagline: { type: 'string', default: 'Share Your World' }, | |
| logoUrl: { type: 'string', default: '' }, | |
| activeTheme: { type: 'string', default: 'midnight' }, | |
| allowSignup: { type: 'boolean', default: true }, | |
| features: { | |
| likes: { type: 'boolean', default: true }, | |
| comments: { type: 'boolean', default: true }, | |
| stories: { type: 'boolean', default: true }, | |
| explore: { type: 'boolean', default: true }, | |
| saves: { type: 'boolean', default: true }, | |
| directMessages: { type: 'boolean', default: false }, | |
| }, | |
| moderation: { | |
| requireApproval: { type: 'boolean', default: false }, | |
| autoHideFlags: { type: 'boolean', default: true }, | |
| flagThreshold: { type: 'number', default: 3 }, | |
| allowedDomains: { type: 'array', items: 'string', default: [] }, | |
| }, | |
| limits: { | |
| maxPostsPerDay: { type: 'number', default: 20 }, | |
| maxCaptionLength:{ type: 'number', default: 2200 }, | |
| maxCommentLength:{ type: 'number', default: 500 }, | |
| maxBioLength: { type: 'number', default: 160 }, | |
| }, | |
| }, | |
| }; | |
| /** | |
| * Validate a data object against a schema definition. | |
| * Returns { valid: bool, errors: string[] } | |
| */ | |
| function validate(schemaName, data) { | |
| const schema = Schema[schemaName]; | |
| if (!schema) return { valid: false, errors: [`Unknown schema: ${schemaName}`] }; | |
| const errors = []; | |
| for (const [key, rules] of Object.entries(schema)) { | |
| if (typeof rules !== 'object' || Array.isArray(rules)) continue; | |
| const value = data[key]; | |
| const isDefined = value !== undefined && value !== null; | |
| if (rules.required && !isDefined) { | |
| errors.push(`"${key}" is required`); | |
| continue; | |
| } | |
| if (!isDefined) continue; | |
| if (rules.type === 'string' && typeof value !== 'string') | |
| errors.push(`"${key}" must be a string`); | |
| if (rules.maxLength && typeof value === 'string' && value.length > rules.maxLength) | |
| errors.push(`"${key}" exceeds max length of ${rules.maxLength}`); | |
| if (rules.type === 'enum' && !rules.values.includes(value)) | |
| errors.push(`"${key}" must be one of: ${rules.values.join(', ')}`); | |
| if (rules.type === 'number' && typeof value !== 'number') | |
| errors.push(`"${key}" must be a number`); | |
| if (rules.type === 'boolean' && typeof value !== 'boolean') | |
| errors.push(`"${key}" must be a boolean`); | |
| if (rules.pattern && typeof value === 'string' && !rules.pattern.test(value)) | |
| errors.push(`"${key}" does not match required pattern`); | |
| } | |
| return { valid: errors.length === 0, errors }; | |
| } | |
| /** | |
| * Apply defaults from schema to a data object. | |
| */ | |
| function applyDefaults(schemaName, data = {}) { | |
| const schema = Schema[schemaName]; | |
| if (!schema) return data; | |
| const result = { ...data }; | |
| for (const [key, rules] of Object.entries(schema)) { | |
| if (typeof rules !== 'object' || Array.isArray(rules)) continue; | |
| if (result[key] === undefined && rules.default !== undefined) { | |
| result[key] = typeof rules.default === 'object' | |
| ? JSON.parse(JSON.stringify(rules.default)) | |
| : rules.default; | |
| } | |
| } | |
| return result; | |
| } | |
| export { Schema, validate, applyDefaults }; | |