# Architecture

Last source audit: 2026-05-12, based on git commit `8ee86a9`.

## System Overview

MiaoJing is a self-hostable AI multimodal creation platform built on:

- Next.js 16 App Router
- React 19
- TypeScript
- PostgreSQL via `pg`
- Local disk storage through `/api/local-storage/*`
- PM2 process management
- Optional upstream AI providers through SDK, custom API keys, and admin system API configs

The repository contains frontend pages, API route handlers, business services, database helpers, storage helpers, deployment scripts, and PM2 config in one app.

## Runtime Shape

```text
Browser
  |
  | HTTP
  v
Next.js App Router
  |
  +-- src/app pages
  +-- src/app/api route handlers
  |
  +-- src/components UI and workflow components
  |
  +-- src/lib business logic and stores
  |
  +-- src/storage database clients
  |
  +-- local disk storage via src/lib/local-storage.ts
  |
  +-- PostgreSQL via src/storage/database/local-db.ts
  |
  +-- Upstream AI providers via SDK/custom fetch
```

Production is controlled by `ecosystem.config.cjs` and the scripts under `scripts/`. Always verify PM2 cwd and runtime environment before editing a live server.

## Directory Boundaries

| Directory | Responsibility |
| --- | --- |
| `src/app` | Next.js pages, layouts, and API route handlers. |
| `src/components` | Reusable UI and product workflow components. |
| `src/components/ui` | Radix/shadcn-style primitives. Keep generic. |
| `src/components/create` | Create center panels and shared generation UI. |
| `src/components/admin` | Admin console tab components. |
| `src/components/canvas` | Legacy infinite canvas UI and rendering. The public `/canvas` route is currently disabled and should not appear in navbar unless explicitly re-enabled. |
| `src/components/profile` | Profile page tabs and API key manager. |
| `src/lib` | Business logic, stores, model config, auth, storage, email, generation jobs, provider resolution. |
| `src/modules` | Thin module export and console page wrappers. |
| `src/storage` | Database client/schema compatibility. |
| `scripts` | Build, deploy, backup, restore, DB patch, admin upgrade runner. |
| `public` | Public static assets. |
| `assets` | Project-owned source assets. |

## Frontend Architecture

The app is route-driven through `src/app`:

- Public shell: `src/app/layout.tsx` and `src/components/app-shell.tsx`.
- Navigation and global UI: navbar, footer, announcement popup, site config sync, visit tracker.
- Product workflows:
  - Create center: `src/app/create/page.tsx` plus `src/components/create/*`.
  - Gallery: `src/app/gallery/page.tsx`.
  - Profile: `src/app/profile/page.tsx` plus `src/components/profile/*`.
  - Canvas: legacy `src/app/canvas/page.tsx` plus `src/components/canvas/*`; `/canvas` currently returns 404 and is not part of public navigation.
  - Admin console: `src/app/console/*`, `src/modules/console/pages/*`, `src/components/admin/*`.

Client stores in `src/lib/*-store.ts` mediate API calls and local UI state. When fixing a UI persistence bug, inspect both the component and the matching store/API route.

## API Architecture

All APIs are route handlers in `src/app/api`.

Major groups:

- `auth/*`: login, register, admin existence, provider API testing.
- `profile/*`: user profile and theme.
- `user-api-keys`: user-owned custom API credentials.
- `model-config`: public model/provider config.
- `style-presets`: public DB-backed image style presets ordered by usage count.
- `generate/*`: direct generation, reverse prompt, prompt suggestion.
- `generation-jobs/*`: queued generation job creation/status.
- `creation-history`: user works/history.
- `gallery/*`: public gallery and publishing.
- `canvas/projects/*`: legacy canvas persistence routes. They remain in source, but the public `/canvas` UI is disabled.
- `admin/*`: console dashboard, users, providers, system APIs, orders, payment, upgrade, data import/export, email settings.
- `site-config`, `site-stats`, `announcements`: public site content and counters.
- `local-storage/*`, `download`: file serving and download proxy.

Auth is not implicit. Each route must call the correct helper:

- User route: `getAuthenticatedUserId` or `getAuthenticatedUser`.
- Admin route: `requireAdmin` or `requireAdminUser`.
- Internal generation route: `isTrustedInternalGenerationRequest`.

## Generation Flow

```text
Create panel
  -> src/lib/generation-job-client.ts
  -> POST /api/generation-jobs
  -> generation_jobs row inserted
  -> src/lib/generation-job-worker.ts
  -> src/lib/generation-job-runner.ts
  -> POST /api/generate/image or /api/generate/video
  -> SDK or custom/system API upstream call
  -> src/lib/local-storage.ts persists result
  -> generation_jobs updated with result/error/progress
  -> client polls GET /api/generation-jobs/[id]
  -> history/gallery persistence via works APIs
```

Key source files:

- `src/components/create/text-to-image.tsx`
- `src/components/create/image-to-image.tsx`
- `src/components/create/text-to-video.tsx`
- `src/components/create/image-to-video.tsx`
- `src/lib/generation-job-client.ts`
- `src/app/api/generation-jobs/route.ts`
- `src/app/api/style-presets/route.ts`
- `src/lib/style-preset-store.ts`
- `src/lib/generation-job-worker.ts`
- `src/lib/generation-job-runner.ts`
- `src/app/api/generate/image/route.ts`
- `src/app/api/generate/video/route.ts`

Do not bypass the job flow unless intentionally implementing synchronous/internal-only behavior.

## Provider Resolution

There are three provider sources:

1. Built-in model config: `src/lib/model-config.ts`.
2. User custom API keys: `src/app/api/user-api-keys/route.ts`, `src/lib/custom-api-store.ts`.
3. Admin system APIs: `src/app/api/admin/system-apis/route.ts`, `src/lib/server-api-config.ts`.

`src/lib/server-api-config.ts` resolves:

- `customApiKeyId` into a user-owned decrypted API config.
- `systemApiId` into an active admin-managed decrypted API config.
- direct `apiKey` passthrough for legacy/custom callers.

Secrets must be encrypted at rest with `src/lib/server-crypto.ts` and never returned in API responses.

## Storage Architecture

`src/lib/local-storage.ts` is the storage abstraction.

- Base path: `LOCAL_STORAGE_DIR`, or repo-local `local-storage` if unset.
- Public URL shape: `/api/local-storage/<key>`.
- File serving route: `src/app/api/local-storage/[...path]/route.ts`.
- Download route: `src/app/api/download/route.ts`.
- Storage key validation prevents traversal through `normalizeKey`, `path.resolve`, and `..` checks.

Generation routes persist generated media into storage. Gallery publish copies referenced result URLs into gallery folders when possible.

## Database Architecture

Main DB entry:

- `src/storage/database/local-db.ts`: PostgreSQL pool from `LOCAL_DB_URL`.

Schema sources:

- `src/storage/database/shared/schema.ts`: Drizzle snapshot for core tables.
- `scripts/init-database.sql`: full initialization.
- Runtime routes/services also ensure compatible columns/tables with `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.

Core data areas:

- Users: `auth.users`, `profiles`.
- Works: `works`.
- Credits: `credit_transactions`.
- Orders: `orders`.
- API credentials: `user_api_keys`, `system_api_configs`, `api_providers`.
- Site content: `site_config`, `announcements`, `site_stats`.
- Jobs/logs: `generation_jobs`, `platform_logs`.
- Canvas: legacy state handled through `src/lib/canvas-store.ts` and canvas project routes; the user-facing route is disabled.

Because several routes self-migrate compatibility columns, DB bugs often require checking both SQL scripts and route-level `ensure...Schema` functions.

User display identity: `profiles.nickname` is retained as the login username so existing username/phone/email login and `works.user_id` ownership remain stable. Public display uses `profiles.display_nickname`, surfaced to clients as `nickname`; `src/lib/user-profile-defaults.ts` owns runtime schema creation plus random Chinese nickname/default 3D cartoon avatar generation. Existing users without `display_nickname` or `avatar_url` can be backfilled with `scripts/backfill-user-display-profile.mjs`.

## Admin Console Architecture

Admin console UI is split across:

- Page/wrapper: `src/app/console/page.tsx`, `src/app/console/dashboard/page.tsx`.
- Module pages: `src/modules/console/pages/console-login-page.tsx`, `src/modules/console/pages/console-dashboard-page.tsx`.
- Tabs: `src/components/admin/*`.
- APIs: `src/app/api/admin/*`.

Admin auth flows through the same login endpoint with admin role checks. API routes should use `requireAdmin`.

## Upgrade And Deployment Architecture

Scripts:

- `scripts/build.sh`: builds Next.js and server.
- `scripts/start.sh`: production start path.
- `scripts/deploy-or-upgrade.sh`: deployment/upgrade automation.
- `scripts/admin-upgrade-runner.mjs`: package extraction/build/restart runner.
- `scripts/backup-create.sh`, `backup-list.sh`, `backup-restore.sh`: backups. Backup creation validates `pg_dump` output and tar integrity; restore validates archive/dump contents, creates a pre-restore safety dump/copy, uses a single DB transaction, and swaps local storage atomically.
- `scripts/apply-database-patch.sh`: DB patch execution.

Runtime:

- `ecosystem.config.cjs` defines PM2 processes and environment roles.
- `src/server.ts` is the custom Node server entry.
- Admin upgrade API and UI:
  - `src/app/api/admin/upgrade/route.ts`
  - `src/components/admin/system-upgrade-tab.tsx`

Production note from the 2026-05-14 update: the reachable SSH endpoint was `root@124.174.9.29 -p 5238`, while PM2 still served `/opt/miaojingAI` with Node/PM2 under `/data/miaojingAI/node/node-v24.15.0-linux-x64/bin`. The live ports were `8000` for web, `8100` for API, and `8200` for console. Do not overwrite production `ecosystem.config.cjs` with a repository or dev-server copy during rsync-style source updates; it can switch PM2 back to `/root/miaojingAI` and ports `5000/5100/5200`.

When changing deploy/upgrade behavior, validate package limits, disk checks, backup creation, rollback paths, restore safety backups, PM2 restart command, and health checks.

All new development must be designed so the production server can be updated later through the admin console upgrade package flow. Classify every deployable change before handoff:

- Hot update candidate: static/public asset-only payloads that the upgrade runner accepts without restart. Preflight must reject source, dependency, script, lockfile, secret, backup, storage, and runtime paths.
- Cold update candidate: any change involving `src`, API routes, server code, dependencies, `package.json`, `pnpm-lock.yaml`, DB schema or compatibility migration behavior, environment variables, PM2/runtime config, build scripts, backup/restore scripts, deployment scripts, or generated server assets.

Cold updates must preserve this safety chain: package preflight, disk checks, data backup, source snapshot, build/type verification, PM2 reload/restart, `/api/health`, and rollback through source restore plus backup restore when needed. When a feature introduces new persistent data, schema expectations, file-storage paths, background jobs, or environment variables, update the upgrade/package notes in this architecture document so future production packages can be prepared safely.

## Data Portability

Admin data export/import is a portability layer, separate from the full tar backup scripts:

- `src/app/api/admin/data-export/route.ts` exports database business tables and bundles local-storage media referenced by `works` and `site_config` under `_media`.
- `src/app/api/admin/data-import/route.ts` accepts older DB-only exports and newer media-inclusive exports. Newer exports restore media files to stable sha-based local-storage keys before writing work rows.
- Import runs one DB transaction with per-row savepoints, remaps user/work/custom API key IDs, preserves old source URL/media SHA markers in `works.params`, and merges repeated imports by URL/source URL/media SHA.
- Older exports without `_media` can restore database rows but cannot recreate missing local files by themselves; copy `LOCAL_STORAGE_DIR` or use a newer export for full gallery/history image recovery.

## Security Boundaries

- API key storage: encrypted in DB, preview-only responses.
- User auth: bearer session token from `src/lib/session-auth.ts`.
- Admin auth: role must be `admin` or `enterprise_admin`.
- Internal generation: protected by `x-miaojing-generation-internal`.
- Local file serving: must preserve storage key normalization and path traversal guards.
- Browser embedding: `src/proxy.ts` owns CSP security headers. `frame-ancestors` defaults to self plus mozheAPI origins and can be overridden with `MIAOJING_FRAME_ANCESTORS`; if an external ancestor is allowed, omit `X-Frame-Options` because `SAMEORIGIN` would still block the iframe.
- Admin destructive actions: keep environment gates, admin checks, and limits.

## Verification Strategy

Use the narrowest useful check, then broaden as needed.

| Change Type | Minimum Verification |
| --- | --- |
| Docs only | Link/path sanity with `rg`, `git diff --check`. |
| TypeScript source | `pnpm run ts-check`. |
| API route | `pnpm run ts-check`, route smoke with `curl` where runtime exists. |
| UI workflow | `pnpm run ts-check`, `pnpm run build`, browser/manual or Playwright check if visual. |
| Generation path | `pnpm run ts-check`, `pnpm run build`, job create/poll route check, storage result check. |
| Static hot-update candidate | Admin upgrade dry run/preflight, verify runner accepts the payload without restart, then route/static asset smoke check. |
| Cold-update candidate | `pnpm run ts-check`, `pnpm run build`, admin upgrade dry run/preflight, backup/rollback readiness, PM2 reload/restart, health checks. |
| Deployment/upgrade tooling | `pnpm run ts-check`, `pnpm run build`, package/upgrade dry run, PM2 reload, health checks. |

## Known Risk Points

- Some source files import `@/lib/model-display`, but `src/lib/model-display.ts` is absent at the audited commit. This can break `pnpm run ts-check` independent of documentation changes.
- The documentation commit attempted `corepack pnpm run ts-check` and the audited local baseline also reported missing `ag-psd` and `@xyflow/react` module/type resolution plus canvas implicit-`any` errors. Reinstall dependencies with `corepack pnpm install --frozen-lockfile` before diagnosing dependency-resolution errors as source bugs.
- Generation routes are large and mix upstream adapter logic, persistence, and job progress. Prefer small surgical edits.
- Several DB schema changes are applied lazily at runtime; verify migration behavior on fresh and upgraded DBs.
- Production/dev servers can have different checkout paths. Always verify PM2 cwd before live edits.
- Admin upgrade touches file system, build, backup, disk, and PM2 state. Treat it as high-risk.
