Files
agent-park/.planning/codebase/ARCHITECTURE.md
T
2026-04-20 18:59:15 +08:00

258 lines
19 KiB
Markdown

# Architecture
**Analysis Date:** 2026-04-20
## Pattern Overview
**Overall:** Localized Next.js monolith with App Router pages, colocated JSON APIs, Prisma-backed read models, and an external automation perimeter for n8n and discovery-task execution.
**Key Characteristics:**
- User-facing pages and internal JSON APIs ship from the same Next.js app under `src/app/`.
- Server components and API handlers read through shared query/read-model modules in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`.
- External automation is first-class context, but not fully implemented in-repo: `docs/integrations/n8n/*` and `.planning/codebase/N8N-*.md` document workflows that call a subset of repository APIs and also use external discovery services or direct database writes.
## Layers
**Route & Shell Layer:**
- Purpose: Own URL structure, metadata, page composition, SEO documents, and locale-aware layout shells.
- Location: `src/app/layout.tsx`, `src/app/[locale]/layout.tsx`, `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, `src/app/[locale]/signals/page.tsx`, `src/app/[locale]/about/page.tsx`, `src/app/sitemap.ts`, `src/app/robots.ts`, `src/middleware.ts`
- Contains: App Router pages, `generateMetadata`, `generateStaticParams`, route-level `revalidate`, and top-level page composition.
- Depends on: `next-intl`, `src/hooks/*`, reusable components, and Next.js runtime APIs.
- Used by: Browser requests for `/<locale>/*`, `sitemap.xml`, and `robots.txt`.
**Client Interaction Layer:**
- Purpose: Own browser-only state, URL syncing, incremental fetching, and interactive search/filter/feed behavior.
- Location: `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/search/AISearchBar.tsx`, `src/components/search/HomeSearchBar.tsx`, `src/components/search/AISearchResults.tsx`, `src/components/signals/SignalFeedClient.tsx`, `src/components/layout/AnnouncementBar.tsx`, `src/components/locale/LocaleSwitcher.tsx`, `src/app/VercelMetrics.tsx`
- Contains: `"use client"` components, fetch calls to `/api/*`, local state, debounced input, history updates, and analytics instrumentation.
- Depends on: Browser APIs, Next navigation hooks, and JSON returned by `src/app/api/*`.
- Used by: Localized page routes that mount client islands after server rendering.
**Query / Read-Model Layer:**
- Purpose: Encapsulate Prisma reads, filter normalization, retry logic, caching, and response shape flattening for pages and APIs.
- Location: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`
- Contains: `getProjects`, `getProjectBySlug`, `getProjectsByIds`, `getTagCategoryGroups`, `getFixedProjectTypeFilters`, `getTopTags`, and `getHomePageData`.
- Depends on: `src/lib/prisma.ts`, `src/lib/cache.ts`, `src/lib/tag-taxonomy.ts`, Prisma types, and `next/cache`.
- Used by: Server pages such as `src/app/[locale]/page.tsx` and APIs such as `src/app/api/projects/route.ts`.
**API Integration & Mutation Layer:**
- Purpose: Expose JSON endpoints for browser fetches and authenticated automation/webhook writes.
- Location: `src/app/api/projects/route.ts`, `src/app/api/projects/[slug]/route.ts`, `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/tags/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/maintenance/service.ts`, `src/app/api/tags/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts`
- Contains: Zod request validation, Prisma queries and transactions, n8n proxying, revalidation, and webhook ingestion.
- Depends on: `src/hooks/*`, `src/lib/*`, Prisma, and external env/config for webhook integration.
- Used by: Client components in this repo, n8n workflows, and other internal automation clients.
**Shared Domain & Infrastructure Layer:**
- Purpose: Centralize cross-cutting rules and infrastructure helpers used by both pages and route handlers.
- Location: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/prisma-url.ts`, `src/lib/signal-hotness.ts`, `src/lib/slug.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/validations.ts`, `src/lib/github/badges.ts`, `src/i18n/request.ts`
- Contains: API-key validation, Prisma bootstrap, cache fallback helpers, hotness scoring, slug generation, taxonomy metadata, and Zod schemas.
- Depends on: Node APIs, Prisma, Zod, and Next.js server utilities.
- Used by: Both `src/hooks/*` and `src/app/api/*`, plus selected components.
**Persistence Layer:**
- Purpose: Define the application data model, migrations, and seed path.
- Location: `prisma/schema.prisma`, `prisma/migrations/*`, `prisma/seed.ts`
- Contains: PostgreSQL schema for `projects`, `external_links`, `tags`, `project_tags`, and `signals`, plus migration history and seed logic.
- Depends on: Prisma tooling and `DATABASE_URL`.
- Used by: `src/lib/prisma.ts` at runtime and Prisma CLI during migration/seed flows.
**External Automation Context Layer:**
- Purpose: Document and constrain the parts of the system that live outside this repository.
- Location: `docs/integrations/n8n/README.md`, `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/DATAFLOW.md`, `docs/integrations/n8n/workflows/*.md`, `.planning/codebase/N8N-CONTEXT.md`, `.planning/codebase/N8N-DATAFLOW.md`, `scripts/generate-n8n-context.mjs`
- Contains: Workflow inventory, request/response contracts, source-to-DB/API/UI flows, and generated mirrors for GSD.
- Depends on: External n8n workflows, registry metadata, and repository scanning.
- Used by: Humans and GSD agents to understand how external pipelines affect the in-repo app.
## Data Flow
**Localized Page Render:**
1. `src/middleware.ts` applies locale-prefixed routing for non-API, non-static requests.
2. `src/app/[locale]/layout.tsx` validates the locale, runs `setRequestLocale(locale)`, loads messages from `src/i18n/request.ts`, and wraps the subtree in `NextIntlClientProvider`.
3. Route pages such as `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, and `src/app/[locale]/signals/page.tsx` fetch server data or mount client fetchers.
4. Reusable components under `src/components/*` render the localized UI.
**Traditional Projects Browse Flow:**
1. `src/app/[locale]/projects/page.tsx` normalizes search params for `search`, `tags`, `domains`, `productForms`, `projectType`, `sort`, `page`, and `limit`.
2. The page calls `getProjects`, `getFixedProjectTypeFilters`, and `getTagCategoryGroups` from `src/hooks/useProjects.ts`.
3. `src/app/[locale]/projects/ProjectsPageClient.tsx` controls search-mode switching and filter-panel expansion.
4. `src/app/[locale]/projects/ProjectsResultsClient.tsx` fetches updated pages from `src/app/api/projects/route.ts` and keeps the URL query string in sync with browser state.
**AI Search Flow:**
1. `src/components/search/AISearchBar.tsx` captures the search term and mode.
2. `src/app/[locale]/projects/ProjectsResultsClient.tsx` posts the normalized request to `src/app/api/search/ai/route.ts`.
3. `src/app/api/search/ai/route.ts` validates the body with `ProjectQuerySchema`, forwards a GET request to the external n8n webhook from `N8N_AI_SEARCH_WEBHOOK`, and receives ranked `{id, similarity}` candidates.
4. The route hydrates those IDs from PostgreSQL through `getProjectsByIds` in `src/hooks/useProjects.ts`, reapplies local sorting/filtering rules, and returns the final JSON payload.
**Project Detail Flow:**
1. `src/app/[locale]/projects/[id]/page.tsx` resolves the route param as a slug and caches `getProjectBySlug` with `react` `cache()`.
2. The page fetches the selected project and a small recent-project set through `getProjects({ limit: 3 })`.
3. `src/components/project/ProjectDetail.tsx`, `src/components/project/ProjectSidebar.tsx`, and `src/components/project/RelatedProjects.tsx` render the long-form page.
4. `generateMetadata` in the same file derives title/description from the fetched project.
**Signals Read Flow:**
1. `src/app/[locale]/signals/page.tsx` renders the shell and mounts `src/components/signals/SignalFeedClient.tsx`.
2. `SignalFeedClient` manages debounced text search, source filters, sort mode, cursor pagination, and incremental loading in browser state.
3. The client calls `GET /api/signals` on `src/app/api/signals/route.ts`.
4. `src/app/api/signals/route.ts` validates the request with `SignalQuerySchema`, queries Prisma, converts JSON sections/tags into view models, and uses `src/lib/signal-hotness.ts` when `hotScore`/`isHot` columns are missing or unavailable.
**Signals Ingestion Flow:**
1. External automation posts batches to `src/app/api/webhook/signals/route.ts`.
2. The route validates the top-level payload with `SignalWebhookPayloadSchema` and each item with `SignalIngestionInputSchema` from `src/lib/validations.ts`.
3. `src/lib/auth.ts` checks the shared API key with constant-time comparison.
4. The handler computes fallback hotness when needed and upserts each signal into Prisma.
**Tag Governance Flow:**
1. Automation or internal tools call `POST /api/tags/maintenance` or `POST /api/tags/reset-projects`.
2. `src/app/api/tags/maintenance/route.ts` delegates merge/update logic to `src/app/api/tags/maintenance/service.ts` inside a transaction.
3. `src/app/api/tags/reset-projects/route.ts` validates requested tag slugs, resolves project/tag IDs, performs transactional tag replacement, and tracks per-project results.
4. Both mutation routes call `revalidatePath` for affected localized pages after successful writes.
**Home Aggregate Flow:**
1. `src/app/[locale]/page.tsx` calls `getProjects({ limit: 6 })` and `getHomePageData()` in parallel.
2. `src/hooks/useHome.ts` builds counts, rankings, top tags, and recent timeline data from Prisma with `unstable_cache`.
3. Home sections under `src/components/home/*` render those aggregates.
**External Discovery / Ingestion Boundary:**
1. Workflow specifications in `docs/integrations/n8n/workflows/01-topic-discovery.md`, `docs/integrations/n8n/workflows/02-github-trending-discovery.md`, and `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md` describe the discovery queue and task lifecycle.
2. The documented discovery endpoints include `POST /api/discovery/check-duplicates`, `POST /api/discovery/tasks`, `GET /api/discovery/tasks`, `PATCH /api/discovery/tasks/:id`, and `POST /api/discovery/tasks/:id/complete`.
3. No `/api/discovery/*` implementation exists under `src/app/api/`; that queue/task service is external to this repository.
4. This repository consumes the results of that pipeline through the shared PostgreSQL schema and its read APIs, not by executing the queue itself.
**Direct DB Maintenance Boundary:**
1. `docs/integrations/n8n/workflows/04-github-star-refresh.md` and `docs/integrations/n8n/workflows/05-project-description-vectorization.md` describe workflows that update `projects.githubStars`, `projects.githubStarsUpdatedAt`, `projects.embedding`, and `projects.embeddingUpdatedAt`.
2. Those workflows are documented as direct database maintenance jobs, not as repository API routes.
3. `src/app/api/search/ai/route.ts` and the home/projects pages consume the resulting columns after the external workflows finish.
**State Management:**
- Persistent domain state lives in PostgreSQL as modeled in `prisma/schema.prisma`.
- Cached server read state lives in `unstable_cache` wrappers in `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, and `src/app/api/tags/route.ts`, with fallback behavior from `src/lib/cache.ts`.
- Client UI state lives in route-local/browser components such as `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/components/search/AISearchBar.tsx`, and `src/components/signals/SignalFeedClient.tsx`.
- The projects browse experience treats the query string as the canonical state for filters, sort, pagination, and AI mode.
## Key Abstractions
**Project Read Model:**
- Purpose: Flatten Prisma join-table data into project objects that pages and APIs can render directly.
- Examples: `ProjectWithFlatTags`, `getProjects`, `getProjectBySlug`, and `getProjectsByIds` in `src/hooks/useProjects.ts`
- Pattern: Prisma `include` queries are transformed into a stable read model with `tags` flattened from `project_tags`.
**Home Aggregate Read Model:**
- Purpose: Produce a homepage-specific summary instead of exposing raw Prisma rows to UI sections.
- Examples: `HomePageData` and `getHomePageData` in `src/hooks/useHome.ts`
- Pattern: Aggregate counters and ranked slices are computed once, cached, and returned in a UI-ready object graph.
**Tag Taxonomy:**
- Purpose: Encode canonical tag categories, project-type presets, and category ordering rules in one place.
- Examples: `src/lib/tag-taxonomy.ts`, `getTagCategoryGroups` in `src/hooks/useProjects.ts`, `isFixedProjectTypeSlug` in `src/lib/tag-taxonomy.ts`
- Pattern: Shared domain vocabulary drives both browse filters and maintenance APIs.
**Signal View Model:**
- Purpose: Convert stored signal rows plus JSON payloads into localized feed cards.
- Examples: `parseSections` and `toSignalView` in `src/app/api/signals/route.ts`
- Pattern: API-specific presentation mapping handles fallback localization, section parsing, and hotness derivation.
**Internal Webhook/Auth Guard:**
- Purpose: Reuse constant-time API-key verification across internal write endpoints.
- Examples: `isValidApiKey` in `src/lib/auth.ts`, used by `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`
- Pattern: Small shared helper instead of a global auth middleware.
**Cache Fallback Wrapper:**
- Purpose: Allow cached read paths to degrade to direct fetches when Incremental Cache is unavailable.
- Examples: `runWithCacheFallback` in `src/lib/cache.ts`
- Pattern: Wrap `unstable_cache` fetchers so the app still serves data in environments without Incremental Cache support.
**n8n Contract Registry:**
- Purpose: Keep external workflow contracts reviewable inside the repo without pretending their runtime lives here.
- Examples: `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/DATAFLOW.md`, `.planning/codebase/N8N-CONTEXT.md`
- Pattern: External workflow metadata is committed and mirrored so architecture work can describe the full system boundary accurately.
## Entry Points
**Root HTML Shell:**
- Location: `src/app/layout.tsx`
- Triggers: Every page render.
- Responsibilities: Global HTML/body shell, CSS import, and conditional analytics injection through `src/app/VercelMetrics.tsx`.
**Locale Middleware:**
- Location: `src/middleware.ts`
- Triggers: All non-API, non-static requests.
- Responsibilities: Apply locale prefix rules and keep `/api` and asset paths out of locale routing.
**Localized App Shell:**
- Location: `src/app/[locale]/layout.tsx`
- Triggers: Every localized page render.
- Responsibilities: Locale validation, translations, header/footer shell, announcement bar, and provider setup.
**Home Route:**
- Location: `src/app/[locale]/page.tsx`
- Triggers: `GET /zh` and `GET /en`
- Responsibilities: Render the home hero plus cached rankings and featured projects.
**Projects Route:**
- Location: `src/app/[locale]/projects/page.tsx`
- Triggers: `GET /<locale>/projects`
- Responsibilities: Parse query-string filters, fetch initial browse data, and mount the projects client islands.
**Project Detail Route:**
- Location: `src/app/[locale]/projects/[id]/page.tsx`
- Triggers: `GET /<locale>/projects/:slug`
- Responsibilities: Fetch one project, render detail/sidebar sections, derive related-project cards, and emit metadata.
**Signals Route:**
- Location: `src/app/[locale]/signals/page.tsx`
- Triggers: `GET /<locale>/signals`
- Responsibilities: Render the signals page shell and mount the client-side feed loader.
**JSON API Surface:**
- Location: `src/app/api/*`
- Triggers: Browser fetches and automation/webhook requests.
- Responsibilities: Read endpoints for projects/tags/signals, AI search proxying, authenticated tag maintenance, project-tag reset, and signal ingestion.
**n8n Context Generator:**
- Location: `scripts/generate-n8n-context.mjs`
- Triggers: `pnpm n8n:context`
- Responsibilities: Read `docs/integrations/n8n/registry.json`, scan `src/` for n8n/webhook touchpoints, and regenerate `docs/integrations/n8n/CONTEXT.generated.md` plus `.planning/codebase/N8N-CONTEXT.md`.
## Error Handling
**Strategy:** Validate inputs early, return structured JSON errors from route handlers, use `notFound()` for invalid route resources, and degrade selected read paths to fallback data rather than failing the whole page render.
**Patterns:**
- Use shared Zod schemas from `src/lib/validations.ts` in `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`.
- Use `notFound()` in `src/app/[locale]/layout.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, and `src/app/[locale]/[...catchAll]/page.tsx`.
- Retry transient project/tag DB reads in `src/hooks/useProjects.ts` through `withDbRetry`.
- Degrade cached or aggregate reads in `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, and `src/lib/cache.ts` when the cache/runtime is unavailable.
- Convert route-specific domain failures into `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`.
- Detect schema drift for signal hotness columns through `src/lib/signal-hotness.ts` and downgrade to fallback sorting/calculation.
## Cross-Cutting Concerns
**Logging:** Use `console.error` and `console.warn` directly in server code such as `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/webhook/signals/route.ts`, `src/hooks/useProjects.ts`, and `src/hooks/useHome.ts`.
**Validation:** Centralize public and internal payload/query contracts in `src/lib/validations.ts`.
**Authentication:** Protect mutation/webhook endpoints with shared API-key validation from `src/lib/auth.ts`. No user/session auth layer is present in the inspected app architecture.
**Internationalization:** Keep locale routing and messages in `src/middleware.ts`, `src/i18n/request.ts`, `src/messages/en.json`, and `src/messages/zh.json`.
**Caching & Revalidation:** Use `unstable_cache`, route-level `revalidate = 300`, and `revalidatePath` after tag mutations.
**SEO & Discovery:** Build `sitemap.xml` from `getProjects` in `src/app/sitemap.ts` and block `/api/*` from indexing in `src/app/robots.ts`.
**Observability:** Restrict Vercel Analytics and Speed Insights to locale-prefixed user-facing routes in `src/app/VercelMetrics.tsx`.
**External System Boundary:** Treat n8n workflows and discovery-task services as external systems unless a concrete route exists under `src/app/api/`. The repo documents those systems in `docs/integrations/n8n/*` and `.planning/codebase/N8N-*.md`, but does not execute the queue/task infrastructure itself.
---
*Architecture analysis: 2026-04-20*