258 lines
19 KiB
Markdown
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*
|