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

19 KiB

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