diff --git a/apps/docs/src/routes.json b/apps/docs/src/routes.json index ea2370bf..89ca1306 100644 --- a/apps/docs/src/routes.json +++ b/apps/docs/src/routes.json @@ -224,7 +224,7 @@ "title": "Cloudflare Pages", "meta": { "title": "Cloudflare Pages", - "description": "Deploy your gallery to Cloudflare Pages for fast global CDN distribution.", + "description": "Guide to deploying Afilmory via Cloudflare Pages.", "createdAt": "2025-07-20T22:35:03+08:00", "lastModified": "2025-11-23T19:40:52+08:00", "order": "53" diff --git a/apps/docs/src/routes.ts b/apps/docs/src/routes.ts index 8d4b61e1..1185a347 100644 --- a/apps/docs/src/routes.ts +++ b/apps/docs/src/routes.ts @@ -282,7 +282,7 @@ export const routes: RouteConfig[] = [ title: 'Cloudflare Pages', meta: { title: 'Cloudflare Pages', - description: 'Deploy your gallery to Cloudflare Pages for fast global CDN distribution.', + description: 'Guide to deploying Afilmory via Cloudflare Pages.', createdAt: '2025-07-20T22:35:03+08:00', lastModified: '2025-11-23T19:40:52+08:00', order: '53', diff --git a/apps/docs/src/toc-data.ts b/apps/docs/src/toc-data.ts index f5c3a0a8..f04ec30c 100644 --- a/apps/docs/src/toc-data.ts +++ b/apps/docs/src/toc-data.ts @@ -17,1830 +17,1848 @@ export interface FileToc { export const tocData: FileToc[] = [ { - "file": "index.mdx", - "path": "/", - "title": "Overview", - "toc": [ + file: 'index.mdx', + path: '/', + title: 'Overview', + toc: [ { - "id": "heading-overview", - "level": 1, - "text": "Overview", - "children": [ + id: 'heading-overview', + level: 1, + text: 'Overview', + children: [ { - "id": "heading-what-is-afilmory", - "level": 2, - "text": "What is Afilmory?", - "children": [] + id: 'heading-what-is-afilmory', + level: 2, + text: 'What is Afilmory?', + children: [], }, { - "id": "heading-key-features", - "level": 2, - "text": "Key Features", - "children": [] + id: 'heading-key-features', + level: 2, + text: 'Key Features', + children: [], }, { - "id": "heading-prerequisites", - "level": 2, - "text": "Prerequisites", - "children": [] + id: 'heading-prerequisites', + level: 2, + text: 'Prerequisites', + children: [], }, { - "id": "heading-quick-start", - "level": 2, - "text": "Quick Start", - "children": [] + id: 'heading-quick-start', + level: 2, + text: 'Quick Start', + children: [], }, { - "id": "heading-deployment-options", - "level": 2, - "text": "Deployment Options", - "children": [ + id: 'heading-deployment-options', + level: 2, + text: 'Deployment Options', + children: [ { - "id": "heading-static-hosting-easiest", - "level": 3, - "text": "Static Hosting (Easiest)", - "children": [] + id: 'heading-static-hosting-easiest', + level: 3, + text: 'Static Hosting (Easiest)', + children: [], }, { - "id": "heading-server-side-rendering-ssr", - "level": 3, - "text": "Server-Side Rendering (SSR)", - "children": [] - } - ] + id: 'heading-server-side-rendering-ssr', + level: 3, + text: 'Server-Side Rendering (SSR)', + children: [], + }, + ], }, { - "id": "heading-next-steps", - "level": 2, - "text": "Next Steps", - "children": [] + id: 'heading-next-steps', + level: 2, + text: 'Next Steps', + children: [], }, { - "id": "heading-need-help", - "level": 2, - "text": "Need Help?", - "children": [] - } - ] - } - ] + id: 'heading-need-help', + level: 2, + text: 'Need Help?', + children: [], + }, + ], + }, + ], }, { - "file": "architecture.mdx", - "path": "/architecture", - "title": "Architecture", - "toc": [ + file: 'architecture.mdx', + path: '/architecture', + title: 'Architecture', + toc: [ { - "id": "heading-architecture", - "level": 1, - "text": "Architecture", - "children": [ + id: 'heading-architecture', + level: 1, + text: 'Architecture', + children: [ { - "id": "heading-overview", - "level": 2, - "text": "Overview", - "children": [] + id: 'heading-overview', + level: 2, + text: 'Overview', + children: [], }, { - "id": "heading-deployment-modes", - "level": 2, - "text": "Deployment Modes", - "children": [ + id: 'heading-deployment-modes', + level: 2, + text: 'Deployment Modes', + children: [ { - "id": "heading-static-site-spa", - "level": 3, - "text": "Static Site (SPA)", - "children": [] + id: 'heading-static-site-spa', + level: 3, + text: 'Static Site (SPA)', + children: [], }, { - "id": "heading-server-side-rendering-ssr", - "level": 3, - "text": "Server-Side Rendering (SSR)", - "children": [] - } - ] + id: 'heading-server-side-rendering-ssr', + level: 3, + text: 'Server-Side Rendering (SSR)', + children: [], + }, + ], }, { - "id": "heading-common-components", - "level": 2, - "text": "Common Components", - "children": [ + id: 'heading-common-components', + level: 2, + text: 'Common Components', + children: [ { - "id": "heading-builder", - "level": 3, - "text": "Builder", - "children": [] + id: 'heading-builder', + level: 3, + text: 'Builder', + children: [], }, { - "id": "heading-manifest", - "level": 3, - "text": "Manifest", - "children": [] + id: 'heading-manifest', + level: 3, + text: 'Manifest', + children: [], }, { - "id": "heading-web-application", - "level": 3, - "text": "Web Application", - "children": [] - } - ] + id: 'heading-web-application', + level: 3, + text: 'Web Application', + children: [], + }, + ], }, { - "id": "heading-data-flow", - "level": 2, - "text": "Data Flow", - "children": [ + id: 'heading-data-flow', + level: 2, + text: 'Data Flow', + children: [ { - "id": "heading-static-mode-flow", - "level": 3, - "text": "Static Mode Flow", - "children": [] + id: 'heading-static-mode-flow', + level: 3, + text: 'Static Mode Flow', + children: [], }, { - "id": "heading-ssr-mode-flow", - "level": 3, - "text": "SSR Mode Flow", - "children": [] - } - ] + id: 'heading-ssr-mode-flow', + level: 3, + text: 'SSR Mode Flow', + children: [], + }, + ], }, { - "id": "heading-choosing-the-right-mode", - "level": 2, - "text": "Choosing the Right Mode", - "children": [] + id: 'heading-choosing-the-right-mode', + level: 2, + text: 'Choosing the Right Mode', + children: [], }, { - "id": "heading-migration-path", - "level": 2, - "text": "Migration Path", - "children": [] - } - ] - } - ] + id: 'heading-migration-path', + level: 2, + text: 'Migration Path', + children: [], + }, + ], + }, + ], }, { - "file": "builder/index.mdx", - "path": "/builder", - "title": "Builder", - "toc": [ + file: 'builder/index.mdx', + path: '/builder', + title: 'Builder', + toc: [ { - "id": "heading-builder", - "level": 1, - "text": "Builder", - "children": [ + id: 'heading-builder', + level: 1, + text: 'Builder', + children: [ { - "id": "heading-what-you-get", - "level": 2, - "text": "What you get", - "children": [] + id: 'heading-what-you-get', + level: 2, + text: 'What you get', + children: [], }, { - "id": "heading-how-to-run-it", - "level": 2, - "text": "How to run it", - "children": [] + id: 'heading-how-to-run-it', + level: 2, + text: 'How to run it', + children: [], }, { - "id": "heading-choose-your-path", - "level": 2, - "text": "Choose your path", - "children": [] + id: 'heading-choose-your-path', + level: 2, + text: 'Choose your path', + children: [], }, { - "id": "heading-prerequisites", - "level": 2, - "text": "Prerequisites", - "children": [] - } - ] - } - ] + id: 'heading-prerequisites', + level: 2, + text: 'Prerequisites', + children: [], + }, + ], + }, + ], }, { - "file": "builder/built-ins.mdx", - "path": "/builder/built-ins", - "title": "Built-in Plugins", - "toc": [ + file: 'builder/built-ins.mdx', + path: '/builder/built-ins', + title: 'Built-in Plugins', + toc: [ { - "id": "heading-built-in-plugins", - "level": 1, - "text": "Built-in Plugins", - "children": [ + id: 'heading-built-in-plugins', + level: 1, + text: 'Built-in Plugins', + children: [ { - "id": "heading-github-repo-sync-githubreposyncplugin", - "level": 2, - "text": "GitHub Repo Sync (`githubRepoSyncPlugin`)", - "children": [] + id: 'heading-github-repo-sync-githubreposyncplugin', + level: 2, + text: 'GitHub Repo Sync (`githubRepoSyncPlugin`)', + children: [], }, { - "id": "heading-thumbnail-storage-afilmorybuilderpluginsthumbnail-storage", - "level": 2, - "text": "Thumbnail Storage (`@afilmory/builder/plugins/thumbnail-storage`)", - "children": [] + id: 'heading-thumbnail-storage-afilmorybuilderpluginsthumbnail-storage', + level: 2, + text: 'Thumbnail Storage (`@afilmory/builder/plugins/thumbnail-storage`)', + children: [], }, { - "id": "heading-use-as-references", - "level": 2, - "text": "Use as references", - "children": [] - } - ] - } - ] + id: 'heading-use-as-references', + level: 2, + text: 'Use as references', + children: [], + }, + ], + }, + ], }, { - "file": "builder/cli.mdx", - "path": "/builder/cli", - "title": "CLI & Run Modes", - "toc": [ + file: 'builder/cli.mdx', + path: '/builder/cli', + title: 'CLI & Run Modes', + toc: [ { - "id": "heading-cli-run-modes", - "level": 1, - "text": "CLI & Run Modes", - "children": [ + id: 'heading-cli-run-modes', + level: 1, + text: 'CLI & Run Modes', + children: [ { - "id": "heading-commands", - "level": 2, - "text": "Commands", - "children": [] + id: 'heading-commands', + level: 2, + text: 'Commands', + children: [], }, { - "id": "heading-concurrency-execution-model", - "level": 2, - "text": "Concurrency & execution model", - "children": [] + id: 'heading-concurrency-execution-model', + level: 2, + text: 'Concurrency & execution model', + children: [], }, { - "id": "heading-incremental-logic-per-file", - "level": 2, - "text": "Incremental logic (per file)", - "children": [] + id: 'heading-incremental-logic-per-file', + level: 2, + text: 'Incremental logic (per file)', + children: [], }, { - "id": "heading-exit-codes-help", - "level": 2, - "text": "Exit codes & help", - "children": [] - } - ] - } - ] + id: 'heading-exit-codes-help', + level: 2, + text: 'Exit codes & help', + children: [], + }, + ], + }, + ], }, { - "file": "builder/config.mdx", - "path": "/builder/config", - "title": "Configuration", - "toc": [ + file: 'builder/config.mdx', + path: '/builder/config', + title: 'Configuration', + toc: [ { - "id": "heading-configuration", - "level": 1, - "text": "Configuration", - "children": [ + id: 'heading-configuration', + level: 1, + text: 'Configuration', + children: [ { - "id": "heading-minimal-template", - "level": 2, - "text": "Minimal template", - "children": [] + id: 'heading-minimal-template', + level: 2, + text: 'Minimal template', + children: [], }, { - "id": "heading-storage-providers", - "level": 2, - "text": "Storage providers", - "children": [] + id: 'heading-storage-providers', + level: 2, + text: 'Storage providers', + children: [], }, { - "id": "heading-system-settings", - "level": 2, - "text": "System settings", - "children": [] + id: 'heading-system-settings', + level: 2, + text: 'System settings', + children: [], }, { - "id": "heading-plugins-array", - "level": 2, - "text": "Plugins array", - "children": [] - } - ] - } - ] + id: 'heading-plugins-array', + level: 2, + text: 'Plugins array', + children: [], + }, + ], + }, + ], }, { - "file": "builder/pipeline.mdx", - "path": "/builder/pipeline", - "title": "Processing Flow", - "toc": [ + file: 'builder/pipeline.mdx', + path: '/builder/pipeline', + title: 'Processing Flow', + toc: [ { - "id": "heading-processing-flow", - "level": 1, - "text": "Processing Flow", - "children": [ + id: 'heading-processing-flow', + level: 1, + text: 'Processing Flow', + children: [ { - "id": "heading-high-level-steps", - "level": 2, - "text": "High-level steps", - "children": [] + id: 'heading-high-level-steps', + level: 2, + text: 'High-level steps', + children: [], }, { - "id": "heading-incremental-rules", - "level": 2, - "text": "Incremental rules", - "children": [] + id: 'heading-incremental-rules', + level: 2, + text: 'Incremental rules', + children: [], }, { - "id": "heading-outputs", - "level": 2, - "text": "Outputs", - "children": [] + id: 'heading-outputs', + level: 2, + text: 'Outputs', + children: [], }, { - "id": "heading-error-handling", - "level": 2, - "text": "Error handling", - "children": [] + id: 'heading-error-handling', + level: 2, + text: 'Error handling', + children: [], }, { - "id": "heading-performance-levers", - "level": 2, - "text": "Performance levers", - "children": [] - } - ] - } - ] + id: 'heading-performance-levers', + level: 2, + text: 'Performance levers', + children: [], + }, + ], + }, + ], }, { - "file": "builder/plugins.mdx", - "path": "/builder/plugins", - "title": "Plugins", - "toc": [ + file: 'builder/plugins.mdx', + path: '/builder/plugins', + title: 'Plugins', + toc: [ { - "id": "heading-plugins", - "level": 1, - "text": "Plugins", - "children": [ + id: 'heading-plugins', + level: 1, + text: 'Plugins', + children: [ { - "id": "heading-lifecycle-surface", - "level": 2, - "text": "Lifecycle surface", - "children": [] + id: 'heading-lifecycle-surface', + level: 2, + text: 'Lifecycle surface', + children: [], }, { - "id": "heading-writing-your-first-plugin", - "level": 2, - "text": "Writing your first plugin", - "children": [] + id: 'heading-writing-your-first-plugin', + level: 2, + text: 'Writing your first plugin', + children: [], }, { - "id": "heading-patterns", - "level": 2, - "text": "Patterns", - "children": [] + id: 'heading-patterns', + level: 2, + text: 'Patterns', + children: [], }, { - "id": "heading-built-in-plugins", - "level": 2, - "text": "Built-in plugins", - "children": [ + id: 'heading-built-in-plugins', + level: 2, + text: 'Built-in plugins', + children: [ { - "id": "heading-github-repo-sync-githubreposyncplugin", - "level": 3, - "text": "GitHub repo sync (`githubRepoSyncPlugin`)", - "children": [] + id: 'heading-github-repo-sync-githubreposyncplugin', + level: 3, + text: 'GitHub repo sync (`githubRepoSyncPlugin`)', + children: [], }, { - "id": "heading-thumbnail-storage-afilmorybuilderpluginsthumbnail-storage", - "level": 3, - "text": "Thumbnail storage (`@afilmory/builder/plugins/thumbnail-storage`)", - "children": [] - } - ] - } - ] - } - ] + id: 'heading-thumbnail-storage-afilmorybuilderpluginsthumbnail-storage', + level: 3, + text: 'Thumbnail storage (`@afilmory/builder/plugins/thumbnail-storage`)', + children: [], + }, + ], + }, + ], + }, + ], }, { - "file": "deployment/index.mdx", - "path": "/deployment", - "title": "Deployment", - "toc": [ + file: 'deployment/index.mdx', + path: '/deployment', + title: 'Deployment', + toc: [ { - "id": "heading-deployment", - "level": 1, - "text": "Deployment", - "children": [ + id: 'heading-deployment', + level: 1, + text: 'Deployment', + children: [ { - "id": "heading-deployment-options", - "level": 2, - "text": "Deployment Options", - "children": [ + id: 'heading-deployment-options', + level: 2, + text: 'Deployment Options', + children: [ { - "id": "heading-static-hosting", - "level": 3, - "text": "Static Hosting", - "children": [] + id: 'heading-static-hosting', + level: 3, + text: 'Static Hosting', + children: [], }, { - "id": "heading-server-side-rendering-ssr", - "level": 3, - "text": "Server-Side Rendering (SSR)", - "children": [] + id: 'heading-server-side-rendering-ssr', + level: 3, + text: 'Server-Side Rendering (SSR)', + children: [], }, { - "id": "heading-docker", - "level": 3, - "text": "Docker", - "children": [] + id: 'heading-docker', + level: 3, + text: 'Docker', + children: [], }, { - "id": "heading-multi-tenant-saas", - "level": 3, - "text": "Multi-tenant SaaS", - "children": [] - } - ] + id: 'heading-multi-tenant-saas', + level: 3, + text: 'Multi-tenant SaaS', + children: [], + }, + ], }, { - "id": "heading-deployment-workflow", - "level": 2, - "text": "Deployment Workflow", - "children": [ + id: 'heading-deployment-workflow', + level: 2, + text: 'Deployment Workflow', + children: [ { - "id": "heading-1-build-assets", - "level": 3, - "text": "1. Build Assets", - "children": [] + id: 'heading-1-build-assets', + level: 3, + text: '1. Build Assets', + children: [], }, { - "id": "heading-2-build-application", - "level": 3, - "text": "2. Build Application", - "children": [] + id: 'heading-2-build-application', + level: 3, + text: '2. Build Application', + children: [], }, { - "id": "heading-3-deploy", - "level": 3, - "text": "3. Deploy", - "children": [] - } - ] + id: 'heading-3-deploy', + level: 3, + text: '3. Deploy', + children: [], + }, + ], }, { - "id": "heading-what-to-deploy", - "level": 2, - "text": "What to Deploy", - "children": [ + id: 'heading-what-to-deploy', + level: 2, + text: 'What to Deploy', + children: [ { - "id": "heading-static-hosting-1", - "level": 3, - "text": "Static Hosting", - "children": [] + id: 'heading-static-hosting-1', + level: 3, + text: 'Static Hosting', + children: [], }, { - "id": "heading-ssr", - "level": 3, - "text": "SSR", - "children": [] + id: 'heading-ssr', + level: 3, + text: 'SSR', + children: [], }, { - "id": "heading-docker-1", - "level": 3, - "text": "Docker", - "children": [] - } - ] + id: 'heading-docker-1', + level: 3, + text: 'Docker', + children: [], + }, + ], }, { - "id": "heading-environment-variables", - "level": 2, - "text": "Environment Variables", - "children": [] + id: 'heading-environment-variables', + level: 2, + text: 'Environment Variables', + children: [], }, { - "id": "heading-continuous-deployment", - "level": 2, - "text": "Continuous Deployment", - "children": [] + id: 'heading-continuous-deployment', + level: 2, + text: 'Continuous Deployment', + children: [], }, { - "id": "heading-custom-domains", - "level": 2, - "text": "Custom Domains", - "children": [] + id: 'heading-custom-domains', + level: 2, + text: 'Custom Domains', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] - } - ] - } - ] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], + }, + ], + }, + ], }, { - "file": "deployment/cloudflare-pages.mdx", - "path": "/deployment/cloudflare-pages", - "title": "Cloudflare Pages", - "toc": [ + file: 'deployment/cloudflare-pages.mdx', + path: '/deployment/cloudflare-pages', + title: 'Cloudflare Pages', + toc: [ { - "id": "heading-cloudflare-pages-deployment", - "level": 1, - "text": "Cloudflare Pages Deployment", - "children": [ + id: 'heading-cloudflare-pages-deployment', + level: 1, + text: 'Cloudflare Pages Deployment', + children: [ { - "id": "heading-prerequisites", - "level": 2, - "text": "Prerequisites", - "children": [] + id: 'heading-prerequisites', + level: 2, + text: 'Prerequisites', + children: [], }, { - "id": "heading-deployment-steps", - "level": 2, - "text": "Deployment Steps", - "children": [ + id: 'heading-quick-deploy', + level: 2, + text: 'Quick Deploy', + children: [], + }, + { + id: 'heading-deployment-steps', + level: 2, + text: 'Deployment Steps', + children: [ { - "id": "heading-step-1-connect-repository", - "level": 3, - "text": "Step 1: Connect Repository", - "children": [] + id: 'heading-step-1-connect-repository', + level: 3, + text: 'Step 1: Connect Repository', + children: [], }, { - "id": "heading-step-2-configure-build-settings", - "level": 3, - "text": "Step 2: Configure Build Settings", - "children": [] + id: 'heading-step-2-configure-build-settings', + level: 3, + text: 'Step 2: Configure Build Settings', + children: [], }, { - "id": "heading-step-3-deploy", - "level": 3, - "text": "Step 3: Deploy", - "children": [] - } - ] + id: 'heading-step-3-deploy', + level: 3, + text: 'Step 3: Deploy', + children: [], + }, + ], }, { - "id": "heading-build-configuration", - "level": 2, - "text": "Build Configuration", - "children": [ + id: 'heading-build-configuration', + level: 2, + text: 'Build Configuration', + children: [ { - "id": "heading-using-wranglertoml-optional", - "level": 3, - "text": "Using wrangler.toml (Optional)", - "children": [] + id: 'heading-using-wranglertoml-optional', + level: 3, + text: 'Using wrangler.toml (Optional)', + children: [], }, { - "id": "heading-environment-variables", - "level": 3, - "text": "Environment Variables", - "children": [] - } - ] + id: 'heading-environment-variables', + level: 3, + text: 'Environment Variables', + children: [], + }, + ], }, { - "id": "heading-custom-domain", - "level": 2, - "text": "Custom Domain", - "children": [] + id: 'heading-custom-domain', + level: 2, + text: 'Custom Domain', + children: [], }, { - "id": "heading-continuous-deployment", - "level": 2, - "text": "Continuous Deployment", - "children": [] + id: 'heading-continuous-deployment', + level: 2, + text: 'Continuous Deployment', + children: [], }, { - "id": "heading-performance-features", - "level": 2, - "text": "Performance Features", - "children": [] + id: 'heading-performance-features', + level: 2, + text: 'Performance Features', + children: [], }, { - "id": "heading-build-limits", - "level": 2, - "text": "Build Limits", - "children": [] + id: 'heading-build-limits', + level: 2, + text: 'Build Limits', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], }, { - "id": "heading-next-steps", - "level": 2, - "text": "Next Steps", - "children": [] - } - ] - } - ] + id: 'heading-limitations', + level: 2, + text: 'Limitations', + children: [], + }, + { + id: 'heading-next-steps', + level: 2, + text: 'Next Steps', + children: [], + }, + { + id: 'heading-learn-more', + level: 2, + text: 'Learn more', + children: [], + }, + ], + }, + ], }, { - "file": "deployment/docker.mdx", - "path": "/deployment/docker", - "title": "Docker", - "toc": [ + file: 'deployment/docker.mdx', + path: '/deployment/docker', + title: 'Docker', + toc: [ { - "id": "heading-docker-deployment", - "level": 1, - "text": "Docker Deployment", - "children": [ + id: 'heading-docker-deployment', + level: 1, + text: 'Docker Deployment', + children: [ { - "id": "heading-prerequisites", - "level": 2, - "text": "Prerequisites", - "children": [] + id: 'heading-prerequisites', + level: 2, + text: 'Prerequisites', + children: [], }, { - "id": "heading-quick-start", - "level": 2, - "text": "Quick Start", - "children": [ + id: 'heading-quick-start', + level: 2, + text: 'Quick Start', + children: [ { - "id": "heading-option-1-use-pre-built-docker-setup", - "level": 3, - "text": "Option 1: Use Pre-built Docker Setup", - "children": [] + id: 'heading-option-1-use-pre-built-docker-setup', + level: 3, + text: 'Option 1: Use Pre-built Docker Setup', + children: [], }, { - "id": "heading-option-2-build-from-source", - "level": 3, - "text": "Option 2: Build from Source", - "children": [] - } - ] + id: 'heading-option-2-build-from-source', + level: 3, + text: 'Option 2: Build from Source', + children: [], + }, + ], }, { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [ + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [ { - "id": "heading-configjson", - "level": 3, - "text": "config.json", - "children": [] + id: 'heading-configjson', + level: 3, + text: 'config.json', + children: [], }, { - "id": "heading-builderconfigts", - "level": 3, - "text": "builder.config.ts", - "children": [] + id: 'heading-builderconfigts', + level: 3, + text: 'builder.config.ts', + children: [], }, { - "id": "heading-env", - "level": 3, - "text": ".env", - "children": [] - } - ] + id: 'heading-env', + level: 3, + text: '.env', + children: [], + }, + ], }, { - "id": "heading-dockerfile", - "level": 2, - "text": "Dockerfile", - "children": [] + id: 'heading-dockerfile', + level: 2, + text: 'Dockerfile', + children: [], }, { - "id": "heading-docker-compose", - "level": 2, - "text": "Docker Compose", - "children": [] + id: 'heading-docker-compose', + level: 2, + text: 'Docker Compose', + children: [], }, { - "id": "heading-building-and-running", - "level": 2, - "text": "Building and Running", - "children": [ + id: 'heading-building-and-running', + level: 2, + text: 'Building and Running', + children: [ { - "id": "heading-using-docker-compose-recommended", - "level": 3, - "text": "Using Docker Compose (Recommended)", - "children": [] + id: 'heading-using-docker-compose-recommended', + level: 3, + text: 'Using Docker Compose (Recommended)', + children: [], }, { - "id": "heading-manual-docker-build", - "level": 3, - "text": "Manual Docker Build", - "children": [] - } - ] + id: 'heading-manual-docker-build', + level: 3, + text: 'Manual Docker Build', + children: [], + }, + ], }, { - "id": "heading-storage-providers", - "level": 2, - "text": "Storage Providers", - "children": [] + id: 'heading-storage-providers', + level: 2, + text: 'Storage Providers', + children: [], }, { - "id": "heading-performance-tuning", - "level": 2, - "text": "Performance Tuning", - "children": [] + id: 'heading-performance-tuning', + level: 2, + text: 'Performance Tuning', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], }, { - "id": "heading-production-considerations", - "level": 2, - "text": "Production Considerations", - "children": [] + id: 'heading-production-considerations', + level: 2, + text: 'Production Considerations', + children: [], }, { - "id": "heading-next-steps", - "level": 2, - "text": "Next Steps", - "children": [] - } - ] - } - ] + id: 'heading-next-steps', + level: 2, + text: 'Next Steps', + children: [], + }, + ], + }, + ], }, { - "file": "deployment/github-pages.mdx", - "path": "/deployment/github-pages", - "title": "GitHub Pages", - "toc": [ + file: 'deployment/github-pages.mdx', + path: '/deployment/github-pages', + title: 'GitHub Pages', + toc: [ { - "id": "heading-github-pages-deployment", - "level": 1, - "text": "GitHub Pages Deployment", - "children": [ + id: 'heading-github-pages-deployment', + level: 1, + text: 'GitHub Pages Deployment', + children: [ { - "id": "heading-prerequisites", - "level": 2, - "text": "Prerequisites", - "children": [] + id: 'heading-prerequisites', + level: 2, + text: 'Prerequisites', + children: [], }, { - "id": "heading-deployment-steps", - "level": 2, - "text": "Deployment Steps", - "children": [ + id: 'heading-deployment-steps', + level: 2, + text: 'Deployment Steps', + children: [ { - "id": "heading-step-1-build-your-gallery", - "level": 3, - "text": "Step 1: Build Your Gallery", - "children": [] + id: 'heading-step-1-build-your-gallery', + level: 3, + text: 'Step 1: Build Your Gallery', + children: [], }, { - "id": "heading-step-2-prepare-files-for-deployment", - "level": 3, - "text": "Step 2: Prepare Files for Deployment", - "children": [] + id: 'heading-step-2-prepare-files-for-deployment', + level: 3, + text: 'Step 2: Prepare Files for Deployment', + children: [], }, { - "id": "heading-step-3-deploy-to-github-pages", - "level": 3, - "text": "Step 3: Deploy to GitHub Pages", - "children": [ + id: 'heading-step-3-deploy-to-github-pages', + level: 3, + text: 'Step 3: Deploy to GitHub Pages', + children: [ { - "id": "heading-option-a-gh-pages-branch-simplest", - "level": 4, - "text": "Option A: gh-pages Branch (Simplest)", - "children": [] + id: 'heading-option-a-gh-pages-branch-simplest', + level: 4, + text: 'Option A: gh-pages Branch (Simplest)', + children: [], }, { - "id": "heading-option-b-github-actions-automated", - "level": 4, - "text": "Option B: GitHub Actions (Automated)", - "children": [] - } - ] + id: 'heading-option-b-github-actions-automated', + level: 4, + text: 'Option B: GitHub Actions (Automated)', + children: [], + }, + ], }, { - "id": "heading-step-4-configure-github-pages", - "level": 3, - "text": "Step 4: Configure GitHub Pages", - "children": [] - } - ] + id: 'heading-step-4-configure-github-pages', + level: 3, + text: 'Step 4: Configure GitHub Pages', + children: [], + }, + ], }, { - "id": "heading-custom-domain", - "level": 2, - "text": "Custom Domain", - "children": [] + id: 'heading-custom-domain', + level: 2, + text: 'Custom Domain', + children: [], }, { - "id": "heading-updating-your-gallery", - "level": 2, - "text": "Updating Your Gallery", - "children": [] + id: 'heading-updating-your-gallery', + level: 2, + text: 'Updating Your Gallery', + children: [], }, { - "id": "heading-limitations", - "level": 2, - "text": "Limitations", - "children": [] + id: 'heading-limitations', + level: 2, + text: 'Limitations', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] - } - ] - } - ] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], + }, + ], + }, + ], }, { - "file": "deployment/vercel.mdx", - "path": "/deployment/vercel", - "title": "Vercel", - "toc": [ + file: 'deployment/vercel.mdx', + path: '/deployment/vercel', + title: 'Vercel', + toc: [ { - "id": "heading-vercel-deployment", - "level": 1, - "text": "Vercel Deployment", - "children": [ + id: 'heading-vercel-deployment', + level: 1, + text: 'Vercel Deployment', + children: [ { - "id": "heading-prerequisites", - "level": 2, - "text": "Prerequisites", - "children": [] + id: 'heading-prerequisites', + level: 2, + text: 'Prerequisites', + children: [], }, { - "id": "heading-quick-start", - "level": 2, - "text": "Quick Start", - "children": [ + id: 'heading-quick-start', + level: 2, + text: 'Quick Start', + children: [ { - "id": "heading-option-1-deploy-from-git-recommended", - "level": 3, - "text": "Option 1: Deploy from Git (Recommended)", - "children": [] + id: 'heading-option-1-deploy-from-git-recommended', + level: 3, + text: 'Option 1: Deploy from Git (Recommended)', + children: [], }, { - "id": "heading-option-2-deploy-with-pre-built-assets", - "level": 3, - "text": "Option 2: Deploy with Pre-built Assets", - "children": [] - } - ] + id: 'heading-option-2-deploy-with-pre-built-assets', + level: 3, + text: 'Option 2: Deploy with Pre-built Assets', + children: [], + }, + ], }, { - "id": "heading-project-configuration", - "level": 2, - "text": "Project Configuration", - "children": [ + id: 'heading-project-configuration', + level: 2, + text: 'Project Configuration', + children: [ { - "id": "heading-verceljson-optional", - "level": 3, - "text": "vercel.json (Optional)", - "children": [] + id: 'heading-verceljson-optional', + level: 3, + text: 'vercel.json (Optional)', + children: [], }, { - "id": "heading-environment-variables", - "level": 3, - "text": "Environment Variables", - "children": [] - } - ] + id: 'heading-environment-variables', + level: 3, + text: 'Environment Variables', + children: [], + }, + ], }, { - "id": "heading-build-process", - "level": 2, - "text": "Build Process", - "children": [] + id: 'heading-build-process', + level: 2, + text: 'Build Process', + children: [], }, { - "id": "heading-performance-optimization", - "level": 2, - "text": "Performance Optimization", - "children": [ + id: 'heading-performance-optimization', + level: 2, + text: 'Performance Optimization', + children: [ { - "id": "heading-use-repo-cache-plugin", - "level": 3, - "text": "Use Repo Cache Plugin", - "children": [] + id: 'heading-use-repo-cache-plugin', + level: 3, + text: 'Use Repo Cache Plugin', + children: [], }, { - "id": "heading-build-caching", - "level": 3, - "text": "Build Caching", - "children": [] - } - ] + id: 'heading-build-caching', + level: 3, + text: 'Build Caching', + children: [], + }, + ], }, { - "id": "heading-custom-domain", - "level": 2, - "text": "Custom Domain", - "children": [] + id: 'heading-custom-domain', + level: 2, + text: 'Custom Domain', + children: [], }, { - "id": "heading-continuous-deployment", - "level": 2, - "text": "Continuous Deployment", - "children": [] + id: 'heading-continuous-deployment', + level: 2, + text: 'Continuous Deployment', + children: [], }, { - "id": "heading-monitoring-and-analytics", - "level": 2, - "text": "Monitoring and Analytics", - "children": [] + id: 'heading-monitoring-and-analytics', + level: 2, + text: 'Monitoring and Analytics', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], }, { - "id": "heading-next-steps", - "level": 2, - "text": "Next Steps", - "children": [] - } - ] - } - ] + id: 'heading-next-steps', + level: 2, + text: 'Next Steps', + children: [], + }, + ], + }, + ], }, { - "file": "docs-site.mdx", - "path": "/docs-site", - "title": "Docs Site", - "toc": [ + file: 'docs-site.mdx', + path: '/docs-site', + title: 'Docs Site', + toc: [ { - "id": "heading-afilmory-docs-site", - "level": 1, - "text": "Afilmory Docs Site", - "children": [ + id: 'heading-afilmory-docs-site', + level: 1, + text: 'Afilmory Docs Site', + children: [ { - "id": "heading-features", - "level": 2, - "text": "Features", - "children": [] + id: 'heading-features', + level: 2, + text: 'Features', + children: [], }, { - "id": "heading-quick-start", - "level": 2, - "text": "Quick Start", - "children": [ + id: 'heading-quick-start', + level: 2, + text: 'Quick Start', + children: [ { - "id": "heading-development", - "level": 3, - "text": "Development", - "children": [] + id: 'heading-development', + level: 3, + text: 'Development', + children: [], }, { - "id": "heading-building", - "level": 3, - "text": "Building", - "children": [] + id: 'heading-building', + level: 3, + text: 'Building', + children: [], }, { - "id": "heading-preview", - "level": 3, - "text": "Preview", - "children": [] - } - ] + id: 'heading-preview', + level: 3, + text: 'Preview', + children: [], + }, + ], }, { - "id": "heading-project-structure", - "level": 2, - "text": "Project Structure", - "children": [] + id: 'heading-project-structure', + level: 2, + text: 'Project Structure', + children: [], }, { - "id": "heading-writing-documentation", - "level": 2, - "text": "Writing Documentation", - "children": [ + id: 'heading-writing-documentation', + level: 2, + text: 'Writing Documentation', + children: [ { - "id": "heading-file-based-routing", - "level": 3, - "text": "File-based Routing", - "children": [] + id: 'heading-file-based-routing', + level: 3, + text: 'File-based Routing', + children: [], }, { - "id": "heading-mdx-format", - "level": 3, - "text": "MDX Format", - "children": [] + id: 'heading-mdx-format', + level: 3, + text: 'MDX Format', + children: [], }, { - "id": "heading-frontmatter", - "level": 3, - "text": "Frontmatter", - "children": [] - } - ] + id: 'heading-frontmatter', + level: 3, + text: 'Frontmatter', + children: [], + }, + ], }, { - "id": "heading-development-guide", - "level": 2, - "text": "Development Guide", - "children": [ + id: 'heading-development-guide', + level: 2, + text: 'Development Guide', + children: [ { - "id": "heading-adding-new-content", - "level": 3, - "text": "Adding New Content", - "children": [] + id: 'heading-adding-new-content', + level: 3, + text: 'Adding New Content', + children: [], }, { - "id": "heading-custom-components", - "level": 3, - "text": "Custom Components", - "children": [] + id: 'heading-custom-components', + level: 3, + text: 'Custom Components', + children: [], }, { - "id": "heading-styling", - "level": 3, - "text": "Styling", - "children": [] + id: 'heading-styling', + level: 3, + text: 'Styling', + children: [], }, { - "id": "heading-route-generation-plugin", - "level": 3, - "text": "Route Generation Plugin", - "children": [] - } - ] + id: 'heading-route-generation-plugin', + level: 3, + text: 'Route Generation Plugin', + children: [], + }, + ], }, { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [ + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [ { - "id": "heading-vite-configuration", - "level": 3, - "text": "Vite Configuration", - "children": [] + id: 'heading-vite-configuration', + level: 3, + text: 'Vite Configuration', + children: [], }, { - "id": "heading-supported-languages", - "level": 3, - "text": "Supported Languages", - "children": [] - } - ] + id: 'heading-supported-languages', + level: 3, + text: 'Supported Languages', + children: [], + }, + ], }, { - "id": "heading-deployment", - "level": 2, - "text": "Deployment", - "children": [] + id: 'heading-deployment', + level: 2, + text: 'Deployment', + children: [], }, { - "id": "heading-scripts-reference", - "level": 2, - "text": "Scripts Reference", - "children": [] + id: 'heading-scripts-reference', + level: 2, + text: 'Scripts Reference', + children: [], }, { - "id": "heading-contributing", - "level": 2, - "text": "Contributing", - "children": [] - } - ] - } - ] + id: 'heading-contributing', + level: 2, + text: 'Contributing', + children: [], + }, + ], + }, + ], }, { - "file": "getting-started/quick-start.mdx", - "path": "/getting-started/quick-start", - "title": "Quick Start", - "toc": [ + file: 'getting-started/quick-start.mdx', + path: '/getting-started/quick-start', + title: 'Quick Start', + toc: [ { - "id": "heading-quick-start", - "level": 1, - "text": "Quick Start", - "children": [ + id: 'heading-quick-start', + level: 1, + text: 'Quick Start', + children: [ { - "id": "heading-step-1-configure-your-site", - "level": 2, - "text": "Step 1: Configure Your Site", - "children": [] + id: 'heading-step-1-configure-your-site', + level: 2, + text: 'Step 1: Configure Your Site', + children: [], }, { - "id": "heading-step-2-choose-and-configure-storage", - "level": 2, - "text": "Step 2: Choose and Configure Storage", - "children": [] + id: 'heading-step-2-choose-and-configure-storage', + level: 2, + text: 'Step 2: Choose and Configure Storage', + children: [], }, { - "id": "heading-step-3-generate-thumbnails-and-manifest", - "level": 2, - "text": "Step 3: Generate Thumbnails and Manifest", - "children": [] + id: 'heading-step-3-generate-thumbnails-and-manifest', + level: 2, + text: 'Step 3: Generate Thumbnails and Manifest', + children: [], }, { - "id": "heading-step-4-preview-locally", - "level": 2, - "text": "Step 4: Preview Locally", - "children": [] + id: 'heading-step-4-preview-locally', + level: 2, + text: 'Step 4: Preview Locally', + children: [], }, { - "id": "heading-step-5-deploy", - "level": 2, - "text": "Step 5: Deploy", - "children": [ + id: 'heading-step-5-deploy', + level: 2, + text: 'Step 5: Deploy', + children: [ { - "id": "heading-static-hosting", - "level": 3, - "text": "Static Hosting", - "children": [] + id: 'heading-static-hosting', + level: 3, + text: 'Static Hosting', + children: [], }, { - "id": "heading-server-side-rendering-ssr", - "level": 3, - "text": "Server-Side Rendering (SSR)", - "children": [] - } - ] + id: 'heading-server-side-rendering-ssr', + level: 3, + text: 'Server-Side Rendering (SSR)', + children: [], + }, + ], }, { - "id": "heading-what-you-have-now", - "level": 2, - "text": "What You Have Now", - "children": [] + id: 'heading-what-you-have-now', + level: 2, + text: 'What You Have Now', + children: [], }, { - "id": "heading-next-steps", - "level": 2, - "text": "Next Steps", - "children": [] - } - ] - } - ] + id: 'heading-next-steps', + level: 2, + text: 'Next Steps', + children: [], + }, + ], + }, + ], }, { - "file": "saas/index.mdx", - "path": "/saas", - "title": "SaaS Mode", - "toc": [ + file: 'saas/index.mdx', + path: '/saas', + title: 'SaaS Mode', + toc: [ { - "id": "heading-saas-mode", - "level": 1, - "text": "SaaS Mode", - "children": [ + id: 'heading-saas-mode', + level: 1, + text: 'SaaS Mode', + children: [ { - "id": "heading-why-saas", - "level": 2, - "text": "Why SaaS?", - "children": [] + id: 'heading-why-saas', + level: 2, + text: 'Why SaaS?', + children: [], }, { - "id": "heading-what-you-can-do", - "level": 2, - "text": "What you can do", - "children": [] + id: 'heading-what-you-can-do', + level: 2, + text: 'What you can do', + children: [], }, { - "id": "heading-architecture-at-a-glance", - "level": 2, - "text": "Architecture at a glance", - "children": [] + id: 'heading-architecture-at-a-glance', + level: 2, + text: 'Architecture at a glance', + children: [], }, { - "id": "heading-licensing-note", - "level": 2, - "text": "Licensing note", - "children": [] - } - ] - } - ] + id: 'heading-licensing-note', + level: 2, + text: 'Licensing note', + children: [], + }, + ], + }, + ], }, { - "file": "saas/architecture.mdx", - "path": "/saas/architecture", - "title": "SaaS Architecture", - "toc": [ + file: 'saas/architecture.mdx', + path: '/saas/architecture', + title: 'SaaS Architecture', + toc: [ { - "id": "heading-saas-architecture", - "level": 1, - "text": "SaaS Architecture", - "children": [ + id: 'heading-saas-architecture', + level: 1, + text: 'SaaS Architecture', + children: [ { - "id": "heading-tenant-model", - "level": 2, - "text": "Tenant model", - "children": [] + id: 'heading-tenant-model', + level: 2, + text: 'Tenant model', + children: [], }, { - "id": "heading-domains", - "level": 2, - "text": "Domains", - "children": [] + id: 'heading-domains', + level: 2, + text: 'Domains', + children: [], }, { - "id": "heading-tenant-resolution-core", - "level": 2, - "text": "Tenant resolution (core)", - "children": [] + id: 'heading-tenant-resolution-core', + level: 2, + text: 'Tenant resolution (core)', + children: [], }, { - "id": "heading-oauth-gateway-flow", - "level": 2, - "text": "OAuth gateway flow", - "children": [] + id: 'heading-oauth-gateway-flow', + level: 2, + text: 'OAuth gateway flow', + children: [], }, { - "id": "heading-data-path", - "level": 2, - "text": "Data path", - "children": [] + id: 'heading-data-path', + level: 2, + text: 'Data path', + children: [], }, { - "id": "heading-static-assets-roots", - "level": 2, - "text": "Static assets roots", - "children": [] + id: 'heading-static-assets-roots', + level: 2, + text: 'Static assets roots', + children: [], }, { - "id": "heading-billing-plans", - "level": 2, - "text": "Billing & plans", - "children": [] + id: 'heading-billing-plans', + level: 2, + text: 'Billing & plans', + children: [], }, { - "id": "heading-root-account-provisioning", - "level": 2, - "text": "Root account provisioning", - "children": [] - } - ] - } - ] + id: 'heading-root-account-provisioning', + level: 2, + text: 'Root account provisioning', + children: [], + }, + ], + }, + ], }, { - "file": "saas/cms.mdx", - "path": "/saas/cms", - "title": "CMS & Live Updates", - "toc": [ + file: 'saas/cms.mdx', + path: '/saas/cms', + title: 'CMS & Live Updates', + toc: [ { - "id": "heading-cms-live-updates", - "level": 1, - "text": "CMS & Live Updates", - "children": [ + id: 'heading-cms-live-updates', + level: 1, + text: 'CMS & Live Updates', + children: [ { - "id": "heading-how-it-works", - "level": 2, - "text": "How it works", - "children": [] + id: 'heading-how-it-works', + level: 2, + text: 'How it works', + children: [], }, { - "id": "heading-editing-flow-dashboardcms", - "level": 2, - "text": "Editing flow (dashboard/CMS)", - "children": [] + id: 'heading-editing-flow-dashboardcms', + level: 2, + text: 'Editing flow (dashboard/CMS)', + children: [], }, { - "id": "heading-when-to-run-the-builder", - "level": 2, - "text": "When to run the builder", - "children": [] + id: 'heading-when-to-run-the-builder', + level: 2, + text: 'When to run the builder', + children: [], }, { - "id": "heading-caching-behavior", - "level": 2, - "text": "Caching behavior", - "children": [] + id: 'heading-caching-behavior', + level: 2, + text: 'Caching behavior', + children: [], }, { - "id": "heading-permissions", - "level": 2, - "text": "Permissions", - "children": [] + id: 'heading-permissions', + level: 2, + text: 'Permissions', + children: [], }, { - "id": "heading-gotchas", - "level": 2, - "text": "Gotchas", - "children": [] - } - ] - } - ] + id: 'heading-gotchas', + level: 2, + text: 'Gotchas', + children: [], + }, + ], + }, + ], }, { - "file": "saas/deployment.mdx", - "path": "/saas/deployment", - "title": "SaaS Deployment", - "toc": [ + file: 'saas/deployment.mdx', + path: '/saas/deployment', + title: 'SaaS Deployment', + toc: [ { - "id": "heading-saas-deployment", - "level": 1, - "text": "SaaS Deployment", - "children": [ + id: 'heading-saas-deployment', + level: 1, + text: 'SaaS Deployment', + children: [ { - "id": "heading-components", - "level": 2, - "text": "Components", - "children": [] + id: 'heading-components', + level: 2, + text: 'Components', + children: [], }, { - "id": "heading-domain-model-dns", - "level": 2, - "text": "Domain model & DNS", - "children": [] + id: 'heading-domain-model-dns', + level: 2, + text: 'Domain model & DNS', + children: [], }, { - "id": "heading-quick-start-with-docker-core", - "level": 2, - "text": "Quick start with Docker (core)", - "children": [ + id: 'heading-quick-start-with-docker-core', + level: 2, + text: 'Quick start with Docker (core)', + children: [ { - "id": "heading-minimal-env-core", - "level": 3, - "text": "Minimal env (core)", - "children": [] - } - ] + id: 'heading-minimal-env-core', + level: 3, + text: 'Minimal env (core)', + children: [], + }, + ], }, { - "id": "heading-oauth-gateway", - "level": 2, - "text": "OAuth Gateway", - "children": [] + id: 'heading-oauth-gateway', + level: 2, + text: 'OAuth Gateway', + children: [], }, { - "id": "heading-first-login-flow", - "level": 2, - "text": "First-login flow", - "children": [] + id: 'heading-first-login-flow', + level: 2, + text: 'First-login flow', + children: [], }, { - "id": "heading-request-routing", - "level": 2, - "text": "Request routing", - "children": [] + id: 'heading-request-routing', + level: 2, + text: 'Request routing', + children: [], }, { - "id": "heading-deployment-checklist", - "level": 2, - "text": "Deployment checklist", - "children": [] + id: 'heading-deployment-checklist', + level: 2, + text: 'Deployment checklist', + children: [], }, { - "id": "heading-licensing-notice-commercial-use", - "level": 2, - "text": "Licensing notice (commercial use)", - "children": [] - } - ] - } - ] + id: 'heading-licensing-notice-commercial-use', + level: 2, + text: 'Licensing notice (commercial use)', + children: [], + }, + ], + }, + ], }, { - "file": "storage/providers/index.mdx", - "path": "/storage/providers", - "title": "Storage Providers", - "toc": [ + file: 'storage/providers/index.mdx', + path: '/storage/providers', + title: 'Storage Providers', + toc: [ { - "id": "heading-storage-providers", - "level": 1, - "text": "Storage Providers", - "children": [ + id: 'heading-storage-providers', + level: 1, + text: 'Storage Providers', + children: [ { - "id": "heading-available-providers", - "level": 2, - "text": "Available Providers", - "children": [ + id: 'heading-available-providers', + level: 2, + text: 'Available Providers', + children: [ { - "id": "heading-s3-s3-compatiblestorageproviderss3", - "level": 3, - "text": "[S3 / S3-Compatible](/storage/providers/s3)", - "children": [] + id: 'heading-s3-s3-compatiblestorageproviderss3', + level: 3, + text: '[S3 / S3-Compatible](/storage/providers/s3)', + children: [], }, { - "id": "heading-b2-backblaze-b2storageprovidersb2", - "level": 3, - "text": "[B2 (Backblaze B2)](/storage/providers/b2)", - "children": [] + id: 'heading-b2-backblaze-b2storageprovidersb2', + level: 3, + text: '[B2 (Backblaze B2)](/storage/providers/b2)', + children: [], }, { - "id": "heading-githubstorageprovidersgithub", - "level": 3, - "text": "[GitHub](/storage/providers/github)", - "children": [] + id: 'heading-githubstorageprovidersgithub', + level: 3, + text: '[GitHub](/storage/providers/github)', + children: [], }, { - "id": "heading-localstorageproviderslocal", - "level": 3, - "text": "[Local](/storage/providers/local)", - "children": [] + id: 'heading-localstorageproviderslocal', + level: 3, + text: '[Local](/storage/providers/local)', + children: [], }, { - "id": "heading-eaglestorageproviderseagle", - "level": 3, - "text": "[Eagle](/storage/providers/eagle)", - "children": [] - } - ] + id: 'heading-eaglestorageproviderseagle', + level: 3, + text: '[Eagle](/storage/providers/eagle)', + children: [], + }, + ], }, { - "id": "heading-choosing-a-provider", - "level": 2, - "text": "Choosing a Provider", - "children": [] + id: 'heading-choosing-a-provider', + level: 2, + text: 'Choosing a Provider', + children: [], }, { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [] - } - ] - } - ] + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [], + }, + ], + }, + ], }, { - "file": "storage/providers/b2.mdx", - "path": "/storage/providers/b2", - "title": "B2 (Backblaze B2)", - "toc": [ + file: 'storage/providers/b2.mdx', + path: '/storage/providers/b2', + title: 'B2 (Backblaze B2)', + toc: [ { - "id": "heading-b2-backblaze-b2-storage", - "level": 1, - "text": "B2 (Backblaze B2) Storage", - "children": [ + id: 'heading-b2-backblaze-b2-storage', + level: 1, + text: 'B2 (Backblaze B2) Storage', + children: [ { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [] + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [], }, { - "id": "heading-environment-variables", - "level": 2, - "text": "Environment Variables", - "children": [] + id: 'heading-environment-variables', + level: 2, + text: 'Environment Variables', + children: [], }, { - "id": "heading-getting-b2-credentials", - "level": 2, - "text": "Getting B2 Credentials", - "children": [] + id: 'heading-getting-b2-credentials', + level: 2, + text: 'Getting B2 Credentials', + children: [], }, { - "id": "heading-cdn-integration", - "level": 2, - "text": "CDN Integration", - "children": [] + id: 'heading-cdn-integration', + level: 2, + text: 'CDN Integration', + children: [], }, { - "id": "heading-prefix-organization", - "level": 2, - "text": "Prefix Organization", - "children": [] + id: 'heading-prefix-organization', + level: 2, + text: 'Prefix Organization', + children: [], }, { - "id": "heading-cost-considerations", - "level": 2, - "text": "Cost Considerations", - "children": [] + id: 'heading-cost-considerations', + level: 2, + text: 'Cost Considerations', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] - } - ] - } - ] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], + }, + ], + }, + ], }, { - "file": "storage/providers/eagle.mdx", - "path": "/storage/providers/eagle", - "title": "Eagle Storage", - "toc": [ + file: 'storage/providers/eagle.mdx', + path: '/storage/providers/eagle', + title: 'Eagle Storage', + toc: [ { - "id": "heading-eagle-storage", - "level": 1, - "text": "Eagle Storage", - "children": [ + id: 'heading-eagle-storage', + level: 1, + text: 'Eagle Storage', + children: [ { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [] + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [], }, { - "id": "heading-path-configuration", - "level": 2, - "text": "Path Configuration", - "children": [ + id: 'heading-path-configuration', + level: 2, + text: 'Path Configuration', + children: [ { - "id": "heading-librarypath", - "level": 3, - "text": "libraryPath", - "children": [] + id: 'heading-librarypath', + level: 3, + text: 'libraryPath', + children: [], }, { - "id": "heading-distpath", - "level": 3, - "text": "distPath", - "children": [] + id: 'heading-distpath', + level: 3, + text: 'distPath', + children: [], }, { - "id": "heading-baseurl", - "level": 3, - "text": "baseUrl", - "children": [] - } - ] + id: 'heading-baseurl', + level: 3, + text: 'baseUrl', + children: [], + }, + ], }, { - "id": "heading-filtering", - "level": 2, - "text": "Filtering", - "children": [ + id: 'heading-filtering', + level: 2, + text: 'Filtering', + children: [ { - "id": "heading-include-rules", - "level": 3, - "text": "Include Rules", - "children": [] + id: 'heading-include-rules', + level: 3, + text: 'Include Rules', + children: [], }, { - "id": "heading-exclude-rules", - "level": 3, - "text": "Exclude Rules", - "children": [] + id: 'heading-exclude-rules', + level: 3, + text: 'Exclude Rules', + children: [], }, { - "id": "heading-empty-filters", - "level": 3, - "text": "Empty Filters", - "children": [] - } - ] + id: 'heading-empty-filters', + level: 3, + text: 'Empty Filters', + children: [], + }, + ], }, { - "id": "heading-folder-as-tag", - "level": 2, - "text": "Folder as Tag", - "children": [] + id: 'heading-folder-as-tag', + level: 2, + text: 'Folder as Tag', + children: [], }, { - "id": "heading-use-cases", - "level": 2, - "text": "Use Cases", - "children": [] + id: 'heading-use-cases', + level: 2, + text: 'Use Cases', + children: [], }, { - "id": "heading-best-practices", - "level": 2, - "text": "Best Practices", - "children": [] + id: 'heading-best-practices', + level: 2, + text: 'Best Practices', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] - } - ] - } - ] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], + }, + ], + }, + ], }, { - "file": "storage/providers/github.mdx", - "path": "/storage/providers/github", - "title": "GitHub Storage", - "toc": [ + file: 'storage/providers/github.mdx', + path: '/storage/providers/github', + title: 'GitHub Storage', + toc: [ { - "id": "heading-github-storage", - "level": 1, - "text": "GitHub Storage", - "children": [ + id: 'heading-github-storage', + level: 1, + text: 'GitHub Storage', + children: [ { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [] + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [], }, { - "id": "heading-environment-variables", - "level": 2, - "text": "Environment Variables", - "children": [] + id: 'heading-environment-variables', + level: 2, + text: 'Environment Variables', + children: [], }, { - "id": "heading-getting-a-github-token", - "level": 2, - "text": "Getting a GitHub Token", - "children": [] + id: 'heading-getting-a-github-token', + level: 2, + text: 'Getting a GitHub Token', + children: [], }, { - "id": "heading-repository-setup", - "level": 2, - "text": "Repository Setup", - "children": [] + id: 'heading-repository-setup', + level: 2, + text: 'Repository Setup', + children: [], }, { - "id": "heading-limitations", - "level": 2, - "text": "Limitations", - "children": [] + id: 'heading-limitations', + level: 2, + text: 'Limitations', + children: [], }, { - "id": "heading-raw-url-cdn", - "level": 2, - "text": "Raw URL CDN", - "children": [] + id: 'heading-raw-url-cdn', + level: 2, + text: 'Raw URL CDN', + children: [], }, { - "id": "heading-private-repositories", - "level": 2, - "text": "Private Repositories", - "children": [] + id: 'heading-private-repositories', + level: 2, + text: 'Private Repositories', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] - } - ] - } - ] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], + }, + ], + }, + ], }, { - "file": "storage/providers/local.mdx", - "path": "/storage/providers/local", - "title": "Local Storage", - "toc": [ + file: 'storage/providers/local.mdx', + path: '/storage/providers/local', + title: 'Local Storage', + toc: [ { - "id": "heading-local-storage", - "level": 1, - "text": "Local Storage", - "children": [ + id: 'heading-local-storage', + level: 1, + text: 'Local Storage', + children: [ { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [] + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [], }, { - "id": "heading-path-configuration", - "level": 2, - "text": "Path Configuration", - "children": [ + id: 'heading-path-configuration', + level: 2, + text: 'Path Configuration', + children: [ { - "id": "heading-basepath", - "level": 3, - "text": "basePath", - "children": [] + id: 'heading-basepath', + level: 3, + text: 'basePath', + children: [], }, { - "id": "heading-distpath-optional", - "level": 3, - "text": "distPath (Optional)", - "children": [] + id: 'heading-distpath-optional', + level: 3, + text: 'distPath (Optional)', + children: [], }, { - "id": "heading-baseurl", - "level": 3, - "text": "baseUrl", - "children": [] - } - ] + id: 'heading-baseurl', + level: 3, + text: 'baseUrl', + children: [], + }, + ], }, { - "id": "heading-use-cases", - "level": 2, - "text": "Use Cases", - "children": [] + id: 'heading-use-cases', + level: 2, + text: 'Use Cases', + children: [], }, { - "id": "heading-file-organization", - "level": 2, - "text": "File Organization", - "children": [] + id: 'heading-file-organization', + level: 2, + text: 'File Organization', + children: [], }, { - "id": "heading-excluding-files", - "level": 2, - "text": "Excluding Files", - "children": [] + id: 'heading-excluding-files', + level: 2, + text: 'Excluding Files', + children: [], }, { - "id": "heading-serving-originals", - "level": 2, - "text": "Serving Originals", - "children": [] + id: 'heading-serving-originals', + level: 2, + text: 'Serving Originals', + children: [], }, { - "id": "heading-best-practices", - "level": 2, - "text": "Best Practices", - "children": [] + id: 'heading-best-practices', + level: 2, + text: 'Best Practices', + children: [], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] - } - ] - } - ] + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], + }, + ], + }, + ], }, { - "file": "storage/providers/s3.mdx", - "path": "/storage/providers/s3", - "title": "S3 / S3-Compatible", - "toc": [ + file: 'storage/providers/s3.mdx', + path: '/storage/providers/s3', + title: 'S3 / S3-Compatible', + toc: [ { - "id": "heading-s3-s3-compatible-storage", - "level": 1, - "text": "S3 / S3-Compatible Storage", - "children": [ + id: 'heading-s3-s3-compatible-storage', + level: 1, + text: 'S3 / S3-Compatible Storage', + children: [ { - "id": "heading-configuration", - "level": 2, - "text": "Configuration", - "children": [] + id: 'heading-configuration', + level: 2, + text: 'Configuration', + children: [], }, { - "id": "heading-environment-variables", - "level": 2, - "text": "Environment Variables", - "children": [] + id: 'heading-environment-variables', + level: 2, + text: 'Environment Variables', + children: [], }, { - "id": "heading-supported-services", - "level": 2, - "text": "Supported Services", - "children": [] + id: 'heading-supported-services', + level: 2, + text: 'Supported Services', + children: [], }, { - "id": "heading-best-practices", - "level": 2, - "text": "Best Practices", - "children": [ + id: 'heading-best-practices', + level: 2, + text: 'Best Practices', + children: [ { - "id": "heading-custom-domain-cdn", - "level": 3, - "text": "Custom Domain (CDN)", - "children": [] + id: 'heading-custom-domain-cdn', + level: 3, + text: 'Custom Domain (CDN)', + children: [], }, { - "id": "heading-prefix-organization", - "level": 3, - "text": "Prefix Organization", - "children": [] + id: 'heading-prefix-organization', + level: 3, + text: 'Prefix Organization', + children: [], }, { - "id": "heading-download-concurrency", - "level": 3, - "text": "Download Concurrency", - "children": [] + id: 'heading-download-concurrency', + level: 3, + text: 'Download Concurrency', + children: [], }, { - "id": "heading-exclude-patterns", - "level": 3, - "text": "Exclude Patterns", - "children": [] - } - ] + id: 'heading-exclude-patterns', + level: 3, + text: 'Exclude Patterns', + children: [], + }, + ], }, { - "id": "heading-troubleshooting", - "level": 2, - "text": "Troubleshooting", - "children": [] - } - ] - } - ] - } + id: 'heading-troubleshooting', + level: 2, + text: 'Troubleshooting', + children: [], + }, + ], + }, + ], + }, ] // Helper function to find TOC data by file path export function getTocByFile(filePath: string): TocItem[] | undefined { - const item = tocData.find(item => item.file === filePath) + const item = tocData.find((item) => item.file === filePath) return item?.toc } // Helper function to find TOC data by route path export function getTocByPath(routePath: string): TocItem[] | undefined { - const item = tocData.find(item => item.path === routePath) + const item = tocData.find((item) => item.path === routePath) return item?.toc } // Helper function to flatten TOC tree into a simple array export function flattenToc(toc: TocItem[]): TocItem[] { const result: TocItem[] = [] - + function traverse(items: TocItem[]) { for (const item of items) { result.push({ @@ -1853,7 +1871,7 @@ export function flattenToc(toc: TocItem[]): TocItem[] { } } } - + traverse(toc) return result } @@ -1861,7 +1879,7 @@ export function flattenToc(toc: TocItem[]): TocItem[] { // Helper function to get all headings at a specific level export function getHeadingsByLevel(toc: TocItem[], level: number): TocItem[] { const result: TocItem[] = [] - + function traverse(items: TocItem[]) { for (const item of items) { if (item.level === level) { @@ -1872,7 +1890,7 @@ export function getHeadingsByLevel(toc: TocItem[], level: number): TocItem[] { } } } - + traverse(toc) return result } diff --git a/apps/landing/src/app/[locale]/page.tsx b/apps/landing/src/app/[locale]/page.tsx index 0b9e82f0..e23266be 100644 --- a/apps/landing/src/app/[locale]/page.tsx +++ b/apps/landing/src/app/[locale]/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { GalleryShowcase } from '~/components/landing' import { CreateSpaceCTA, NocturneHero, @@ -11,6 +12,7 @@ export default function Home() {
+
) diff --git a/apps/landing/src/components/landing/GalleryShowcase.tsx b/apps/landing/src/components/landing/GalleryShowcase.tsx new file mode 100644 index 00000000..a8a29f51 --- /dev/null +++ b/apps/landing/src/components/landing/GalleryShowcase.tsx @@ -0,0 +1,227 @@ +'use client' + +import { useQuery } from '@tanstack/react-query' +import { useLocale, useTranslations } from 'next-intl' +import { useEffect, useState } from 'react' + +import { API_URL } from '~/constants/env' + +interface FeaturedGalleryAuthor { + name: string + avatar: string | null +} + +interface FeaturedGallery { + id: string + name: string + slug: string + domain: string | null + description: string | null + author: FeaturedGalleryAuthor | null + createdAt: string +} + +interface FeaturedGalleriesResponse { + galleries: FeaturedGallery[] +} + +const API_BASE_URL = API_URL.replace(/\/$/, '') +const FEATURED_GALLERIES_ENDPOINT = `${API_BASE_URL || ''}/featured-galleries` + +async function fetchFeaturedGalleries(): Promise { + const response = await fetch(FEATURED_GALLERIES_ENDPOINT, { + method: 'GET', + headers: { + 'content-type': 'application/json', + }, + }) + + if (!response.ok) { + throw new Error('Failed to fetch featured galleries') + } + + return response.json() +} + +export const GalleryShowcase = () => { + const t = useTranslations('GalleryShowcase') + const locale = useLocale() + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + + const { data, isLoading, error } = useQuery({ + queryKey: ['featured-galleries'], + queryFn: fetchFeaturedGalleries, + enabled: mounted, + staleTime: 5 * 60 * 1000, // 5 minutes + }) + + const galleries = data?.galleries ?? [] + + const getBaseDomain = () => { + if (typeof window === 'undefined') return 'afilmory.art' + const { hostname } = window.location + // Extract base domain from current hostname, or use default + if ( + hostname.includes('.') && + !hostname.includes('localhost') && + !hostname.includes('127.0.0.1') + ) { + return hostname.split('.').slice(-2).join('.') + } + return 'afilmory.art' + } + + const buildGalleryUrl = (gallery: FeaturedGallery) => { + if (typeof window === 'undefined') return '#' + const { protocol } = window.location + // Prefer custom domain, fallback to slug subdomain + if (gallery.domain) { + return `${protocol}//${gallery.domain}` + } + const baseDomain = getBaseDomain() + return `${protocol}//${gallery.slug}.${baseDomain}` + } + + const getDisplayUrl = (gallery: FeaturedGallery) => { + // Prefer custom domain, fallback to slug subdomain + if (gallery.domain) { + return gallery.domain + } + return `${gallery.slug}.${getBaseDomain()}` + } + + const formatDate = (dateString: string) => { + const date = new Date(dateString) + const localeMap: Record = { + 'zh-CN': 'zh-CN', + 'zh-TW': 'zh-TW', + 'zh-HK': 'zh-HK', + en: 'en-US', + jp: 'ja-JP', + ko: 'ko-KR', + } + const dateLocale = localeMap[locale] || 'en-US' + return date.toLocaleDateString(dateLocale, { + year: 'numeric', + month: 'long', + day: 'numeric', + }) + } + + if (!mounted) { + return null + } + + return ( + + ) +} diff --git a/apps/landing/src/components/landing/index.ts b/apps/landing/src/components/landing/index.ts index 31c8b2fc..0ffb54c9 100644 --- a/apps/landing/src/components/landing/index.ts +++ b/apps/landing/src/components/landing/index.ts @@ -4,6 +4,7 @@ */ export { CreateSpaceModal } from './CreateSpaceModal' +export { GalleryShowcase } from './GalleryShowcase' export { NocturneBackground } from './NocturneBackground' export { NocturneButton } from './NocturneButton' export * from './NocturneSections' diff --git a/apps/landing/src/lib/query-client.ts b/apps/landing/src/lib/query-client.ts new file mode 100644 index 00000000..2f1526c1 --- /dev/null +++ b/apps/landing/src/lib/query-client.ts @@ -0,0 +1,12 @@ +import { QueryClient } from '@tanstack/react-query' + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime) + retry: 1, + refetchOnWindowFocus: false, + }, + }, +}) diff --git a/apps/landing/src/locales/en.json b/apps/landing/src/locales/en.json index 96f24643..271e29e0 100644 --- a/apps/landing/src/locales/en.json +++ b/apps/landing/src/locales/en.json @@ -177,5 +177,12 @@ "privacy": "Privacy Policy", "terms": "Terms of Service", "copyright": "© 2025 Afilmory.art" + }, + "GalleryShowcase": { + "eyebrow": "Registered Galleries", + "title": "Explore registered visual spaces", + "description": "Discover amazing photography archives created by other photographers and curators, experience different visual storytelling styles.", + "error": "Failed to load gallery list, please try again later.", + "empty": "No registered galleries yet." } } diff --git a/apps/landing/src/locales/zh-CN.json b/apps/landing/src/locales/zh-CN.json index 92bb0b4e..de15ae17 100644 --- a/apps/landing/src/locales/zh-CN.json +++ b/apps/landing/src/locales/zh-CN.json @@ -177,5 +177,12 @@ "privacy": "隐私政策", "terms": "服务条款", "copyright": "© 2025 Afilmory.art" + }, + "GalleryShowcase": { + "eyebrow": "已注册画展", + "title": "探索已注册的影像空间", + "description": "发现其他摄影师和策展人创建的精彩影像档案馆,感受不同的视觉叙事风格。", + "error": "加载画展列表时出错,请稍后重试。", + "empty": "暂无已注册的画展。" } } diff --git a/apps/landing/src/providers/root/index.tsx b/apps/landing/src/providers/root/index.tsx index 5529f360..94beaf9c 100644 --- a/apps/landing/src/providers/root/index.tsx +++ b/apps/landing/src/providers/root/index.tsx @@ -1,22 +1,20 @@ 'use client' +import { QueryClientProvider } from '@tanstack/react-query' import { LazyMotion } from 'motion/react' import { ThemeProvider } from 'next-themes' import type { JSX, PropsWithChildren } from 'react' import { ProviderComposer } from '../../components/common/ProviderComposer' +import { queryClient } from '../../lib/query-client' const loadFeatures = () => import('./framer-lazy-feature').then((res) => res.default) const contexts: JSX.Element[] = [ , - + , , ] export function Providers({ children }: PropsWithChildren) { - return ( - <> - {children} - - ) + return {children} } diff --git a/be/apps/core/src/modules/index.module.ts b/be/apps/core/src/modules/index.module.ts index 1088ab65..fedcf189 100644 --- a/be/apps/core/src/modules/index.module.ts +++ b/be/apps/core/src/modules/index.module.ts @@ -29,6 +29,7 @@ import { AuthModule } from './platform/auth/auth.module' import { BillingModule } from './platform/billing/billing.module' import { DashboardModule } from './platform/dashboard/dashboard.module' import { DataManagementModule } from './platform/data-management/data-management.module' +import { FeaturedGalleriesModule } from './platform/featured-galleries/featured-galleries.module' import { SuperAdminModule } from './platform/super-admin/super-admin.module' import { TenantModule } from './platform/tenant/tenant.module' @@ -62,6 +63,7 @@ function createEventModuleOptions(redis: RedisAccessor) { BillingModule, DataManagementModule, TenantModule, + FeaturedGalleriesModule, DataSyncModule, FeedModule, OgModule, diff --git a/be/apps/core/src/modules/platform/data-management/data-management.service.ts b/be/apps/core/src/modules/platform/data-management/data-management.service.ts index 98f7aa20..83627bec 100644 --- a/be/apps/core/src/modules/platform/data-management/data-management.service.ts +++ b/be/apps/core/src/modules/platform/data-management/data-management.service.ts @@ -1,14 +1,4 @@ -import { - authSessions, - authUsers, - photoAssets, - reactions, - settings, - tenantAuthAccounts, - tenantAuthSessions, - tenantAuthUsers, - tenants, -} from '@afilmory/db' +import { authSessions, authUsers, photoAssets, reactions, settings, tenants } from '@afilmory/db' import { EventEmitterService } from '@afilmory/framework' import { DbAccessor } from 'core/database/database.provider' import { BizException, ErrorCode } from 'core/errors' @@ -76,9 +66,7 @@ export class DataManagementService { await tx.delete(photoAssets).where(eq(photoAssets.tenantId, tenantId)) await tx.delete(reactions).where(eq(reactions.tenantId, tenantId)) await tx.delete(settings).where(eq(settings.tenantId, tenantId)) - await tx.delete(tenantAuthAccounts).where(eq(tenantAuthAccounts.tenantId, tenantId)) - await tx.delete(tenantAuthSessions).where(eq(tenantAuthSessions.tenantId, tenantId)) - await tx.delete(tenantAuthUsers).where(eq(tenantAuthUsers.tenantId, tenantId)) + await tx.delete(authSessions).where(eq(authSessions.tenantId, tenantId)) await tx.update(authUsers).set({ tenantId: null, role: 'user' }).where(eq(authUsers.tenantId, tenantId)) await tx.delete(tenants).where(eq(tenants.id, tenantId)) diff --git a/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.controller.ts b/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.controller.ts new file mode 100644 index 00000000..53094a2a --- /dev/null +++ b/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.controller.ts @@ -0,0 +1,19 @@ +import { Controller, Get } from '@afilmory/framework' +import { AllowPlaceholderTenant } from 'core/decorators/allow-placeholder.decorator' +import { SkipTenantGuard } from 'core/decorators/skip-tenant.decorator' +import { BypassResponseTransform } from 'core/interceptors/response-transform.decorator' + +import { FeaturedGalleriesService } from './featured-galleries.service' + +@Controller('featured-galleries') +@SkipTenantGuard() +@BypassResponseTransform() +export class FeaturedGalleriesController { + constructor(private readonly featuredGalleriesService: FeaturedGalleriesService) {} + + @AllowPlaceholderTenant() + @Get('/') + async listFeaturedGalleries() { + return await this.featuredGalleriesService.listFeaturedGalleries() + } +} diff --git a/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.module.ts b/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.module.ts new file mode 100644 index 00000000..088ae60e --- /dev/null +++ b/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@afilmory/framework' +import { DatabaseModule } from 'core/database/database.module' + +import { TenantModule } from '../tenant/tenant.module' +import { FeaturedGalleriesController } from './featured-galleries.controller' +import { FeaturedGalleriesService } from './featured-galleries.service' + +@Module({ + imports: [DatabaseModule, TenantModule], + controllers: [FeaturedGalleriesController], + providers: [FeaturedGalleriesService], +}) +export class FeaturedGalleriesModule {} diff --git a/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.service.ts b/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.service.ts new file mode 100644 index 00000000..eff30c4a --- /dev/null +++ b/be/apps/core/src/modules/platform/featured-galleries/featured-galleries.service.ts @@ -0,0 +1,117 @@ +import { authUsers, settings, tenantDomains } from '@afilmory/db' +import { DbAccessor } from 'core/database/database.provider' +import { normalizeDate } from 'core/helpers/normalize.helper' +import { and, asc, eq, inArray, sql } from 'drizzle-orm' +import { injectable } from 'tsyringe' + +import { TenantService } from '../tenant/tenant.service' + +@injectable() +export class FeaturedGalleriesService { + constructor( + private readonly tenantService: TenantService, + private readonly dbAccessor: DbAccessor, + ) {} + + async listFeaturedGalleries() { + const aggregates = await this.tenantService.listTenants() + + // Filter out banned, inactive, and suspended tenants + const validTenants = aggregates + .filter((aggregate) => { + const { tenant } = aggregate + return !tenant.banned && tenant.status === 'active' && tenant.slug !== 'root' && tenant.slug !== 'placeholder' + }) + .slice(0, 20) // Limit to 20 most recent + + const tenantIds = validTenants.map((aggregate) => aggregate.tenant.id) + if (tenantIds.length === 0) { + return { galleries: [] } + } + + const db = this.dbAccessor.get() + + // Fetch site settings for all tenants + const siteSettings = await db + .select() + .from(settings) + .where(and(inArray(settings.tenantId, tenantIds), inArray(settings.key, ['site.name', 'site.description']))) + + // Fetch primary author (admin) for each tenant + const authors = await db + .select({ + tenantId: authUsers.tenantId, + name: authUsers.name, + image: authUsers.image, + }) + .from(authUsers) + .where(inArray(authUsers.tenantId, tenantIds)) + .orderBy( + sql`case when ${authUsers.role} = 'admin' then 0 when ${authUsers.role} = 'superadmin' then 1 else 2 end`, + asc(authUsers.createdAt), + ) + + // Fetch verified domains for all tenants + const domains = await db + .select({ + tenantId: tenantDomains.tenantId, + domain: tenantDomains.domain, + }) + .from(tenantDomains) + .where(and(inArray(tenantDomains.tenantId, tenantIds), eq(tenantDomains.status, 'verified'))) + + // Build maps for quick lookup + const settingsMap = new Map>() + for (const setting of siteSettings) { + if (!settingsMap.has(setting.tenantId)) { + settingsMap.set(setting.tenantId, new Map()) + } + settingsMap.get(setting.tenantId)!.set(setting.key, setting.value) + } + + const authorMap = new Map() + for (const author of authors) { + if (!authorMap.has(author.tenantId!)) { + authorMap.set(author.tenantId!, { + name: author.name, + avatar: author.image ?? null, + }) + } + } + + const domainMap = new Map() + for (const domain of domains) { + // Use the first verified domain for each tenant + if (!domainMap.has(domain.tenantId)) { + domainMap.set(domain.tenantId, domain.domain) + } + } + + // Build response + const featuredGalleries = validTenants.map((aggregate) => { + const { tenant } = aggregate + const tenantSettings = settingsMap.get(tenant.id) ?? new Map() + const author = authorMap.get(tenant.id) + const domain = domainMap.get(tenant.id) + + return { + id: tenant.id, + name: tenantSettings.get('site.name') ?? tenant.name, + slug: tenant.slug, + domain: domain ?? null, + description: tenantSettings.get('site.description') ?? null, + author: author + ? { + name: author.name, + avatar: author.avatar, + } + : null, + createdAt: normalizeDate(tenant.createdAt) ?? tenant.createdAt, + } + }) + + return { + galleries: featuredGalleries, + } + } +} diff --git a/be/packages/db/src/schema.ts b/be/packages/db/src/schema.ts index 16169466..2df8c121 100644 --- a/be/packages/db/src/schema.ts +++ b/be/packages/db/src/schema.ts @@ -168,67 +168,6 @@ export const creemSubscriptions = pgTable('creem_subscription', { updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().notNull(), }) -export const tenantAuthUsers = pgTable( - 'tenant_auth_user', - { - id: text('id').primaryKey(), - tenantId: text('tenant_id') - .notNull() - .references(() => tenants.id, { onDelete: 'cascade' }), - name: text('name').notNull(), - email: text('email').notNull(), - emailVerified: boolean('email_verified').default(false).notNull(), - image: text('image'), - role: text('role').default('guest').notNull(), - createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), - updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().notNull(), - twoFactorEnabled: boolean('two_factor_enabled').default(false).notNull(), - username: text('username'), - displayUsername: text('display_username'), - banned: boolean('banned').default(false).notNull(), - banReason: text('ban_reason'), - banExpires: timestamp('ban_expires_at', { mode: 'string' }), - }, - (t) => [unique('uq_tenant_auth_user_tenant_email').on(t.tenantId, t.email)], -) - -export const tenantAuthSessions = pgTable('tenant_auth_session', { - id: text('id').primaryKey(), - tenantId: text('tenant_id') - .notNull() - .references(() => tenants.id, { onDelete: 'cascade' }), - expiresAt: timestamp('expires_at', { mode: 'string' }).notNull(), - token: text('token').notNull().unique(), - createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), - updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().notNull(), - ipAddress: text('ip_address'), - userAgent: text('user_agent'), - userId: text('user_id') - .notNull() - .references(() => tenantAuthUsers.id, { onDelete: 'cascade' }), -}) - -export const tenantAuthAccounts = pgTable('tenant_auth_account', { - id: text('id').primaryKey(), - tenantId: text('tenant_id') - .notNull() - .references(() => tenants.id, { onDelete: 'cascade' }), - accountId: text('account_id').notNull(), - providerId: text('provider_id').notNull(), - userId: text('user_id') - .notNull() - .references(() => tenantAuthUsers.id, { onDelete: 'cascade' }), - accessToken: text('access_token'), - refreshToken: text('refresh_token'), - idToken: text('id_token'), - accessTokenExpiresAt: timestamp('access_token_expires_at', { mode: 'string' }), - refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { mode: 'string' }), - scope: text('scope'), - password: text('password'), - createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), - updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().notNull(), -}) - export const settings = pgTable( 'settings', { @@ -402,9 +341,7 @@ export const dbSchema = { authAccounts, authVerifications, creemSubscriptions, - tenantAuthUsers, - tenantAuthSessions, - tenantAuthAccounts, + settings, systemSettings, reactions,