docs: refresh codebase map
This commit is contained in:
+178
-153
@@ -1,232 +1,257 @@
|
||||
# Architecture
|
||||
|
||||
**Analysis Date:** 2026-04-18
|
||||
**Analysis Date:** 2026-04-20
|
||||
|
||||
## Pattern Overview
|
||||
|
||||
**Overall:** Server-first Next.js App Router monolith with localized page routes, thin API handlers, Prisma-backed data access, and small client-side interaction islands.
|
||||
**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:**
|
||||
- Use `src/app/layout.tsx` and `src/app/[locale]/layout.tsx` as the primary composition roots, with most route files implemented as async server components.
|
||||
- Keep browser-only interaction in explicit client components such as `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/search/AISearchBar.tsx`, and `src/components/signals/SignalFeedClient.tsx`.
|
||||
- Centralize most project and home-page reads in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`, even though these modules are named like React hooks.
|
||||
- Validate external input with Zod schemas from `src/lib/validations.ts` before querying or mutating the database.
|
||||
- Use Prisma as the only persistence client through `src/lib/prisma.ts`, with schema ownership in `prisma/schema.prisma`.
|
||||
- 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
|
||||
|
||||
**Routing and Layout Layer:**
|
||||
- Purpose: Resolve locale-aware routes, shared layout chrome, SEO metadata, and global CSS.
|
||||
- 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`, `src/i18n/request.ts`
|
||||
- Contains: App Router entry points, `generateMetadata`, `generateStaticParams`, `revalidate` exports, locale middleware, and SEO documents.
|
||||
- Depends on: `next-intl`, server query modules in `src/hooks`, UI components in `src/components`, and shared helpers in `src/lib`.
|
||||
- Used by: The Next.js runtime.
|
||||
|
||||
**UI Composition Layer:**
|
||||
- Purpose: Render reusable view fragments for pages and route-specific UI sections.
|
||||
- Location: `src/components/home/*`, `src/components/layout/*`, `src/components/locale/*`, `src/components/project/*`, `src/components/search/*`, `src/components/signals/*`
|
||||
- Contains: Mostly presentational components, plus a few server components that call translations or helpers directly, such as `src/components/project/ProjectList.tsx` and `src/components/project/ProjectSidebar.tsx`.
|
||||
- Depends on: Translation APIs, route props, and typed data returned by `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`.
|
||||
- Used by: Route entry points under `src/app/[locale]`.
|
||||
**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 mutation, fetch loops, local storage, and interactive filters/search.
|
||||
- Location: `src/app/VercelMetrics.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/layout/AnnouncementBar.tsx`, `src/components/locale/LocaleSwitcher.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/search/AISearchBar.tsx`, `src/components/search/HomeSearchBar.tsx`, `src/components/search/AISearchResults.tsx`, `src/components/signals/SignalFeedClient.tsx`
|
||||
- Contains: `useState`/`useEffect`-driven UI state, `window.history.replaceState`, `window.location.href`, `localStorage`, and client-side `fetch` calls to `/api/*`.
|
||||
- Depends on: Page props, Next navigation APIs, and JSON APIs implemented under `src/app/api`.
|
||||
- Used by: Server-rendered pages and layouts.
|
||||
- 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 and Read Model Layer:**
|
||||
- Purpose: Encapsulate database reads, filtering, retry logic, shape transformations, and cached read models for projects, tags, and home-page aggregates.
|
||||
**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: Prisma queries, pagination logic, tag-category grouping, home-page aggregate builders, and result-type definitions such as `ProjectWithFlatTags`, `FilterTagCategoryGroup`, and `HomePageData`.
|
||||
- Depends on: `src/lib/prisma.ts`, `src/lib/cache.ts`, `src/lib/tag-taxonomy.ts`, and Prisma types from `@prisma/client`.
|
||||
- Used by: Page routes such as `src/app/[locale]/page.tsx` and list APIs such as `src/app/api/projects/route.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 and Mutation Layer:**
|
||||
- Purpose: Expose JSON endpoints for list/detail reads, AI search proxying, signals feed reads, authenticated tag maintenance, project-tag resets, and signal ingestion.
|
||||
**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: `GET` and `POST` route handlers, request parsing, validation, response shaping, cache revalidation, and transaction-scoped maintenance logic.
|
||||
- Depends on: `src/lib/validations.ts`, `src/lib/auth.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, and the query layer in `src/hooks`.
|
||||
- Used by: Client components, external webhook callers, and automation clients.
|
||||
- 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.
|
||||
|
||||
**Domain Utility Layer:**
|
||||
- Purpose: Hold cross-route domain logic that is not tied to a single route.
|
||||
- Location: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, `src/lib/slug.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/validations.ts`, `src/lib/github/badges.ts`
|
||||
- Contains: API-key verification, cache fallback helpers, Prisma singleton setup, signal hotness scoring, slug generation, tag taxonomy inference, schema validation, and GitHub link parsing.
|
||||
- Depends on: Standard library APIs, Prisma, and Zod.
|
||||
- Used by: Both pages and API handlers.
|
||||
**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 and migrate the database model and seed baseline data.
|
||||
- Purpose: Define the application data model, migrations, and seed path.
|
||||
- Location: `prisma/schema.prisma`, `prisma/migrations/*`, `prisma/seed.ts`
|
||||
- Contains: PostgreSQL schema for `Project`, `Tag`, `ProjectTag`, `ExternalLink`, and `Signal`, plus migrations and seed data.
|
||||
- Depends on: Prisma CLI and environment-provided `DATABASE_URL`.
|
||||
- Used by: `src/lib/prisma.ts` at runtime and Prisma tooling during migrations/seed.
|
||||
- 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` forces a locale-prefixed pathname and excludes `/api`, `/_next`, and static assets from locale routing.
|
||||
2. `src/app/[locale]/layout.tsx` validates the locale, calls `setRequestLocale(locale)`, loads messages through `next-intl`, and wraps the tree with `NextIntlClientProvider`.
|
||||
3. Route pages such as `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, and `src/app/[locale]/projects/[id]/page.tsx` fetch data from `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`.
|
||||
4. Reusable server and client components render the data, with ISR enabled through `export const revalidate = 300` on the main content routes.
|
||||
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` reads `searchParams`, normalizes filters, and calls `getProjects`, `getFixedProjectTypeFilters`, and `getTagCategoryGroups` from `src/hooks/useProjects.ts`.
|
||||
2. `src/app/[locale]/projects/ProjectsPageClient.tsx` and `src/components/project/TagFilterPanel.tsx` manage filter-panel visibility and query-string updates in the browser.
|
||||
3. `src/app/[locale]/projects/ProjectsResultsClient.tsx` fetches updated pages from `src/app/api/projects/route.ts` for client-side pagination and sorting.
|
||||
4. `src/app/api/projects/route.ts` validates query parameters with Zod, then delegates to `getProjects`.
|
||||
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` toggles between traditional and AI mode and forwards the query back to the page-level handler.
|
||||
2. `src/app/[locale]/projects/ProjectsResultsClient.tsx` requests `POST /api/search/ai`.
|
||||
3. `src/app/api/search/ai/route.ts` validates the request with `ProjectQuerySchema`, forwards a GET request to the external N8N webhook URL from `N8N_AI_SEARCH_WEBHOOK`, then hydrates returned IDs by calling `getProjectsByIds` from `src/hooks/useProjects.ts`.
|
||||
4. The route sorts and filters the hydrated projects again before returning JSON to the client.
|
||||
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.
|
||||
|
||||
**Signals Feed Flow:**
|
||||
**Project Detail Flow:**
|
||||
|
||||
1. `src/app/[locale]/signals/page.tsx` renders shell content and mounts `src/components/signals/SignalFeedClient.tsx`.
|
||||
2. `SignalFeedClient` holds search text, debounce state, source filter, sort mode, and pagination cursor in component state.
|
||||
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 query, queries Prisma, computes or reads hotness metadata via `src/lib/signal-hotness.ts`, and returns cursor-based pagination.
|
||||
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.
|
||||
|
||||
**Signal Ingestion Flow:**
|
||||
**Signals Ingestion Flow:**
|
||||
|
||||
1. External automation posts to `src/app/api/webhook/signals/route.ts`.
|
||||
2. The handler validates the top-level payload and each item with `SignalWebhookPayloadSchema` and `SignalIngestionInputSchema` from `src/lib/validations.ts`.
|
||||
3. `src/lib/auth.ts` verifies `apiKey` using `crypto.timingSafeEqual`.
|
||||
4. The handler upserts signals in Prisma, computing fallback hotness when needed and downgrading if `hotScore`/`isHot` columns are absent.
|
||||
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 Maintenance Flow:**
|
||||
**Tag Governance Flow:**
|
||||
|
||||
1. Clients call `POST /api/tags/maintenance` or `POST /api/tags/reset-projects`.
|
||||
2. The route validates input, authenticates with `isValidApiKey`, and runs Prisma mutations.
|
||||
3. `src/app/api/tags/maintenance/route.ts` delegates transactional merge/update logic to `src/app/api/tags/maintenance/service.ts`.
|
||||
4. Successful mutations call `revalidatePath` for affected localized pages.
|
||||
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.
|
||||
|
||||
## State Management
|
||||
**Home Aggregate Flow:**
|
||||
|
||||
**Server State:**
|
||||
- Persistent application state lives in PostgreSQL, modeled in `prisma/schema.prisma` and accessed only through Prisma in `src/lib/prisma.ts`.
|
||||
- Page-level read models are built in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`.
|
||||
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.
|
||||
|
||||
**Cached Server State:**
|
||||
- `unstable_cache` is used in `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, and `src/app/api/tags/route.ts`.
|
||||
- `src/lib/cache.ts` provides `runWithCacheFallback` so cached fetchers can fall back to direct database reads when Incremental Cache is unavailable.
|
||||
**External Discovery / Ingestion Boundary:**
|
||||
|
||||
**Client UI State:**
|
||||
- Local component state, not a global store, drives interactivity.
|
||||
- `src/app/[locale]/projects/ProjectsPageClient.tsx` stores filter-panel expansion.
|
||||
- `src/app/[locale]/projects/ProjectsResultsClient.tsx` stores AI/traditional pagination, loading flags, and sort/limit state.
|
||||
- `src/components/signals/SignalFeedClient.tsx` stores debounced search, source filters, sort mode, cursor, and loading state.
|
||||
- `src/components/layout/AnnouncementBar.tsx` stores dismissal state in `localStorage`.
|
||||
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.
|
||||
|
||||
**URL State:**
|
||||
- Search, pagination, sort, AI mode, and taxonomy filters are encoded in the query string on `src/app/[locale]/projects/page.tsx`.
|
||||
- The projects UI treats the URL as the source of truth and synchronizes local state to it through `router.push` and `window.history.replaceState`.
|
||||
**Direct DB Maintenance Boundary:**
|
||||
|
||||
**Translation State:**
|
||||
- Locale selection is path-based and enforced by `src/middleware.ts`.
|
||||
- Message bundles load from `src/messages/en.json` and `src/messages/zh.json` via `src/i18n/request.ts`.
|
||||
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: Present projects with flattened tags and related links, independent of Prisma join-table shape.
|
||||
- Examples: `ProjectWithFlatTags` and `getProjects` in `src/hooks/useProjects.ts`, `getProjectBySlug` in `src/hooks/useProjects.ts`
|
||||
- Pattern: Read-model transformation from Prisma includes to page/API-friendly objects.
|
||||
- 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: Normalize and classify project tags into fixed categories used by filters and maintenance APIs.
|
||||
- Examples: `src/lib/tag-taxonomy.ts`, `getTagCategoryGroups` in `src/hooks/useProjects.ts`
|
||||
- Pattern: Central domain vocabulary with inference helpers shared across pages and mutations.
|
||||
- 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 raw `Signal` rows, localized text fields, JSON sections, and hotness metadata into feed-ready objects.
|
||||
- Examples: `toSignalView` and `parseSections` in `src/app/api/signals/route.ts`
|
||||
- Pattern: API-specific presentation mapping on top of Prisma rows.
|
||||
- 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.
|
||||
|
||||
**Maintenance Service:**
|
||||
- Purpose: Keep complex tag merge/update rules out of the route handler.
|
||||
- Examples: `executeTagMaintenance` and `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`
|
||||
- Pattern: Route delegates orchestration to a transaction-aware service module.
|
||||
|
||||
**Auth Guard for Internal APIs:**
|
||||
- Purpose: Reuse constant-time API-key validation across maintenance and webhook routes.
|
||||
**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 guard function, not middleware-based auth.
|
||||
- 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 App Shell:**
|
||||
**Root HTML Shell:**
|
||||
- Location: `src/app/layout.tsx`
|
||||
- Triggers: Every page render.
|
||||
- Responsibilities: Global HTML/body shell, global CSS import, and conditional Vercel analytics injection through `src/app/VercelMetrics.tsx`.
|
||||
|
||||
**Localized App Shell:**
|
||||
- Location: `src/app/[locale]/layout.tsx`
|
||||
- Triggers: Every localized page render under `/<locale>/*`.
|
||||
- Responsibilities: Locale validation, translations, site navigation, announcement bar, footer, and `NextIntlClientProvider`.
|
||||
|
||||
**Home Route:**
|
||||
- Location: `src/app/[locale]/page.tsx`
|
||||
- Triggers: `GET /zh` and `GET /en`
|
||||
- Responsibilities: Render home hero, stats, rankings, tag insights, recent timeline, and featured projects.
|
||||
|
||||
**Projects Route:**
|
||||
- Location: `src/app/[locale]/projects/page.tsx`
|
||||
- Triggers: `GET /<locale>/projects`
|
||||
- Responsibilities: Parse URL filters, render filter/search UI, fetch paginated project data, and mount the projects results client island.
|
||||
|
||||
**Project Detail Route:**
|
||||
- Location: `src/app/[locale]/projects/[id]/page.tsx`
|
||||
- Triggers: `GET /<locale>/projects/:slug`
|
||||
- Responsibilities: Fetch a single project, derive related projects, render sidebar/details, and generate route metadata.
|
||||
|
||||
**Signals Route:**
|
||||
- Location: `src/app/[locale]/signals/page.tsx`
|
||||
- Triggers: `GET /<locale>/signals`
|
||||
- Responsibilities: Render the signals feed shell and mount the client-side feed loader.
|
||||
|
||||
**API Routes:**
|
||||
- Location: `src/app/api/*`
|
||||
- Triggers: Browser fetches and external webhook clients.
|
||||
- Responsibilities: JSON read endpoints, AI search proxying, signals feed pagination, authenticated tag maintenance, project tag resets, and signal ingestion.
|
||||
- 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 the `next-intl` locale prefix policy and route matching.
|
||||
- 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 early, return structured JSON for API errors, use `notFound()` for invalid page resources, and degrade some read paths to empty or fallback results instead of hard-failing the page.
|
||||
**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 Zod validation in `src/app/api/projects/route.ts`, `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` for route-level misses.
|
||||
- Retry transient database errors in `src/hooks/useProjects.ts` via `withDbRetry`.
|
||||
- Degrade to empty or zero-count results in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` when some DB reads fail.
|
||||
- Wrap service-specific failures in `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`.
|
||||
- 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 route handlers and server query modules, for example in `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`.
|
||||
**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:** Use shared Zod schemas from `src/lib/validations.ts` for external request bodies and query parameters.
|
||||
**Validation:** Centralize public and internal payload/query contracts in `src/lib/validations.ts`.
|
||||
|
||||
**Authentication:** Protect internal mutation/webhook endpoints with API-key checks from `src/lib/auth.ts`. No session or user-account auth layer is present in the inspected files.
|
||||
**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:** Use `next-intl` with locale-prefixed routing through `src/middleware.ts`, request config in `src/i18n/request.ts`, and message bundles in `src/messages/*.json`.
|
||||
**Internationalization:** Keep locale routing and messages in `src/middleware.ts`, `src/i18n/request.ts`, `src/messages/en.json`, and `src/messages/zh.json`.
|
||||
|
||||
**Caching and Revalidation:** Use ISR-style route revalidation (`revalidate = 300`), `unstable_cache`, and `revalidatePath` after tag mutations.
|
||||
**Caching & Revalidation:** Use `unstable_cache`, route-level `revalidate = 300`, and `revalidatePath` after tag mutations.
|
||||
|
||||
**Observability:** The only built-in runtime instrumentation found is Vercel analytics in `src/app/VercelMetrics.tsx`. No separate tracing or background job framework was detected in inspected files.
|
||||
**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-18*
|
||||
*Architecture analysis: 2026-04-20*
|
||||
|
||||
+146
-108
@@ -1,178 +1,216 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-04-18
|
||||
**Analysis Date:** 2026-04-20
|
||||
|
||||
## Tech Debt
|
||||
|
||||
**Oversized mixed-responsibility modules:**
|
||||
- Issue: Data access, cache policy, retry logic, query construction, and response shaping are combined in single files instead of being split into smaller modules.
|
||||
- Issue: Database access, retry policy, cache fallback, query normalization, and response shaping are combined in the same modules instead of being split into smaller server-side layers.
|
||||
- Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/signals/SignalFeedClient.tsx`, `src/app/[locale]/layout.tsx`
|
||||
- Impact: Safe changes require understanding many unrelated branches at once; regressions are likely because query behavior, cache behavior, and UI state are tightly coupled.
|
||||
- Fix approach: Split server-side data access from presentation and client state; isolate URL/query builders, Prisma queries, cache wrappers, and presentational subcomponents into separate files before adding more behavior.
|
||||
- Impact: Safe changes require understanding unrelated concerns at once, so regressions in caching, pagination, or rendering are easy to introduce.
|
||||
- Fix approach: Split server data access into `src/lib/` or `src/server/`, keep client state in client components only, and extract URL/query helpers plus cache wrappers into dedicated modules.
|
||||
|
||||
**Misleading server utilities under `hooks/`:**
|
||||
- Issue: `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` are not React hooks. They contain Prisma queries, `unstable_cache`, retry logic, and server-only behavior.
|
||||
**Server-only data modules are mislabeled as hooks:**
|
||||
- Issue: `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` are not React hooks. They contain Prisma queries, `unstable_cache`, retry logic, and server-only fallback behavior.
|
||||
- Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`
|
||||
- Impact: The naming invites accidental client imports and makes server/client boundaries harder to reason about during refactors.
|
||||
- Fix approach: Move these modules to a server-oriented location such as `src/lib/` or `src/server/`, then keep React hooks in `src/hooks/` only.
|
||||
- Impact: The naming invites accidental client imports and hides the real server/client boundary during refactors.
|
||||
- Fix approach: Move these modules to a server-oriented location and reserve `src/hooks/` for actual React hooks.
|
||||
|
||||
**Schema-drift tolerance around signal hotness columns:**
|
||||
- Issue: The application carries compatibility code for `hotScore` and `isHot` column absence even though the Prisma schema declares both fields.
|
||||
- Files: `src/lib/signal-hotness.ts`, `src/app/api/signals/route.ts`, `src/app/api/webhook/signals/route.ts`, `prisma/schema.prisma`, `prisma/migrations/20260224120000_add_signal_hot_fields/migration.sql`
|
||||
- Impact: Deployments can run with partially applied migrations without failing fast. That reduces blast radius in production but also hides schema drift and makes behavior environment-dependent.
|
||||
- Fix approach: Treat missing columns as deployment errors after rollout is stable, or move the compatibility branch behind an explicit feature flag with clear removal criteria.
|
||||
**n8n workflow state is documented, but executable exports are still missing:**
|
||||
- Issue: The repository now carries rich n8n metadata and generated context, but `docs/integrations/n8n/exports/` contains only `.gitkeep` and every workflow entry currently records `Export file: not recorded`.
|
||||
- Files: `docs/integrations/n8n/README.md`, `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/exports/.gitkeep`, `scripts/generate-n8n-context.mjs`
|
||||
- Impact: The repo captures contracts and touchpoints, but not the actual workflow logic. Incident response, review, and reproducibility still depend on external n8n access.
|
||||
- Fix approach: Commit workflow exports alongside `registry.json`, treat them as versioned artifacts, and keep `pnpm n8n:context` as a verification step instead of the primary source of truth.
|
||||
|
||||
**Boot-time failure for AI search route configuration:**
|
||||
- Issue: The AI search route reads `process.env.N8N_AI_SEARCH_WEBHOOK!` at module scope and throws immediately if the variable is missing.
|
||||
- Files: `src/app/api/search/ai/route.ts`
|
||||
- Impact: A missing env var breaks route initialization instead of returning a controlled runtime error. This is fragile in local setup, preview deployments, and tests.
|
||||
- Fix approach: Read the env var inside the request handler, return a structured `500`, and cover the missing-env path with tests.
|
||||
**Repository instructions drift from the actual toolchain:**
|
||||
- Issue: `AGENTS.md` documents `pnpm test:e2e` and an `e2e/` suite, but `package.json` has no `test:e2e` script and the repo contains no `playwright.config.*` or `e2e/` directory.
|
||||
- Files: `AGENTS.md`, `package.json`
|
||||
- Impact: Contributors can assume browser-level regression coverage exists when it does not.
|
||||
- Fix approach: Either add the documented Playwright harness or remove the claim from `AGENTS.md` so verification expectations match reality.
|
||||
|
||||
## Known Bugs
|
||||
|
||||
**Related projects are not selected from the actual related set:**
|
||||
- Symptoms: The project detail page fetches only the latest three projects and then filters that tiny set for shared tags.
|
||||
**Related projects are picked from the newest three projects, not the actual related set:**
|
||||
- Symptoms: The project detail page fetches `getProjects({ limit: 3 })` and then filters that tiny subset for shared tags.
|
||||
- Files: `src/app/[locale]/projects/[id]/page.tsx`, `src/hooks/useProjects.ts`
|
||||
- Trigger: Open a project whose related items are not in the newest three active projects.
|
||||
- Workaround: None in code. The page simply renders fewer or zero related projects.
|
||||
- Trigger: Open a project whose genuinely related items are not among the newest three active projects.
|
||||
- Workaround: None in code. The page simply shows fewer or zero related projects.
|
||||
|
||||
**Interactive navigation and CTA elements are placeholders:**
|
||||
- Symptoms: Several visible controls navigate to `#`, the mobile menu button has no behavior, and the newsletter form has no action handler.
|
||||
**AI search route crashes at module initialization when its env var is missing:**
|
||||
- Symptoms: Importing the route throws before handling a request because `process.env.N8N_AI_SEARCH_WEBHOOK` is read at module scope and hard-fails.
|
||||
- Files: `src/app/api/search/ai/route.ts`
|
||||
- Trigger: Start the app, build, or run tests without `N8N_AI_SEARCH_WEBHOOK` configured.
|
||||
- Workaround: Provide the env var in every environment that loads the route.
|
||||
|
||||
**Visible navigation and conversion entry points are placeholders:**
|
||||
- Symptoms: Submit-project, footer resource/legal links, social links, and the newsletter form do not connect to real destinations or handlers. The mobile menu button also has no behavior.
|
||||
- Files: `src/app/[locale]/layout.tsx`, `src/components/project/ProjectCard.tsx`
|
||||
- Trigger: Click `submitProject`, footer/legal/social links, the mobile menu button, or submit the newsletter form.
|
||||
- Workaround: None in code. Users stay on the same page or submit a form with no integration.
|
||||
- Trigger: Click the submit CTA, footer links, social buttons, or submit the newsletter form on any localized page.
|
||||
- Workaround: None in code. Users stay on the same page or submit to a no-op form target.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Admin and ingestion endpoints rely on a single shared API key in request bodies:**
|
||||
- Risk: The code checks only `apiKey` equality. There is no request signature, timestamp, nonce, replay protection, or rate limiting in application code.
|
||||
- Files: `src/lib/auth.ts`, `src/app/api/webhook/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`
|
||||
- Current mitigation: Timing-safe comparison via `crypto.timingSafeEqual` in `src/lib/auth.ts`
|
||||
- Recommendations: Prefer HMAC-signed requests or provider-native webhook signatures, reject stale timestamps, add rate limiting, and keep network allowlisting outside the app if that is part of deployment.
|
||||
**Admin and ingestion endpoints depend on a single shared API key passed in request bodies:**
|
||||
- Risk: The code checks only equality against `WEBHOOK_API_KEY`. There is no request signature, timestamp, nonce, replay protection, or application-side rate limiting.
|
||||
- Files: `src/lib/auth.ts`, `src/app/api/webhook/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/07-signals-aggregation.md`, `docs/integrations/n8n/workflows/08-project-tag-reset.md`
|
||||
- Current mitigation: `src/lib/auth.ts` uses `crypto.timingSafeEqual`, and the n8n docs call out the shared-secret problem explicitly.
|
||||
- Recommendations: Move to HMAC-signed requests or provider-native webhook verification, reject stale timestamps, and apply rate limiting at the app or edge layer.
|
||||
|
||||
**AI search leaks query text through URL-based webhook forwarding:**
|
||||
- Risk: The route forwards user search text and filters to an external service with a `GET` request query string.
|
||||
- Files: `src/app/api/search/ai/route.ts`
|
||||
**AI search forwards user queries to n8n in a GET query string:**
|
||||
- Risk: Search text and filters are serialized into the webhook URL, which is more likely to be logged by reverse proxies, platforms, and third-party tooling.
|
||||
- Files: `src/app/api/search/ai/route.ts`, `docs/integrations/n8n/workflows/06-rag-project-search.md`
|
||||
- Current mitigation: Not detected in code.
|
||||
- Recommendations: Send the payload with `POST`, avoid placing search text in URLs, and document the data handling expectations for the external n8n workflow.
|
||||
- Recommendations: Switch to `POST`, move request data into the body, and document the retention/logging expectations for the n8n side.
|
||||
|
||||
**Raw project markdown can load third-party images:**
|
||||
- Risk: Markdown rendering sanitizes HTML, but image URLs still render through plain `<img>` elements. If project content is not fully trusted, remote images can leak user IPs and referrers to arbitrary hosts.
|
||||
**Runtime secret handling spreads certificate material and credentials outside the repo boundary:**
|
||||
- Risk: Database TLS assets are reconstructed from env vars onto disk at runtime and the client identity password is appended to the Prisma datasource URL. The n8n docs also confirm workflows that need app secrets or raw database access outside this repository.
|
||||
- Files: `src/lib/prisma-url.ts`, `src/lib/prisma.ts`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/04-github-star-refresh.md`, `docs/integrations/n8n/workflows/05-project-description-vectorization.md`
|
||||
- Current mitigation: `src/lib/prisma-url.ts` writes files with restrictive permissions and uses an environment-configurable certificate directory.
|
||||
- Recommendations: Prefer mounted secrets over reconstructing certs in temp storage, keep passwords out of DSN strings where possible, scope database users per workflow, and document secret ownership/rotation outside the app.
|
||||
|
||||
**Raw markdown content can load remote images from arbitrary hosts:**
|
||||
- Risk: Markdown rendering sanitizes HTML, but it still renders remote `<img>` URLs directly. If project content is not fully trusted, remote hosts can observe client IPs and referrers.
|
||||
- Files: `src/components/project/MarkdownContent.tsx`
|
||||
- Current mitigation: `rehype-sanitize` removes unsafe HTML, and external links use `rel="noopener noreferrer"`.
|
||||
- Recommendations: Proxy images, restrict allowed image hosts, or disable markdown images for untrusted content. This concern depends on whether project content is curated or user-submitted; the code alone does not establish that trust boundary.
|
||||
- Current mitigation: `rehype-sanitize` removes unsafe HTML and external links use `rel="noopener noreferrer"`.
|
||||
- Recommendations: Proxy images, restrict allowed hosts, or disable markdown image rendering for untrusted content.
|
||||
|
||||
## Performance Bottlenecks
|
||||
|
||||
**Signal ingestion performs per-item existence checks and upserts sequentially:**
|
||||
- Problem: Each signal performs validation, `findUnique`, and `upsert` inside a loop. The route processes up to 100 signals per request and does not batch database writes.
|
||||
- Problem: Each signal performs validation, `findUnique`, and `upsert` inside a loop, with optional retry into a second upsert path when hotness columns are unavailable.
|
||||
- Files: `src/app/api/webhook/signals/route.ts`
|
||||
- Cause: The ingestion path is written for straightforward correctness and partial failure reporting, not throughput.
|
||||
- Improvement path: Preload existing records in bulk, batch inserts/updates where possible, and use a transaction or job queue if partial writes are unacceptable.
|
||||
- Cause: The route is optimized for straightforward per-item error reporting, not for throughput.
|
||||
- Improvement path: Bulk-load existing keys, batch inserts/updates, and move high-volume ingestion to a queue or transactional batch writer.
|
||||
|
||||
**Project and signal text search use `contains` scans without matching full-text indexes:**
|
||||
- Problem: Search endpoints query multiple text fields with case-insensitive `contains`, but the Prisma schema indexes only sorting/filter columns such as `slug`, `status`, `createdAt`, `githubStars`, `publishedAt`, `hotScore`, and `engagement`.
|
||||
**Project and signal search rely on case-insensitive `contains` scans instead of search-specific indexes:**
|
||||
- Problem: Search touches multiple text fields with `contains`, while `prisma/schema.prisma` indexes sorting/filter columns but no text-search structures.
|
||||
- Files: `src/hooks/useProjects.ts`, `src/app/api/signals/route.ts`, `prisma/schema.prisma`
|
||||
- Cause: Search logic is application-level string matching with no dedicated full-text search index.
|
||||
- Improvement path: Add PostgreSQL full-text search or trigram indexes for project and signal search fields, or move search to a dedicated search service.
|
||||
- Cause: Search is implemented as application-level substring matching over Prisma filters.
|
||||
- Improvement path: Add PostgreSQL full-text or trigram indexes, or move search to a dedicated retrieval service.
|
||||
|
||||
**Silent fallback paths can cache outage-shaped responses:**
|
||||
- Problem: Several server-side queries catch database errors and return empty arrays or zero counts instead of surfacing failure.
|
||||
- Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`
|
||||
- Cause: The code prefers graceful degradation, combined with `unstable_cache` wrappers in the same modules.
|
||||
- Improvement path: Separate fallback behavior from cached fetchers, emit structured telemetry, and avoid caching degraded empty results for homepage and listing data.
|
||||
|
||||
**AI search hydrates external results with local DB lookups and in-memory reordering:**
|
||||
- Problem: The AI route requests candidate IDs from n8n, fetches projects from the database, then matches records back to the remote order with repeated `find` calls.
|
||||
**AI search does extra in-memory filtering and O(n²) result hydration after the webhook call:**
|
||||
- Problem: `src/app/api/search/ai/route.ts` fetches candidate IDs from n8n, loads projects from the database, repeatedly calls `projects.find(...)` to restore ranking order, and then re-applies tag/domain/product-form filtering in memory.
|
||||
- Files: `src/app/api/search/ai/route.ts`, `src/hooks/useProjects.ts`
|
||||
- Cause: Remote ranking and local hydration are stitched together in the route layer instead of a dedicated search service.
|
||||
- Improvement path: Preserve order with an ID-to-project map, keep pagination logic on one side, and avoid fetching more rows than the final page requires.
|
||||
- Cause: Responsibility is split awkwardly between n8n ranking and route-side post-processing.
|
||||
- Improvement path: Use an ID-to-project map, keep filtering ownership on one side of the contract, and avoid loading more rows than the final page needs.
|
||||
|
||||
**Graceful-degradation fallbacks can return empty or zero-shaped data under DB failure:**
|
||||
- Problem: Homepage and project metadata helpers catch database errors and degrade to empty arrays or zero counts.
|
||||
- Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`
|
||||
- Cause: The code prefers availability over surfacing failures and uses the same modules for cached and uncached access.
|
||||
- Improvement path: Separate failure-aware fetchers from UI fallback shaping, emit explicit telemetry, and avoid treating outage-shaped responses as normal product data.
|
||||
|
||||
## Fragile Areas
|
||||
|
||||
**Projects results UI has duplicated URL, pagination, and fetch state machines:**
|
||||
**Projects results UI maintains two overlapping state machines in one client component:**
|
||||
- Files: `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`
|
||||
- Why fragile: Traditional search and AI search maintain separate state, pagination, URL sync, and fetch flows inside one client component. The file also manually uses `window.history.replaceState`, which is easy to desynchronize from server-rendered state.
|
||||
- Safe modification: Change one mode at a time, verify deep-linking and pagination after every edit, and extract shared query/pagination logic before adding more filters or sorts.
|
||||
- Why fragile: Traditional search and AI search keep separate pagination, sort, loading, error, and URL-sync state inside the same file. The component also mutates the browser URL with `window.history.replaceState`, which is easy to desynchronize from server-rendered state.
|
||||
- Safe modification: Change only one search mode at a time, verify deep-linking after every edit, and extract shared query-state helpers before adding more filters or sorts.
|
||||
- Test coverage: No tests detected for `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/api/projects/route.ts`, or `src/app/api/search/ai/route.ts`
|
||||
|
||||
**Tag maintenance endpoints mix validation, mutation, and cache invalidation in request handlers:**
|
||||
- Files: `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/maintenance/service.ts`, `src/app/api/tags/reset-projects/route.ts`
|
||||
- Why fragile: Business rules, Prisma writes, per-project iteration, and `revalidatePath` calls are tightly coupled. Bulk operations can change many rows and many pages in one request.
|
||||
- Safe modification: Keep schema validation and mutation rules under tests, preserve transaction boundaries, and review all `revalidatePath` targets before changing route semantics.
|
||||
- Test coverage: Route and service tests exist for these files, but there are no broader integration tests across Prisma, cache invalidation, and localized page rendering.
|
||||
**Direct database-writing n8n jobs bypass repository API routes and cache invalidation:**
|
||||
- Files: `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/04-github-star-refresh.md`, `docs/integrations/n8n/workflows/05-project-description-vectorization.md`, `prisma/schema.prisma`, `src/hooks/useProjects.ts`, `src/app/api/search/ai/route.ts`
|
||||
- Why fragile: The documented star-refresh and vectorization workflows write straight to Postgres instead of going through repository routes. That bypasses app-level validation, audit points, and any future route-based revalidation logic.
|
||||
- Safe modification: Treat the Prisma schema and n8n registry as one shared contract, and change database columns/indexes only with coordinated workflow updates plus runtime verification.
|
||||
- Test coverage: No contract or integration tests detected for these cross-system write paths
|
||||
|
||||
**Shared layout contains production UI plus unfinished placeholders:**
|
||||
- Files: `src/app/[locale]/layout.tsx`
|
||||
- Why fragile: The same layout file owns metadata, locale setup, navigation, announcement bar, newsletter section, footer, and placeholder interactions.
|
||||
- Safe modification: Extract navigation, newsletter, and footer into dedicated components before wiring real integrations.
|
||||
- Test coverage: No tests detected for this file.
|
||||
**The project ingestion loop is not self-contained in this repository:**
|
||||
- Files: `docs/integrations/n8n/DATAFLOW.md`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/01-topic-discovery.md`, `docs/integrations/n8n/workflows/02-github-trending-discovery.md`, `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md`
|
||||
- Why fragile: The docs explicitly depend on `/api/discovery/check-duplicates` and `/api/discovery/tasks*`, but there is no `src/app/api/discovery/` implementation in this repo. The repo depends on an upstream discovery service to stay operational.
|
||||
- Safe modification: Treat discovery endpoints as an external contract, version their request/response shapes, and avoid assuming local end-to-end reproducibility for ingestion work.
|
||||
- Test coverage: No in-repo tests can cover the full discovery-to-ingestion flow because the required service is absent here
|
||||
|
||||
**Bulk tag mutation endpoints couple validation, writes, and cache invalidation at the route layer:**
|
||||
- Files: `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/maintenance/service.ts`, `src/app/api/tags/reset-projects/route.ts`
|
||||
- Why fragile: A single request can mutate many tags or projects and immediately trigger localized page revalidation. Business rules, transactional writes, and cache invalidation are tightly coupled.
|
||||
- Safe modification: Preserve transaction boundaries, keep mutation rules covered with focused tests, and review every `revalidatePath` target before changing route semantics.
|
||||
- Test coverage: Unit-style route and service tests exist, but there are no broader integration tests across Prisma writes, cache invalidation, and localized page rendering
|
||||
|
||||
## Scaling Limits
|
||||
|
||||
**Offset pagination on projects will degrade as data grows:**
|
||||
- Current capacity: `src/hooks/useProjects.ts` uses `skip` and `take`; `src/app/api/projects/route.ts` allows `limit` up to `100`.
|
||||
- Limit: High page numbers require larger offset scans in PostgreSQL, especially when combined with multi-join tag filters and text search.
|
||||
- Scaling path: Move to cursor-based pagination for project listings or restrict deep paging with indexed sort keys.
|
||||
**Offset pagination on projects degrades with table size:**
|
||||
- Current capacity: `src/hooks/useProjects.ts` uses `skip` and `take`, and `src/app/api/projects/route.ts` allows `limit` up to `100`.
|
||||
- Limit: Deep pages require larger offset scans in PostgreSQL, especially when combined with tag joins and text filters.
|
||||
- Scaling path: Move project listings to cursor pagination keyed by indexed sort fields or restrict deep paging.
|
||||
|
||||
**Signal search and sort scale with table growth, not just page size:**
|
||||
- Current capacity: `src/app/api/signals/route.ts` limits pages to `50` items, but search still scans multiple text columns and sort-by-hot depends on computed/indexed metadata.
|
||||
- Limit: Query latency rises as the `signals` table grows because there is no text-search index for `q`.
|
||||
- Scaling path: Add dedicated search indexes and keep cursor pagination tied to indexed sort orders only.
|
||||
**Signals pagination is ordered by mutable ranking fields:**
|
||||
- Current capacity: `src/app/api/signals/route.ts` sorts hot feeds by `isHot`, `hotScore`, `engagement`, `publishedAt`, and `id`, but cursors by `id` only.
|
||||
- Limit: As new signals arrive or hotness changes, clients can observe duplicates or skips between pages because the rank can move independently of the cursor key.
|
||||
- Scaling path: Use a stable compound cursor that includes the sort fields or snapshot the ranking inputs for pagination windows.
|
||||
|
||||
**Bulk tag reset revalidates pages per updated project:**
|
||||
- Current capacity: `src/app/api/tags/reset-projects/route.ts` accepts up to `100` projects and revalidates both locale list pages plus two detail pages per updated slug.
|
||||
- Limit: A large batch creates many cache invalidations and can amplify request time.
|
||||
- Scaling path: Batch revalidation, use broader tag-based invalidation if available, or move bulk operations to a background job.
|
||||
**Bulk tag reset scales linearly in both writes and cache invalidations:**
|
||||
- Current capacity: `src/app/api/tags/reset-projects/route.ts` can process many projects in one request and revalidate both locale list pages plus every updated detail page.
|
||||
- Limit: Large batch operations increase request time and invalidation fan-out.
|
||||
- Scaling path: Batch revalidation, shift large maintenance jobs to background execution, or use broader cache-tag invalidation when available.
|
||||
|
||||
## Dependencies at Risk
|
||||
|
||||
**`next/cache` `unstable_cache` behavior is runtime-sensitive:**
|
||||
- Risk: Both server data modules include custom fallback logic for missing incremental cache support, which means cache behavior is not consistent across every execution context.
|
||||
- Impact: The same function can behave differently in local development, tests, and production-like runtimes.
|
||||
- Migration plan: Centralize cache wrappers in one server utility, document expected runtimes, and replace `unstable_cache` usage with stable APIs when the project upgrades to a supported alternative.
|
||||
**External discovery services are mandatory but not versioned here:**
|
||||
- Risk: Project discovery, dedupe, and task lifecycle all depend on endpoints that are documented but not implemented in this repo.
|
||||
- Impact: Local development, replay, debugging, and disaster recovery are incomplete without a second system.
|
||||
- Migration plan: Either bring `src/app/api/discovery/*` into the repo or maintain a separately versioned API contract with tests and operational ownership.
|
||||
|
||||
**External AI search depends on an n8n webhook contract with no local fallback:**
|
||||
- Risk: The project assumes an external response shape and throws or fails requests when the webhook is unavailable or changes shape.
|
||||
- Impact: `/api/search/ai` becomes a single external point of failure for AI search.
|
||||
- Migration plan: Version the webhook contract, add contract tests, and consider a local adapter layer that can degrade more predictably.
|
||||
**AI search depends on an external n8n webhook contract with no local fallback:**
|
||||
- Risk: `/api/search/ai` assumes the remote workflow exists, responds quickly, and preserves the `results[].id` plus `similarity` schema.
|
||||
- Impact: AI search becomes a single external point of failure and contract drift breaks the feature immediately.
|
||||
- Migration plan: Version the webhook contract in `docs/integrations/n8n/registry.json`, add contract tests around `src/app/api/search/ai/route.ts`, and return controlled degraded responses when the webhook is unavailable.
|
||||
|
||||
**Workflow metadata is committed, but workflow behavior still lives outside git:**
|
||||
- Risk: The repo carries registry entries and generated context, but not the executable n8n exports.
|
||||
- Impact: Reviewers can understand intent, but they still cannot reproduce or diff actual workflow logic from the repository alone.
|
||||
- Migration plan: Start committing workflow exports to `docs/integrations/n8n/exports/` and reference them from `docs/integrations/n8n/registry.json`.
|
||||
|
||||
**`unstable_cache` behavior still depends on runtime capabilities:**
|
||||
- Risk: The app uses `runWithCacheFallback` to catch environments where `unstable_cache` is unavailable.
|
||||
- Impact: Cache behavior differs across local development, tests, and deployed runtimes, which complicates debugging and performance expectations.
|
||||
- Migration plan: Centralize cache policy, document supported runtimes, and replace `unstable_cache` with stable APIs when the stack allows it.
|
||||
|
||||
## Missing Critical Features
|
||||
|
||||
**End-to-end test harness is absent from the repository:**
|
||||
- Problem: `package.json` exposes only `pnpm test` for Vitest. No `playwright.config.*`, no `e2e/` directory, and no `test:e2e` script are present in the repository.
|
||||
- Blocks: Critical user flows such as localized routing, project filtering, AI search, signals pagination, and webhook-backed content updates have no browser-level regression protection.
|
||||
**The repository does not contain a self-contained discovery ingestion loop:**
|
||||
- Problem: The n8n docs depend on discovery task endpoints that do not exist under `src/app/api/`, so the repo cannot run its own project-intake workflow end to end.
|
||||
- Blocks: End-to-end ingestion tests, local replay of failed discovery tasks, and full incident debugging from this repo alone.
|
||||
|
||||
**User-facing submission and newsletter flows are not implemented:**
|
||||
- Problem: The visible submission CTA and newsletter UI are placeholders with no connected backend or external provider.
|
||||
- Blocks: Users cannot actually submit projects, subscribe to updates, or access legal/resource destinations from the shipped UI.
|
||||
**Browser-level regression coverage is missing:**
|
||||
- Problem: The repo ships no `playwright.config.*`, no `e2e/` directory, and no `test:e2e` script even though repository guidance claims they exist.
|
||||
- Blocks: Localized routing, multi-step filtering, AI search UX, and layout-level interaction regressions are not protected at the browser layer.
|
||||
|
||||
**User-facing submission and newsletter flows are still not implemented:**
|
||||
- Problem: The visible submit-project and newsletter UI does not connect to backend handlers or third-party providers.
|
||||
- Blocks: Users cannot submit projects, subscribe for updates, or rely on footer resource/legal/social destinations.
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
**Search and listing APIs are untested:**
|
||||
**Search and listing APIs remain untested:**
|
||||
- What's not tested: `GET /api/projects`, `GET /api/projects/[slug]`, `POST /api/search/ai`, and `GET /api/signals`
|
||||
- Files: `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`
|
||||
- Risk: Pagination, filtering, sort correctness, env-missing behavior, and external-service failure handling can break unnoticed.
|
||||
- Risk: Pagination, sorting, env-missing behavior, cursor correctness, and external-service error handling can break unnoticed.
|
||||
- Priority: High
|
||||
|
||||
**Signal ingestion path is untested:**
|
||||
- What's not tested: Validation, per-item failure accounting, schema-drift fallback, and write behavior in `POST /api/webhook/signals`
|
||||
**Signal ingestion still has no route-level tests:**
|
||||
- What's not tested: Validation, per-item error accounting, hotness fallback, and write behavior in `POST /api/webhook/signals`
|
||||
- Files: `src/app/api/webhook/signals/route.ts`, `src/lib/signal-hotness.ts`
|
||||
- Risk: Ingestion regressions can silently drop, mis-rank, or partially write signals.
|
||||
- Priority: High
|
||||
|
||||
**Large client components and shared layout have no regression tests:**
|
||||
- What's not tested: Filter toggling, URL synchronization, pagination controls, AI/traditional mode switching, signal feed interactions, and layout placeholders
|
||||
- Files: `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/signals/SignalFeedClient.tsx`, `src/app/[locale]/layout.tsx`
|
||||
- Risk: UI regressions are likely because these files are state-heavy and have many interaction branches.
|
||||
**n8n and discovery contracts have no automated verification in this repo:**
|
||||
- What's not tested: That `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/CONTEXT.generated.md`, and route expectations stay aligned with live n8n workflows and the external discovery service.
|
||||
- Files: `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/CONTEXT.generated.md`, `scripts/generate-n8n-context.mjs`, `src/app/api/search/ai/route.ts`, `src/app/api/tags/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts`
|
||||
- Risk: Cross-system contract drift is detected late, usually only after production failures.
|
||||
- Priority: High
|
||||
|
||||
**Current tests focus narrowly on tag maintenance and schema validation:**
|
||||
- What's not tested: Most database-backed pages and non-tag APIs outside a few route/service units
|
||||
- Files: `src/app/api/tags/maintenance/route.test.ts`, `src/app/api/tags/maintenance/service.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, `src/app/api/tags/route.test.ts`, `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, `src/lib/validations.tag-maintenance.test.ts`
|
||||
- Risk: The test suite gives confidence for tag admin flows but not for the main product surfaces.
|
||||
**Large client components and shared layout have no regression tests:**
|
||||
- What's not tested: Filter toggling, URL synchronization, AI/traditional search mode switching, signal feed paging, and layout-level placeholder interactions
|
||||
- Files: `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/signals/SignalFeedClient.tsx`, `src/app/[locale]/layout.tsx`
|
||||
- Risk: UI regressions are likely because these files are state-heavy and branchy.
|
||||
- Priority: High
|
||||
|
||||
**Current tests focus on tag admin flows and low-level helpers, not main user journeys:**
|
||||
- What's not tested: Most database-backed pages and operational boundaries outside tag maintenance, auth comparison, validation schemas, and Prisma URL construction
|
||||
- Files: `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, `src/app/api/tags/maintenance/service.test.ts`, `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, `src/lib/validations.tag-maintenance.test.ts`, `src/lib/prisma-url.test.ts`
|
||||
- Risk: The suite provides confidence for admin mutation helpers but not for the main product surfaces or external integration edges.
|
||||
- Priority: Medium
|
||||
|
||||
---
|
||||
|
||||
*Concerns audit: 2026-04-18*
|
||||
*Concerns audit: 2026-04-20*
|
||||
|
||||
@@ -1,148 +1,126 @@
|
||||
# Coding Conventions
|
||||
|
||||
**Analysis Date:** 2026-04-18
|
||||
**Analysis Date:** 2026-04-20
|
||||
|
||||
## Naming Patterns
|
||||
|
||||
**Files:**
|
||||
- Use Next.js route filenames in `src/app`: `page.tsx`, `layout.tsx`, `route.ts`, `not-found.tsx`, `robots.ts`, and `sitemap.ts` as seen in `src/app/[locale]/page.tsx`, `src/app/[locale]/layout.tsx`, `src/app/api/projects/route.ts`, and `src/app/[locale]/not-found.tsx`.
|
||||
- Use `PascalCase.tsx` for reusable components in `src/components`, for example `src/components/project/ProjectCard.tsx`, `src/components/project/TagFilterPanel.tsx`, and `src/components/signals/SignalFeedClient.tsx`.
|
||||
- Use lower-case utility filenames in `src/lib`, for example `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, and `src/lib/validations.ts`.
|
||||
- `src/hooks` is not limited to React hooks. `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` export server-side data access and aggregation functions, not hook APIs. Extend those files only when adding the same kind of server query layer.
|
||||
- Use Next.js App Router filenames in `src/app`, including `page.tsx`, `layout.tsx`, and `route.ts`, as seen in `src/app/[locale]/page.tsx`, `src/app/[locale]/layout.tsx`, `src/app/api/projects/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Use `PascalCase.tsx` for reusable components in `src/components`, for example `src/components/search/HomeSearchBar.tsx`, `src/components/project/ProjectDetail.tsx`, and `src/components/signals/SignalFeedClient.tsx`.
|
||||
- Use lower-case or kebab-case utility filenames in `src/lib`, for example `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, `src/lib/prisma-url.ts`, and `src/lib/tag-taxonomy.ts`.
|
||||
- Keep tests co-located and named `*.test.ts`, for example `src/lib/auth.test.ts`, `src/app/api/tags/route.test.ts`, and `src/app/api/tags/reset-projects/route.test.ts`.
|
||||
- Treat `src/hooks` as a mixed server query layer plus client hooks. `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` are not React hooks despite the `use*` prefix.
|
||||
|
||||
**Functions:**
|
||||
- Use `camelCase` for functions and helpers: `isValidApiKey` in `src/lib/auth.ts`, `normalizeProjectSort` in `src/hooks/useProjects.ts`, `runWithCacheFallback` in `src/lib/cache.ts`, and `collectSelectedTagSlugs` in `src/app/api/tags/reset-projects/route.ts`.
|
||||
- Use `PascalCase` only for React components and error classes: `ProjectCard` in `src/components/project/ProjectCard.tsx`, `ProjectsResultsClient` in `src/app/[locale]/projects/ProjectsResultsClient.tsx`, and `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`.
|
||||
- Use `GET` and `POST` named exports for route handlers in `src/app/api/**/route.ts`.
|
||||
- Use `camelCase` for helpers and query functions, such as `isValidApiKey` in `src/lib/auth.ts`, `normalizeProjectSort` in `src/hooks/useProjects.ts`, `parseSlugList` in `src/app/api/projects/route.ts`, and `getTimestamp` in `src/app/api/search/ai/route.ts`.
|
||||
- Reserve `PascalCase` for React components, prop interfaces, and domain error classes, such as `ProjectDetail` in `src/components/project/ProjectDetail.tsx` and `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`.
|
||||
- Export route handlers as uppercase HTTP verbs from `src/app/api/**/route.ts`, for example `GET` in `src/app/api/tags/route.ts` and `POST` in `src/app/api/webhook/signals/route.ts`.
|
||||
|
||||
**Variables:**
|
||||
- Use `UPPER_SNAKE_CASE` for constants and configuration knobs, for example `DB_RETRY_DELAYS_MS` in `src/hooks/useProjects.ts`, `DEFAULT_RESET_CATEGORIES` in `src/app/api/tags/reset-projects/route.ts`, `TAGS_CACHE_REVALIDATE_SECONDS` in `src/app/api/tags/route.ts`, and `N8N_WEBHOOK_URL` in `src/app/api/search/ai/route.ts`.
|
||||
- Use descriptive typed local variables for parsed or normalized input, such as `validatedQuery` in `src/app/api/projects/route.ts`, `normalizedTagSlugs` in `src/hooks/useProjects.ts`, and `validationResult` in `src/app/api/webhook/signals/route.ts`.
|
||||
- Use `UPPER_SNAKE_CASE` for configuration constants and env-backed settings, such as `N8N_WEBHOOK_URL` in `src/app/api/search/ai/route.ts`, `TAGS_CACHE_REVALIDATE_SECONDS` in `src/app/api/tags/route.ts`, `DB_RETRY_DELAYS_MS` in `src/hooks/useProjects.ts`, and `ENV_KEYS` in `src/lib/prisma-url.test.ts`.
|
||||
- Use descriptive names for parsed and normalized input, such as `validatedQuery` in `src/app/api/projects/route.ts`, `validationResult` in `src/app/api/webhook/signals/route.ts`, and `normalizedTagSlugs` in `src/hooks/useProjects.ts`.
|
||||
|
||||
**Types:**
|
||||
- Prefer `type` aliases for data shapes and Prisma payloads, for example `ProjectWithFlatTags` in `src/hooks/useProjects.ts`, `HomePageData` in `src/hooks/useHome.ts`, and `ResetResultItem` in `src/app/api/tags/reset-projects/route.ts`.
|
||||
- Use `interface` for component props, for example `ProjectCardProps` in `src/components/project/ProjectCard.tsx`, `ProjectsPageClientProps` in `src/app/[locale]/projects/ProjectsPageClient.tsx`, and `SignalFeedClientProps` in `src/components/signals/SignalFeedClient.tsx`.
|
||||
- Prefer `type` aliases for Prisma payloads and request payload shapes, such as `ProjectWithFlatTags` in `src/hooks/useProjects.ts`, `SignalWebhookPayload` in `src/lib/validations.ts`, and `ResetProjectsRouteTxMock` in `src/app/api/tags/reset-projects/route.test.ts`.
|
||||
- Prefer `interface` for React props, such as `HomeSearchBarProps` in `src/components/search/HomeSearchBar.tsx` and `ProjectDetailProps` in `src/components/project/ProjectDetail.tsx`.
|
||||
|
||||
## Code Style
|
||||
|
||||
**Formatting:**
|
||||
- Prettier is configured in `.prettierrc.json` for 2-space indentation, semicolons, double quotes, trailing commas set to `es5`, and `printWidth` 100.
|
||||
- The repository is not uniformly formatted to that config. Files such as `src/lib/validations.ts`, `src/hooks/useProjects.ts`, and `src/app/api/tags/maintenance/route.ts` match the configured double-quote and semicolon style, while `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`, `src/lib/auth.ts`, and `src/components/project/ProjectCard.tsx` use single quotes and omit semicolons.
|
||||
- For new files, follow `.prettierrc.json`. When editing existing files, preserve the file-local style unless the whole file is reformatted.
|
||||
- Follow `.prettierrc.json`: 2-space indentation, semicolons, double quotes, trailing commas `es5`, and `printWidth` 100.
|
||||
- `AGENTS.md` treats Prettier as authoritative and `pnpm` as the required package manager.
|
||||
- The codebase currently has mixed formatting. Files such as `src/lib/validations.ts`, `src/lib/prisma-url.ts`, and `src/app/api/tags/route.ts` match the configured double-quote style, while `src/lib/auth.ts`, `src/app/api/projects/route.ts`, and `src/app/api/search/ai/route.ts` still use single quotes and omit semicolons.
|
||||
- For new files, follow `.prettierrc.json`. When editing an existing file with a different quote style, either preserve the local file style for a surgical change or reformat the full file consistently.
|
||||
|
||||
**Linting:**
|
||||
- ESLint extends `next/core-web-vitals` and `prettier` in `.eslintrc.json`.
|
||||
- `console.warn` and `console.error` are allowed; other `console` calls are warned by `no-console`.
|
||||
- Quality checks on 2026-04-18: `pnpm lint` passed with `✔ No ESLint warnings or errors`.
|
||||
- `.eslintrc.json` extends `next/core-web-vitals` and `prettier`.
|
||||
- Only `console.warn` and `console.error` are explicitly allowed by `no-console`. This matches the logging used in `src/app/api/tags/route.ts`, `src/app/api/search/ai/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Current verification on 2026-04-20: `pnpm lint` passed with no warnings or errors.
|
||||
|
||||
## Import Organization
|
||||
|
||||
**Order:**
|
||||
1. Framework and platform imports first, for example `next/server`, `next/cache`, `zod`, `@prisma/client`, or `react`.
|
||||
2. Internal alias imports from `@/`, for example `@/lib/prisma`, `@/hooks/useProjects`, and `@/lib/validations`.
|
||||
3. Relative imports last, for example `./service` in `src/app/api/tags/maintenance/route.ts`.
|
||||
1. Framework and platform imports first, such as `next/server`, `next/cache`, `zod`, `crypto`, `fs`, or `@prisma/client`, as seen in `src/app/api/webhook/signals/route.ts` and `src/lib/prisma-url.ts`.
|
||||
2. Internal alias imports from `@/` next, such as `@/lib/prisma`, `@/lib/validations`, and `@/hooks/useProjects`.
|
||||
3. Relative imports last, such as `./service` in `src/app/api/tags/maintenance/route.ts` and `./MarkdownContent` in `src/components/project/ProjectDetail.tsx`.
|
||||
|
||||
**Path Aliases:**
|
||||
- Use the `@/*` alias from `tsconfig.json` and `vitest.config.ts` for internal imports.
|
||||
- Prefer `@/` over deep relative paths across `src`, for example `@/lib/prisma` in `src/app/api/tags/route.ts` and `@/lib/tag-taxonomy` in `src/hooks/useProjects.ts`.
|
||||
- Use the `@/*` alias defined in `tsconfig.json` and mirrored in `vitest.config.ts`.
|
||||
- Prefer `@/` imports for anything under `src`, as seen throughout `src/app/api/tags/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/hooks/useProjects.ts`.
|
||||
|
||||
## Component and Module Design
|
||||
## API Validation and Auth
|
||||
|
||||
**React Components:**
|
||||
- Default-export only route-level pages and layouts, for example `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, and `src/app/layout.tsx`.
|
||||
- Use named exports for reusable components, for example `ProjectCard` in `src/components/project/ProjectCard.tsx`, `HomeRankings` in `src/components/home/HomeRankings.tsx`, and `AnnouncementBar` in `src/components/layout/AnnouncementBar.tsx`.
|
||||
- Mark interactive components with `'use client'`, as seen in `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/components/project/TagFilterPanel.tsx`, and `src/components/signals/SignalFeedClient.tsx`.
|
||||
- Keep server components async and free of client hooks, as seen in `src/app/[locale]/projects/[id]/page.tsx`, `src/app/[locale]/signals/page.tsx`, and `src/components/project/ProjectSidebar.tsx`.
|
||||
- Type component props explicitly with a local `interface ...Props`.
|
||||
**Validation:**
|
||||
- Put shared Zod schemas in `src/lib/validations.ts`. Current examples include `ProjectInputSchema`, `SignalWebhookPayloadSchema`, `SignalQuerySchema`, `TagMaintenanceRequestSchema`, and `ProjectTagResetRequestSchema`.
|
||||
- Define route-local schemas only when the contract is route-specific, such as `ProjectsQuerySchema` in `src/app/api/projects/route.ts` and `N8NSearchResponseSchema` plus `AISearchRequestSchema` in `src/app/api/search/ai/route.ts`.
|
||||
- Use `.safeParse()` when the route should return a custom `400` response without exceptions, as in `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Use `.parse()` when the route already catches `ZodError`, as in `src/app/api/projects/route.ts`, `src/app/api/signals/route.ts`, and `src/app/api/search/ai/route.ts`.
|
||||
- Use `z.coerce.number()` for query-string pagination and limits, as in `src/lib/validations.ts` and `src/app/api/projects/route.ts`.
|
||||
- Use `.superRefine()` for cross-record constraints such as duplicate tag IDs and self-merge prevention, as in `TagMergeSchema`, `TagMaintenanceRequestSchema`, and `ProjectTagResetRequestSchema` in `src/lib/validations.ts`.
|
||||
|
||||
**Service and Query Modules:**
|
||||
- Centralize Prisma-backed read logic in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`. Despite the directory name, these modules act as query services for routes and server components.
|
||||
- Keep route handlers thin where possible and delegate business rules to local services for mutations. The clearest example is `src/app/api/tags/maintenance/route.ts` delegating to `src/app/api/tags/maintenance/service.ts`.
|
||||
- Reuse shared utility modules in `src/lib` for cross-cutting concerns: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, `src/lib/slug.ts`, and `src/lib/tag-taxonomy.ts`.
|
||||
|
||||
**Exports:**
|
||||
- Prefer named exports across shared modules. No barrel files were detected under `src` on 2026-04-18.
|
||||
|
||||
## Validation
|
||||
|
||||
**Schema Placement:**
|
||||
- Put broadly shared Zod schemas in `src/lib/validations.ts`. Examples include `ProjectInputSchema`, `SignalWebhookPayloadSchema`, `SignalQuerySchema`, and `TagMaintenanceRequestSchema`.
|
||||
- Define route-local Zod schemas only when the contract is tightly coupled to a single endpoint, as in `ProjectsQuerySchema` in `src/app/api/projects/route.ts` and `N8NSearchResponseSchema` in `src/app/api/search/ai/route.ts`.
|
||||
|
||||
**Validation Flow:**
|
||||
- Use `.safeParse()` when the route needs to return a custom `400` payload without exceptions, as in `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Use `.parse()` when the route already has a `ZodError` catch branch, as in `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`, and `src/app/api/signals/route.ts`.
|
||||
- Use `z.coerce` for query-string number parsing in shared schemas, as in `SignalQuerySchema` and `ProjectQuerySchema` in `src/lib/validations.ts`.
|
||||
- Add cross-field validation with `.superRefine()` for multi-item or relation rules, as in `TagMergeSchema` and `TagMaintenanceRequestSchema` in `src/lib/validations.ts`.
|
||||
**Webhook Auth:**
|
||||
- Use the timing-safe `isValidApiKey(...)` helper from `src/lib/auth.ts` for internal mutation and webhook routes.
|
||||
- Read the expected secret from `process.env.WEBHOOK_API_KEY` unless a test passes an explicit override.
|
||||
- Validate the API key format at schema level first, then authenticate with `isValidApiKey(...)`, as done in `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Return `401` with a JSON body containing `success: false`, `error: "Unauthorized"`, and a `details` array on auth failure.
|
||||
|
||||
## Data Access
|
||||
|
||||
**Prisma:**
|
||||
- Use the singleton Prisma client from `src/lib/prisma.ts`.
|
||||
- Define Prisma payload types close to the query layer with `Prisma.*GetPayload`, as in `ProjectWithTagsAndLinks` and `TagWithProjectCount` in `src/hooks/useProjects.ts`.
|
||||
- Prefer explicit `include` and `select` clauses to control payload shape, as seen throughout `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/app/api/signals/route.ts`, and `src/app/api/tags/reset-projects/route.ts`.
|
||||
- Use `prisma.$transaction(...)` for write paths that change multiple tables, as in `src/app/api/tags/maintenance/route.ts` and `src/app/api/tags/reset-projects/route.ts`.
|
||||
- Use the shared Prisma client from `src/lib/prisma.ts`.
|
||||
- Keep read-heavy query composition in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`.
|
||||
- Keep write workflows transactional with `prisma.$transaction(...)`, as in `src/app/api/tags/maintenance/route.ts` and `src/app/api/tags/reset-projects/route.ts`.
|
||||
- Use explicit `include` and `select` clauses rather than broad model reads, as seen throughout `src/hooks/useProjects.ts` and `src/app/api/webhook/signals/route.ts`.
|
||||
|
||||
**Caching and Fallbacks:**
|
||||
- Wrap cacheable server reads with `unstable_cache`, using stable key arrays and `revalidate` windows, as in `src/app/api/tags/route.ts`, `src/hooks/useProjects.ts`, and `src/hooks/useHome.ts`.
|
||||
- Route cached reads through `runWithCacheFallback` from `src/lib/cache.ts` so execution can fall back when `unstable_cache` is unavailable.
|
||||
- Cache fetcher functions keyed by input when the function signature varies, as in `topTagsCache` and `fixedProjectTypeFilterCache` in `src/hooks/useProjects.ts`.
|
||||
|
||||
**Retry and Degrade Patterns:**
|
||||
- Use retry wrappers for transient DB issues on read paths. `withDbRetry` and `isTransientDbError` in `src/hooks/useProjects.ts` are the current pattern.
|
||||
- Degrade to safe defaults on non-critical homepage and filter data instead of failing the whole page, as in `safeQuery` in `src/hooks/useHome.ts` and `console.error` fallback branches in `src/hooks/useProjects.ts`.
|
||||
**Caching and Degrade Patterns:**
|
||||
- Use `unstable_cache` for repeatable server reads, as in `src/app/api/tags/route.ts` and `src/hooks/useProjects.ts`.
|
||||
- Route cached reads through `runWithCacheFallback` from `src/lib/cache.ts`.
|
||||
- Use retry helpers for transient DB reads, as in `withDbRetry(...)` and `isTransientDbError(...)` in `src/hooks/useProjects.ts`.
|
||||
- Degrade on schema drift where the route can still succeed, as in `src/app/api/webhook/signals/route.ts` falling back when hotness columns are unavailable.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Patterns:**
|
||||
- Wrap route handlers in `try/catch` and return JSON error payloads through `NextResponse.json`, as in `src/app/api/projects/route.ts`, `src/app/api/tags/route.ts`, `src/app/api/search/ai/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Return `400` for schema and cursor validation failures, `401` for invalid API keys, `409` for tag conflicts inside service code, and `500` for unexpected errors.
|
||||
- Use domain-specific error classes when mutation services need to communicate status and details back to routes. `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts` is the established pattern.
|
||||
- Include machine-readable `success`, `error`, `details`, and sometimes `message` fields in JSON responses. The exact shape varies by route and is not fully normalized.
|
||||
- Wrap route handlers in `try/catch` and return JSON through `NextResponse.json(...)`, as seen in `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Return `400` for schema and query validation failures, `401` for invalid webhook keys, and `500` for unexpected failures.
|
||||
- Use route-local or domain-specific error classes when mutation logic needs structured status and details, as in `TagMaintenanceApiError` from `src/app/api/tags/maintenance/service.ts`.
|
||||
- Keep JSON error payloads stable enough for automation. Current routes usually emit `success`, `error`, `details`, and sometimes `message`, but the exact shape is not yet fully standardized across `src/app/api/**/route.ts`.
|
||||
|
||||
**Logging:**
|
||||
- Use `console.error` for failures and `console.warn` for degraded or summary logging, for example `src/hooks/useHome.ts`, `src/hooks/useProjects.ts`, `src/app/api/signals/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Error-path tests currently allow log output to stderr, as verified by `pnpm test` on 2026-04-18 from `src/app/api/tags/route.test.ts`.
|
||||
- Use `console.error` for failures and `console.warn` for operational summaries or degraded behavior, matching `.eslintrc.json` and examples in `src/app/api/tags/route.ts`, `src/hooks/useProjects.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- Avoid `console.log` in production code because lint warns on it.
|
||||
|
||||
## Comments
|
||||
|
||||
**When to Comment:**
|
||||
- Comments are sparse and usually explain non-obvious intent, numbered route steps, or bilingual product context.
|
||||
- English and Chinese comments coexist. Examples include the numbered route comments in `src/app/api/tags/maintenance/route.ts`, Chinese comments in `src/hooks/useProjects.ts`, and mixed bilingual commentary in `src/components/project/ProjectCard.tsx`.
|
||||
- Prefer comments only where intent is not obvious from code.
|
||||
|
||||
**JSDoc/TSDoc:**
|
||||
- JSDoc is uncommon. The clearest example is the security-sensitive note on `isValidApiKey` in `src/lib/auth.ts`.
|
||||
- Keep comments sparse and intent-focused. Existing comments mostly explain numbered handler steps, fallback rationale, or bilingual product context, as in `src/app/api/tags/maintenance/route.ts`, `src/hooks/useProjects.ts`, and `src/app/api/search/ai/route.ts`.
|
||||
- Use short JSDoc only where security or contract semantics matter, such as the timing-safe explanation above `isValidApiKey(...)` in `src/lib/auth.ts`.
|
||||
|
||||
## Function Design
|
||||
|
||||
**Size:**
|
||||
- Read/query modules tolerate large files with many helpers. `src/hooks/useProjects.ts` is 602 lines, `src/hooks/useHome.ts` is 250 lines, and `src/app/api/signals/route.ts` is 355 lines.
|
||||
- Keep complex logic split into local helpers inside the same file before extracting a new module. Current examples include `parseSlugList` in `src/app/api/projects/route.ts`, `parseSections` in `src/app/api/signals/route.ts`, and `collectValidationErrorsForProjectItem` in `src/app/api/tags/reset-projects/route.ts`.
|
||||
- The repository tolerates large query and route modules. Current examples include `src/hooks/useProjects.ts`, `src/app/api/signals/route.ts`, and `src/app/api/tags/reset-projects/route.ts`.
|
||||
- Keep helper functions close to the route or query layer before extracting a new module. Examples include `parseSlugList(...)` in `src/app/api/projects/route.ts`, `normalizeSlugList(...)` in `src/app/api/search/ai/route.ts`, and `toSectionsJson(...)` in `src/app/api/webhook/signals/route.ts`.
|
||||
|
||||
**Parameters:**
|
||||
- Prefer a single typed options object for query functions, as in `getProjects` in `src/hooks/useProjects.ts`.
|
||||
- Use small helper functions for normalization of query and payload input, such as `normalizePositiveInteger` in `src/hooks/useProjects.ts` and `normalizeSlug` in `src/app/api/tags/reset-projects/route.ts`.
|
||||
- Prefer a single typed options object for non-trivial query helpers, as in `getProjects(...)` from `src/hooks/useProjects.ts`.
|
||||
- For route handlers, parse from `request.nextUrl.searchParams` or `await request.json()` once, then normalize into a validated object before passing deeper.
|
||||
|
||||
**Return Values:**
|
||||
- Return plain serializable objects from server query helpers when the result is meant for pages or APIs, as in `getProjects`, `getHomePageData`, and `getProjectsByIds`.
|
||||
- Flatten Prisma relation shapes before returning to callers when the UI expects direct lists, as in `getProjects`, `getProjectBySlug`, and `getProjectsByIds` in `src/hooks/useProjects.ts`.
|
||||
- Return plain serializable objects from API routes and query helpers.
|
||||
- Flatten Prisma relation shapes before returning UI data, as done by `getProjects(...)` and `getProjectsByIds(...)` in `src/hooks/useProjects.ts`.
|
||||
|
||||
## Environment and Configuration
|
||||
## Module Design
|
||||
|
||||
**Environment Files:**
|
||||
- `.env`, `.env.local`, and `.env.example` are present at repository root. Use `.env.example` as the naming reference; do not commit real secrets.
|
||||
**Exports:**
|
||||
- Prefer named exports across shared modules and components. No barrel files were detected under `src`.
|
||||
- Use default exports mainly for App Router pages and layouts under `src/app`.
|
||||
|
||||
**Observed Variables:**
|
||||
- `.env.example` defines `DATABASE_URL`, `WEBHOOK_API_KEY`, `N8N_AI_SEARCH_WEBHOOK`, `NEXT_INTL_DEFAULT_LOCALE`, and `NEXT_INTL_SUPPORTED_LOCALES`.
|
||||
- Source code reads `WEBHOOK_API_KEY` in `src/lib/auth.ts`, `N8N_AI_SEARCH_WEBHOOK` in `src/app/api/search/ai/route.ts`, `NEXT_PUBLIC_SITE_URL` in `src/app/robots.ts` and `src/app/sitemap.ts`, `VERCEL_ENV` in `src/app/layout.tsx`, and `NODE_ENV` in `src/lib/prisma.ts`.
|
||||
- `NEXT_INTL_DEFAULT_LOCALE` and `NEXT_INTL_SUPPORTED_LOCALES` appear in `.env.example` but were not detected in runtime code on 2026-04-18. Locale behavior is hard-coded in `src/middleware.ts` and `src/i18n/request.ts`.
|
||||
|
||||
**Conventions:**
|
||||
- Fail fast at module load only for truly required integration config. `src/app/api/search/ai/route.ts` throws immediately if `N8N_AI_SEARCH_WEBHOOK` is unset.
|
||||
- Use safe fallbacks for public metadata values, as in `src/app/robots.ts` and `src/app/sitemap.ts` defaulting `NEXT_PUBLIC_SITE_URL` to `https://agentpark.ai`.
|
||||
- Keep deployment and framework config in root files: `next.config.js`, `tailwind.config.ts`, `postcss.config.mjs`, `tsconfig.json`, `.eslintrc.json`, `.prettierrc.json`, and `vitest.config.ts`.
|
||||
**Separation of Concerns:**
|
||||
- Keep Prisma-backed reads in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`.
|
||||
- Keep cross-cutting utilities in `src/lib`, such as `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, and `src/lib/validations.ts`.
|
||||
- Keep mutation business logic in a local service file when the route would otherwise mix transport and domain rules. `src/app/api/tags/maintenance/route.ts` plus `src/app/api/tags/maintenance/service.ts` is the clearest existing pattern.
|
||||
|
||||
---
|
||||
|
||||
*Convention analysis: 2026-04-18*
|
||||
*Convention analysis: 2026-04-20*
|
||||
|
||||
@@ -1,125 +1,153 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-04-18
|
||||
**Analysis Date:** 2026-04-20
|
||||
|
||||
## APIs & External Services
|
||||
|
||||
**Workflow Automation / Search:**
|
||||
- n8n webhook - AI search requests are forwarded from `src/app/api/search/ai/route.ts` to the URL in `process.env.N8N_AI_SEARCH_WEBHOOK`.
|
||||
**In-Repo n8n Contracts:**
|
||||
- n8n AI search webhook - The repo-owned AI search proxy in `src/app/api/search/ai/route.ts` forwards validated search requests to the webhook URL in `N8N_AI_SEARCH_WEBHOOK`, then hydrates returned IDs from PostgreSQL via `src/hooks/useProjects.ts`.
|
||||
- SDK/Client: Native `fetch` in `src/app/api/search/ai/route.ts`
|
||||
- Auth: `N8N_AI_SEARCH_WEBHOOK`
|
||||
- Evidence: outbound `GET` request is constructed in `src/app/api/search/ai/route.ts`; `.env.example` provides the webhook variable name.
|
||||
- Repo-side n8n contract registry - n8n workflow metadata and request/response contracts are committed in `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/DATAFLOW.md`, and per-workflow docs under `docs/integrations/n8n/workflows/*.md`.
|
||||
- SDK/Client: `scripts/generate-n8n-context.mjs`
|
||||
- Auth: None in repo; this is documentation and generation logic
|
||||
- GSD-facing n8n mirrors - Generated mirrors for planning tools live in `.planning/codebase/N8N-CONTEXT.md` and `.planning/codebase/N8N-DATAFLOW.md`.
|
||||
- SDK/Client: Generated by `pnpm n8n:context`
|
||||
- Auth: None in repo
|
||||
|
||||
**Vercel Runtime Telemetry:**
|
||||
- Vercel Analytics - Client analytics are mounted in `src/app/VercelMetrics.tsx` and only rendered when `process.env.VERCEL_ENV === "production"` in `src/app/layout.tsx`.
|
||||
**External-Upstream Workflow Boundaries:**
|
||||
- Discovery Task Service - The n8n docs explicitly describe external endpoints `GET/POST/PATCH /api/discovery/tasks`, `POST /api/discovery/check-duplicates`, and completion/failure callbacks, but this repo does not implement those handlers.
|
||||
- SDK/Client: No in-repo client package; boundary is documented in `docs/integrations/n8n/DATAFLOW.md` and `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md`
|
||||
- Auth: External to this repo
|
||||
- Topic Discovery and GitHub Trending Discovery - Upstream n8n workflows documented in `docs/integrations/n8n/CONTEXT.generated.md` and `docs/integrations/n8n/workflows/01-topic-discovery.md` / `02-github-trending-discovery.md` depend on GitHub Search and GitHub Trending, then enqueue tasks into the external discovery service.
|
||||
- SDK/Client: n8n runtime outside this repo
|
||||
- Auth: External n8n credentials / runtime env, not committed here
|
||||
- Project Ingestion (Multi-source) - Upstream n8n workflow documented in `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md` consumes discovery tasks, enriches data with browser/AI steps, and writes project results back outside this repo’s route layer.
|
||||
- SDK/Client: n8n runtime outside this repo
|
||||
- Auth: External n8n credentials / discovery-service auth
|
||||
|
||||
**External Data Sources Used Through n8n:**
|
||||
- GitHub Search API - Upstream dependency for topic discovery documented in `docs/integrations/n8n/CONTEXT.generated.md`.
|
||||
- SDK/Client: n8n workflow, not repository code
|
||||
- Auth: External GitHub credentials in n8n
|
||||
- GitHub Trending - Upstream source for trending discovery documented in `docs/integrations/n8n/CONTEXT.generated.md`.
|
||||
- SDK/Client: n8n scraping workflow
|
||||
- Auth: None implied for the public page
|
||||
- GitHub Repository API - Upstream source for star refresh documented in `docs/integrations/n8n/workflows/04-github-star-refresh.md`.
|
||||
- SDK/Client: n8n workflow, not repository code
|
||||
- Auth: External GitHub credentials in n8n
|
||||
- Hacker News, Reddit, arXiv, Product Hunt, and Hugging Face - Upstream sources for signals aggregation documented in `docs/integrations/n8n/workflows/07-signals-aggregation.md`.
|
||||
- SDK/Client: n8n workflow, not repository code
|
||||
- Auth: External n8n credentials as needed per source
|
||||
- SiliconFlow embeddings API - Upstream embedding provider referenced by the vectorization and RAG search docs in `docs/integrations/n8n/DATAFLOW.md` and `docs/integrations/n8n/CONTEXT.generated.md`.
|
||||
- SDK/Client: n8n workflow, not repository code
|
||||
- Auth: External embedding-service credentials in n8n
|
||||
|
||||
**Platform / Asset Services:**
|
||||
- Vercel Analytics - Client analytics are mounted in `src/app/VercelMetrics.tsx` and gated by `process.env.VERCEL_ENV` in `src/app/layout.tsx`.
|
||||
- SDK/Client: `@vercel/analytics`
|
||||
- Auth: Managed by Vercel runtime; no repo-managed token detected
|
||||
- Vercel Speed Insights - Frontend performance sampling is mounted beside analytics in `src/app/VercelMetrics.tsx`.
|
||||
- Auth: Vercel-managed runtime integration
|
||||
- Vercel Speed Insights - Client performance telemetry is mounted beside analytics in `src/app/VercelMetrics.tsx`.
|
||||
- SDK/Client: `@vercel/speed-insights`
|
||||
- Auth: Managed by Vercel runtime; no repo-managed token detected
|
||||
|
||||
**Static Asset Providers:**
|
||||
- Google Fonts / Material Icons - CSS imports in `src/app/globals.css` load Inter, Space Mono, and Material Icons from `fonts.googleapis.com`.
|
||||
- Auth: Vercel-managed runtime integration
|
||||
- Google Fonts and Material Icons - CSS imports in `src/app/globals.css` pull `Inter`, `Space Mono`, and Material Icons from `fonts.googleapis.com`.
|
||||
- SDK/Client: CSS `@import`
|
||||
- Auth: None
|
||||
- Shields.io - GitHub badge images are generated in `src/lib/github/badges.ts` and rendered through `next/image` in `src/components/project/ProjectCard.tsx` and `src/components/project/GitHubTextStatsCard.tsx`.
|
||||
- Shields.io - GitHub badge URLs are generated in `src/lib/github/badges.ts` and rendered in `src/components/project/ProjectCard.tsx`, `src/components/project/GitHubTextStatsCard.tsx`, and `src/components/project/ProjectSidebar.tsx`.
|
||||
- SDK/Client: URL construction only
|
||||
- Auth: None
|
||||
- Evidence: `img.shields.io` is explicitly whitelisted in `next.config.js`.
|
||||
|
||||
**Content / Link Surfaces:**
|
||||
- GitHub - The app stores GitHub repository links on projects and builds GitHub badge and deep-link URLs in `src/lib/github/badges.ts`.
|
||||
- SDK/Client: None detected
|
||||
- Auth: None detected
|
||||
- Note: GitHub API calls are not detected in current code; integration is via stored URLs and Shields.io images.
|
||||
|
||||
## Data Storage
|
||||
|
||||
**Databases:**
|
||||
- PostgreSQL
|
||||
- Connection: `DATABASE_URL`
|
||||
- Client: Prisma via `@prisma/client` in `src/lib/prisma.ts`
|
||||
- Client: Prisma via `src/lib/prisma.ts` and `@prisma/client`
|
||||
- Schema: `prisma/schema.prisma`
|
||||
- Migrations: `prisma/migrations/*/migration.sql`
|
||||
- Usage: Queried from `src/hooks/useProjects.ts`, `src/app/api/projects/route.ts`, `src/app/api/tags/route.ts`, `src/app/api/signals/route.ts`, and webhook-style route handlers.
|
||||
- Connection hardening: `src/lib/prisma-url.ts` supports `PG_SSL_ROOT_CERT_B64`, `PG_SSL_IDENTITY_P12_B64`, `PG_SSL_IDENTITY_PASSWORD`, `PG_SSL_CERT_DIR`, and `PG_SSL_MODE`
|
||||
- Notes: `prisma/schema.prisma` stores a `vector` column on `Project.embedding`, and n8n docs state some workflows write directly to the database rather than calling repo routes
|
||||
|
||||
**File Storage:**
|
||||
- Local filesystem only
|
||||
- Evidence: No S3, Blob, Cloudinary, or similar storage SDK is declared in `package.json` or imported under `src/**/*`.
|
||||
- Evidence: No S3, Blob, Cloudinary, or object-storage SDK is declared in `package.json` or imported under `src/**/*`
|
||||
|
||||
**Caching:**
|
||||
- Next.js data cache via `unstable_cache`
|
||||
- Service: Built-in framework cache, not a separate external service
|
||||
- Implementation: `src/app/api/tags/route.ts` and `src/hooks/useProjects.ts`
|
||||
- Built-in Next.js data cache
|
||||
- Service: `unstable_cache` in `src/hooks/useHome.ts`, `src/hooks/useProjects.ts`, and `src/app/api/tags/route.ts`
|
||||
- Client: Framework cache, not an external provider
|
||||
- External cache service: None detected
|
||||
- Evidence: No Redis, Memcached, or similar client package is declared in `package.json`.
|
||||
|
||||
## Authentication & Identity
|
||||
|
||||
**Auth Provider:**
|
||||
- Custom shared-secret authentication for machine-to-machine routes
|
||||
- Implementation: `src/lib/auth.ts` compares a provided API key against `process.env.WEBHOOK_API_KEY` using `crypto.timingSafeEqual`.
|
||||
- Used by:
|
||||
- `src/app/api/webhook/signals/route.ts`
|
||||
- `src/app/api/tags/maintenance/route.ts`
|
||||
- `src/app/api/tags/reset-projects/route.ts`
|
||||
- Custom shared-secret auth for machine-to-machine routes
|
||||
- Implementation: `src/lib/auth.ts` uses `crypto.timingSafeEqual` against `WEBHOOK_API_KEY`
|
||||
- Used by: `src/app/api/webhook/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, and `src/app/api/tags/reset-projects/route.ts`
|
||||
- End-user authentication: Not detected
|
||||
- Evidence: No NextAuth, Clerk, Auth.js, Supabase Auth, OAuth, or session middleware is present in `package.json` or `src/**/*`.
|
||||
- Evidence: No session/auth provider package or auth middleware is committed in `package.json` or `src/**/*`
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
**Error Tracking:**
|
||||
- None detected
|
||||
- Evidence: No Sentry, Bugsnag, Datadog, or Rollbar package is declared in `package.json`.
|
||||
- Evidence: No Sentry, Datadog, Rollbar, Bugsnag, or OpenTelemetry package is declared in `package.json`
|
||||
|
||||
**Logs:**
|
||||
- Server logging uses `console.error` and `console.warn` in route handlers such as `src/app/api/search/ai/route.ts`, `src/app/api/webhook/signals/route.ts`, and `src/app/api/tags/route.ts`.
|
||||
- Frontend telemetry uses Vercel Analytics and Speed Insights in `src/app/VercelMetrics.tsx`.
|
||||
- Server logging uses `console.error` and `console.warn` in route handlers and server data code such as `src/app/api/search/ai/route.ts`, `src/app/api/webhook/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/hooks/useHome.ts`
|
||||
- Frontend telemetry uses Vercel Analytics and Speed Insights via `src/app/VercelMetrics.tsx`
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:**
|
||||
- Vercel
|
||||
- Evidence: `vercel.json` sets `"framework": "nextjs"`, `buildCommand`, `installCommand`, and region `hkg1`.
|
||||
- Evidence: `vercel.json` sets the `nextjs` framework, install/build commands, and region `hkg1`
|
||||
- Container deployment
|
||||
- Evidence: `Dockerfile`, `Dockerfile.runtime`, and `nixpacks.toml` define reproducible Node 22 builds and runtime startup
|
||||
|
||||
**CI Pipeline:**
|
||||
- None detected in repo
|
||||
- Evidence: No `.github/workflows/*`, GitLab CI file, CircleCI config, or other CI config file is present at repo root.
|
||||
- Not detected in repo
|
||||
- Evidence: No committed `.github/` workflow directory and no other CI config file are present at repo root
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**Required env vars:**
|
||||
- `DATABASE_URL` - Required by Prisma datasource in `prisma/schema.prisma`.
|
||||
- `WEBHOOK_API_KEY` - Required for authenticated webhook-style POST endpoints in `src/lib/auth.ts`.
|
||||
- `N8N_AI_SEARCH_WEBHOOK` - Required by the outbound AI search proxy in `src/app/api/search/ai/route.ts`.
|
||||
- `NEXT_PUBLIC_SITE_URL` - Optional but used to generate canonical URLs in `src/app/robots.ts` and `src/app/sitemap.ts`; code falls back to `https://agentpark.ai`.
|
||||
- `VERCEL_ENV` - Read in `src/app/layout.tsx` to gate Vercel telemetry; expected when deployed on Vercel.
|
||||
- `.env.example` also includes `NEXT_INTL_DEFAULT_LOCALE` and `NEXT_INTL_SUPPORTED_LOCALES`, but current locale middleware and request config rely on hard-coded values in `src/middleware.ts` and `src/i18n/request.ts`.
|
||||
- `DATABASE_URL` - Prisma datasource in `prisma/schema.prisma`
|
||||
- `WEBHOOK_API_KEY` - Shared-secret validation in `src/lib/auth.ts`
|
||||
- `N8N_AI_SEARCH_WEBHOOK` - Outbound AI search proxy target in `src/app/api/search/ai/route.ts`
|
||||
- `NEXT_PUBLIC_SITE_URL` - Canonical URL base in `src/app/robots.ts` and `src/app/sitemap.ts`
|
||||
- `VERCEL_ENV` - Telemetry gating in `src/app/layout.tsx`
|
||||
- `PG_SSL_ROOT_CERT_B64`, `PG_SSL_IDENTITY_P12_B64`, `PG_SSL_IDENTITY_PASSWORD`, `PG_SSL_CERT_DIR`, `PG_SSL_MODE` - Optional Postgres SSL/mTLS parameters in `src/lib/prisma-url.ts`
|
||||
|
||||
**Secrets location:**
|
||||
- Local development secrets: `.env.local` and `.env` files are present in repo root; contents were not read.
|
||||
- Production secrets: Vercel environment variables are implied by `vercel.json` and `process.env.*` usage, but no separate secret manager config is committed.
|
||||
- Local development secrets are expected in `.env.local` or `.env`; contents were not read
|
||||
- Production secrets are expected in deployment settings for Vercel or container hosting
|
||||
- n8n runtime credentials, upstream API tokens, and discovery-service secrets are external to this repo and are only described contractually in `docs/integrations/n8n/*.md`
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:**
|
||||
- `POST /api/webhook/signals` in `src/app/api/webhook/signals/route.ts`
|
||||
- Purpose: Ingests batched signal payloads into PostgreSQL via Prisma.
|
||||
- Auth: Shared secret in request body validated against `WEBHOOK_API_KEY`.
|
||||
- Purpose: Accept batched signal payloads from the signals aggregation workflow and upsert them into PostgreSQL
|
||||
- Auth: `WEBHOOK_API_KEY` in request body
|
||||
- `POST /api/tags/maintenance` in `src/app/api/tags/maintenance/route.ts`
|
||||
- Purpose: Applies tag updates/merges and revalidates project pages.
|
||||
- Auth: Shared secret in request body validated against `WEBHOOK_API_KEY`.
|
||||
- Purpose: Apply tag updates/merges and revalidate list pages
|
||||
- Auth: `WEBHOOK_API_KEY` in request body
|
||||
- `POST /api/tags/reset-projects` in `src/app/api/tags/reset-projects/route.ts`
|
||||
- Purpose: Resets project tag assignments by category and optionally revalidates pages.
|
||||
- Auth: Shared secret in request body validated against `WEBHOOK_API_KEY`.
|
||||
- Purpose: Bulk-reset project tags by taxonomy category and revalidate list/detail pages
|
||||
- Auth: `WEBHOOK_API_KEY` in request body
|
||||
|
||||
**Outgoing:**
|
||||
- n8n AI search webhook
|
||||
- Source: `src/app/api/search/ai/route.ts`
|
||||
- Method: `GET`
|
||||
- Target: URL from `N8N_AI_SEARCH_WEBHOOK`
|
||||
- Search engine / badge asset requests from the browser are not hard-coded beyond standard page navigation, Google Fonts CSS, Material Icons CSS, and Shields.io image URLs.
|
||||
- Browser-side external navigations and assets
|
||||
- Sources: `src/app/globals.css`, `src/lib/github/badges.ts`, `src/components/project/GitHubTextStatsCard.tsx`
|
||||
- Targets: `fonts.googleapis.com`, `img.shields.io`, and `github.com`
|
||||
- External-upstream discovery service calls
|
||||
- Source of truth: Documented in `docs/integrations/n8n/DATAFLOW.md` and `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md`
|
||||
- Status: Not implemented by this repo; treat as upstream integration boundary
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-04-18*
|
||||
*Integration audit: 2026-04-20*
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
# N8N Context
|
||||
|
||||
Generated at: 2026-04-20T08:55:12.297Z
|
||||
|
||||
This file is generated from `docs/integrations/n8n/registry.json` plus repository scanning.
|
||||
It exists so external n8n workflows become committed, reviewable context for AI agents and GSD.
|
||||
|
||||
## Workflow Inventory
|
||||
|
||||
## AI Search
|
||||
|
||||
- Status: `confirmed`
|
||||
- ID: `ai-search`
|
||||
- Purpose: Resolve semantic search candidates from n8n and hydrate them into project results.
|
||||
- n8n workflow id: `F5cQ06DykBfpeyfqL-pd7`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- webhook: GET ai-search
|
||||
|
||||
### Repository Touchpoints
|
||||
- src/app/api/search/ai/route.ts
|
||||
|
||||
### Environment
|
||||
- N8N_AI_SEARCH_WEBHOOK
|
||||
|
||||
### Contracts
|
||||
- Request fields: `desc`, `limit`, `page`, `offset`, `tags`, `domains`, `productForms`
|
||||
- Response fields: `results[].id`, `results[].similarity`, `pagination.total`, `pagination.totalPages`, `pagination.hasMore`
|
||||
|
||||
### Schemas
|
||||
- N8NSearchResponseSchema
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: RAG项目搜索, pgvector similarity search, SiliconFlow embeddings
|
||||
- Downstreams: src/hooks/useProjects.ts#getProjectsByIds, POST /api/search/ai response
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: webhook path is `ai-search` and the workflow returns `results[].id` plus `similarity`.
|
||||
|
||||
## Signals Aggregation
|
||||
|
||||
- Status: `confirmed`
|
||||
- ID: `signals-aggregation`
|
||||
- Purpose: Aggregate multi-source discussion signals, filter them with AI, and ingest them into the repository signal store.
|
||||
- n8n workflow id: `bAxNZKGq2ApUUiw9`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- schedule: multi-source discussion crawl
|
||||
|
||||
### Repository Touchpoints
|
||||
- src/lib/auth.ts
|
||||
- src/app/api/webhook/signals/route.ts
|
||||
- src/app/api/signals/route.ts
|
||||
- src/lib/validations.ts
|
||||
- prisma/schema.prisma
|
||||
|
||||
### Environment
|
||||
- WEBHOOK_API_KEY
|
||||
|
||||
### Contracts
|
||||
- Request fields: `apiKey`, `signals[].source`, `signals[].sourceUrl`, `signals[].title`, `signals[].titleEn`, `signals[].summary`, `signals[].summaryEn`, `signals[].topic`, `signals[].topicEn`, `signals[].tags`, `signals[].sections`, `signals[].engagement`, `signals[].hotScore`, `signals[].isHot`, `signals[].publishedAt`, `signals[].isActive`
|
||||
- Response fields: `success`, `processed`, `created`, `updated`, `failed`, `errors[].index`, `errors[].field`, `errors[].message`
|
||||
|
||||
### Schemas
|
||||
- SignalWebhookPayloadSchema
|
||||
- SignalIngestionInputSchema
|
||||
- SignalQuerySchema
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: Hacker News, GitHub, arXiv, Reddit, Product Hunt, Hugging Face
|
||||
- Downstreams: GET /api/signals, signals page feed, signal hotness computation
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: workflow posts to `/api/webhook/signals` and also triggers external discovery dedupe/task creation. Workflow currently embeds a shared secret in HTTP body and should move to credentials/env.
|
||||
|
||||
## Project Tag Reset
|
||||
|
||||
- Status: `confirmed`
|
||||
- ID: `tag-reset`
|
||||
- Purpose: Reset selected project tags in bulk from n8n classification results.
|
||||
- n8n workflow id: `8tIgBqLyWrBewJPs`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- manual: bulk tag reset
|
||||
|
||||
### Repository Touchpoints
|
||||
- src/lib/auth.ts
|
||||
- src/app/api/tags/reset-projects/route.ts
|
||||
- src/lib/validations.ts
|
||||
- prisma/schema.prisma
|
||||
|
||||
### Environment
|
||||
- WEBHOOK_API_KEY
|
||||
|
||||
### Contracts
|
||||
- Request fields: `apiKey`, `dryRun`, `replaceAllCategories`, `categories`, `projects[].projectSlug`, `projects[].selectedTagSlugsByCategory`
|
||||
- Response fields: `success`, `result.dryRun`, `result.categories`, `result.updatedCount`, `result.failedCount`, `result.results[].projectSlug`, `result.results[].status`, `result.results[].details`
|
||||
|
||||
### Schemas
|
||||
- ProjectTagResetRequestSchema
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: n8n tag classification
|
||||
- Downstreams: project tag relations, project detail page revalidation, project list revalidation
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: workflow reads `/api/tags` and `/api/projects`, then posts bulk updates into `/api/tags/reset-projects`. Workflow currently embeds a shared secret in HTTP body and should move to credentials/env.
|
||||
|
||||
## Project Ingestion (Multi-source)
|
||||
|
||||
- Status: `external-upstream`
|
||||
- ID: `project-ingestion-multi-source`
|
||||
- Purpose: Consume queued discovery tasks, enrich project metadata with AI/browser steps, and write final ingestion results back to Agent Park.
|
||||
- n8n workflow id: `1Ig1CyVMsGJFaHOe`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- schedule: every 10 minutes
|
||||
|
||||
### Repository Touchpoints
|
||||
- prisma/schema.prisma
|
||||
- src/app/api/projects/route.ts
|
||||
- src/app/api/projects/[slug]/route.ts
|
||||
|
||||
### Environment
|
||||
- none
|
||||
|
||||
### Contracts
|
||||
- Request fields: `task.status`, `task.sourceUrl`, `task.sourceType`
|
||||
- Response fields: `project content`, `tag assignments`, `task completion status`
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: discovery task queue, browser/AI extraction, tag catalog
|
||||
- Downstreams: project records visible in repository APIs, task completion callbacks, task failure callbacks
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: workflow polls `/api/discovery/tasks`, marks tasks `IN_PROGRESS`, enriches candidates, then completes or fails tasks. The current repo does not contain `/api/discovery/*` handlers, so this is an upstream system dependency rather than a route implemented here.
|
||||
|
||||
## GitHub Star Refresh
|
||||
|
||||
- Status: `confirmed`
|
||||
- ID: `github-star-refresh`
|
||||
- Purpose: Refresh `projects.githubStars` and `projects.githubStarsUpdatedAt` directly from GitHub repository metadata.
|
||||
- n8n workflow id: `ewx9Gs6cjrTXvwD0`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- schedule: daily at 04:00
|
||||
|
||||
### Repository Touchpoints
|
||||
- prisma/schema.prisma
|
||||
- src/app/api/search/ai/route.ts
|
||||
- src/hooks/useProjects.ts
|
||||
|
||||
### Environment
|
||||
- none
|
||||
|
||||
### Contracts
|
||||
- Request fields: `projects.id`, `projects.slug`, `external_links.url(type=GITHUB)`
|
||||
- Response fields: `projects.githubStars`, `projects.githubStarsUpdatedAt`
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: GitHub repository API, projects table, external_links table
|
||||
- Downstreams: project ranking, star sorting, home ranking display
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: workflow reads active project GitHub links from Postgres, fetches repository metadata from GitHub, then writes star counts directly back to Postgres. This bypasses repository API routes.
|
||||
|
||||
## Project Description Vectorization
|
||||
|
||||
- Status: `confirmed`
|
||||
- ID: `project-description-vectorization`
|
||||
- Purpose: Generate and persist project embeddings used by semantic search.
|
||||
- n8n workflow id: `1AvejnM5n-WPApU1vFt9C`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- schedule: every 30 minutes
|
||||
|
||||
### Repository Touchpoints
|
||||
- prisma/schema.prisma
|
||||
- prisma/migrations/20260126000000_add_project_embedding/migration.sql
|
||||
- src/app/api/search/ai/route.ts
|
||||
|
||||
### Environment
|
||||
- none
|
||||
|
||||
### Contracts
|
||||
- Request fields: `projects.id`, `projects.name`, `projects.nameEn`, `projects.description`, `projects.descriptionEn`, `projects.content`, `projects.contentEn`
|
||||
- Response fields: `projects.embedding`, `projects.embeddingUpdatedAt`
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: SiliconFlow embeddings API, projects table
|
||||
- Downstreams: RAG项目搜索, semantic search quality
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: workflow selects active projects with null embeddings, generates `BAAI/bge-m3` vectors, and writes them directly into the `vector` column. This is a direct DB maintenance job, not a repository API route.
|
||||
|
||||
## GitHub Trending Discovery
|
||||
|
||||
- Status: `external-upstream`
|
||||
- ID: `github-trending-discovery`
|
||||
- Purpose: Scrape GitHub Trending, dedupe candidates, filter them with AI, and enqueue project discovery tasks.
|
||||
- n8n workflow id: `hughGsWismCpk7jd`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- schedule: daily at 01:00
|
||||
|
||||
### Repository Touchpoints
|
||||
- src/app/api/projects/route.ts
|
||||
- src/app/api/projects/[slug]/route.ts
|
||||
|
||||
### Environment
|
||||
- none
|
||||
|
||||
### Contracts
|
||||
- Request fields: `GitHub trending repository URL`, `apiKey`, `tasks[].sourceUrl`, `tasks[].sourceType`
|
||||
- Response fields: `dedupe shouldCreate`, `task creation result`
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: https://github.com/trending, AI keep/discard filter
|
||||
- Downstreams: /api/discovery/check-duplicates, /api/discovery/tasks, project ingestion queue
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: workflow scrapes GitHub Trending, filters candidates with an LLM, then posts queued tasks into discovery webhook endpoints. The current repo does not implement `/api/discovery/*`, so treat this as upstream data intake.
|
||||
|
||||
## Topic Discovery
|
||||
|
||||
- Status: `external-upstream`
|
||||
- ID: `topic-discovery`
|
||||
- Purpose: Search GitHub topics and keywords for agent/LLM engineering repos, dedupe them, and enqueue discovery tasks.
|
||||
- n8n workflow id: `iw9vx9ih5Lt0Mobk`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- schedule: daily at 01:00
|
||||
|
||||
### Repository Touchpoints
|
||||
- src/app/api/projects/route.ts
|
||||
- src/app/api/projects/[slug]/route.ts
|
||||
|
||||
### Environment
|
||||
- none
|
||||
|
||||
### Contracts
|
||||
- Request fields: `GitHub search query`, `apiKey`, `tasks[].sourceUrl`, `tasks[].sourceType`
|
||||
- Response fields: `dedupe shouldCreate`, `task creation result`, `low recall alert`
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: GitHub Search API, AI keep/discard filter, topic watchlist
|
||||
- Downstreams: /api/discovery/check-duplicates, /api/discovery/tasks, project ingestion queue
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: workflow searches agent, infra, observability, evaluation, and MCP-related repositories, then posts accepted candidates into discovery webhooks. The current repo does not implement `/api/discovery/*`, so this is upstream context rather than in-repo routing.
|
||||
|
||||
## AI Chat Gateway
|
||||
|
||||
- Status: `adjacent`
|
||||
- ID: `ai-chat-gateway`
|
||||
- Purpose: Expose a chat-oriented webhook wrapper around `RAG项目搜索` and package search hits into chat blocks/citations.
|
||||
- n8n workflow id: `Rncc22jmHEaYOG58`
|
||||
- Export file: `not recorded`
|
||||
|
||||
### Entrypoints
|
||||
- webhook: POST agent-park-chat
|
||||
|
||||
### Repository Touchpoints
|
||||
- none
|
||||
|
||||
### Environment
|
||||
- none
|
||||
|
||||
### Contracts
|
||||
- Request fields: `requestId`, `sessionId`, `clientId`, `locale`, `mode`, `message`
|
||||
- Response fields: `message.blocks`, `message.citations`, `message.meta.source`, `progress.stage`
|
||||
|
||||
### Related Systems
|
||||
- Upstreams: RAG项目搜索, n8n webhook `ai-search`
|
||||
- Downstreams: external chat clients, project detail URLs
|
||||
|
||||
### Ownership
|
||||
- none
|
||||
|
||||
### Notes
|
||||
- Confirmed against live n8n MCP: this workflow is related to Agent Park but does not call a repository route directly. It wraps the RAG webhook and formats citations pointing at project pages.
|
||||
|
||||
## Exported Workflow Files
|
||||
|
||||
- none
|
||||
|
||||
## Detected Repository Touchpoints
|
||||
|
||||
- `src/app/api/search/ai/route.ts` (39 matches)
|
||||
- L6: `const N8N_WEBHOOK_URL = process.env.N8N_AI_SEARCH_WEBHOOK!`
|
||||
- L8: `if (!N8N_WEBHOOK_URL) {`
|
||||
- L9: `throw new Error('N8N_AI_SEARCH_WEBHOOK environment variable is not set')`
|
||||
- L12: `// n8n 返回的搜索结果 Schema(统一格式)`
|
||||
- L13: `const N8NSearchResponseSchema = z.object({`
|
||||
|
||||
- `src/app/api/tags/maintenance/route.test.ts` (1 matches)
|
||||
- L65: `process.env.WEBHOOK_API_KEY = validApiKey;`
|
||||
|
||||
- `src/app/api/tags/reset-projects/route.test.ts` (1 matches)
|
||||
- L78: `process.env.WEBHOOK_API_KEY = validApiKey;`
|
||||
|
||||
- `src/app/api/webhook/signals/route.ts` (7 matches)
|
||||
- L13: `SignalWebhookPayloadSchema,`
|
||||
- L15: `type SignalWebhookPayload,`
|
||||
- L39: `const validationResult = SignalWebhookPayloadSchema.safeParse(body)`
|
||||
- L51: `const payload = validationResult.data as SignalWebhookPayload`
|
||||
- L184: `console.error(\`[Webhook Signals] Error at index ${i}:\`, error)`
|
||||
|
||||
- `src/hooks/useProjects.ts` (2 matches)
|
||||
- L563: `// n8n 返回的简化搜索结果类型`
|
||||
- L564: `export type N8NSearchResult = {`
|
||||
|
||||
- `src/lib/auth.ts` (1 matches)
|
||||
- L9: `expectedApiKey: string | undefined = process.env.WEBHOOK_API_KEY`
|
||||
|
||||
- `src/lib/validations.ts` (5 matches)
|
||||
- L60: `export const WebhookAuthSchema = z.object({`
|
||||
- L106: `export const SignalWebhookPayloadSchema = WebhookAuthSchema.extend({`
|
||||
- L147: `export type SignalWebhookPayload = z.infer<typeof SignalWebhookPayloadSchema>;`
|
||||
|
||||
- `src/messages/en.json` (2 matches)
|
||||
- L220: `"contractTitle": "n8n Field Contract",`
|
||||
- L221: `"contractDescription": "Every visual element maps to fields that can be produced from n8n workflow outputs.",`
|
||||
|
||||
- `src/messages/zh.json` (2 matches)
|
||||
- L220: `"contractTitle": "n8n 字段契约",`
|
||||
- L221: `"contractDescription": "页面元素都对应可由 n8n 输出的字段,避免出现无法供数的设计组件。",`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- N8N_AI_SEARCH_WEBHOOK
|
||||
- WEBHOOK_API_KEY
|
||||
|
||||
## Gaps To Fill
|
||||
|
||||
- All detected repo touchpoints are mapped to documented workflows.
|
||||
|
||||
## Maintenance Rules
|
||||
|
||||
- When an n8n workflow changes, update `docs/integrations/n8n/registry.json` in the same PR.
|
||||
- If possible, export the workflow JSON into `docs/integrations/n8n/exports/` and reference it from the registry.
|
||||
- Re-run `pnpm n8n:context` after every workflow, contract, or route change.
|
||||
- Treat this file as generated output; edit the registry instead of editing this file directly.
|
||||
@@ -0,0 +1,220 @@
|
||||
# AgentPark n8n Dataflow
|
||||
|
||||
这份文档回答两个问题:
|
||||
|
||||
1. 这 8 条生产流程分别负责什么。
|
||||
2. 数据怎样从外部源头进入 AgentPark,再进入数据库、API 和页面展示。
|
||||
|
||||
## 范围
|
||||
|
||||
当前纳入范围的 8 条生产流程:
|
||||
|
||||
1. `Topic项目计划新增`
|
||||
2. `每日Github Trending项目计划新增`
|
||||
3. `项目分析入库(多源)`
|
||||
4. `GitHub Star 每日刷新`
|
||||
5. `项目描述向量化`
|
||||
6. `RAG项目搜索`
|
||||
7. `前沿信号聚合(多源+AI Agent过滤)`
|
||||
8. `项目标签重置`
|
||||
|
||||
不在这 8 条内,但已登记在仓库上下文中的旁路流程:
|
||||
|
||||
- `AI对话网关(Agent Park)`
|
||||
|
||||
## 系统边界
|
||||
|
||||
当前体系不是“单仓库闭环”,而是三层:
|
||||
|
||||
- 外部数据源层:GitHub Search API、GitHub Trending、Hacker News、Reddit、arXiv、Product Hunt、Hugging Face
|
||||
- n8n 编排层:抓取、去重、AI 过滤、标签重置、向量化、信号结构化
|
||||
- AgentPark 应用层:Postgres/Prisma、Next.js API、页面组件
|
||||
|
||||
还有一个明确存在但当前仓库里没有实现代码的外部服务边界:
|
||||
|
||||
- `Discovery Task Service`
|
||||
- `GET/POST/PATCH /api/discovery/tasks`
|
||||
- `POST /api/discovery/check-duplicates`
|
||||
|
||||
## 核心实体
|
||||
|
||||
- `projects`
|
||||
- `external_links`
|
||||
- `tags`
|
||||
- `project_tags`
|
||||
- `signals`
|
||||
- `projects.embedding`
|
||||
|
||||
## 总流图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Sources["External Sources"]
|
||||
GHSearch["GitHub Search API"]
|
||||
GHTrend["GitHub Trending"]
|
||||
GHRepo["GitHub Repository API"]
|
||||
HN["Hacker News"]
|
||||
Reddit["Reddit"]
|
||||
Arxiv["arXiv"]
|
||||
PH["Product Hunt"]
|
||||
HF["Hugging Face"]
|
||||
Silicon["SiliconFlow Embeddings"]
|
||||
end
|
||||
|
||||
subgraph N8N["n8n Workflows"]
|
||||
W1["1 Topic项目计划新增"]
|
||||
W2["2 每日Github Trending项目计划新增"]
|
||||
W3["3 项目分析入库(多源)"]
|
||||
W4["4 GitHub Star 每日刷新"]
|
||||
W5["5 项目描述向量化"]
|
||||
W6["6 RAG项目搜索"]
|
||||
W7["7 前沿信号聚合"]
|
||||
W8["8 项目标签重置"]
|
||||
end
|
||||
|
||||
subgraph Discovery["External Discovery Service"]
|
||||
Dedupe["/api/discovery/check-duplicates"]
|
||||
Tasks["/api/discovery/tasks"]
|
||||
Complete["/api/discovery/tasks/:id/complete"]
|
||||
end
|
||||
|
||||
subgraph App["AgentPark App + DB"]
|
||||
DBProjects["projects"]
|
||||
DBLinks["external_links"]
|
||||
DBTags["tags / project_tags"]
|
||||
DBSignals["signals"]
|
||||
APIProjects["/api/projects"]
|
||||
APISearch["/api/search/ai"]
|
||||
APISignals["/api/signals"]
|
||||
APITagReset["/api/tags/reset-projects"]
|
||||
UIProjects["Projects pages"]
|
||||
UISignals["Signals page"]
|
||||
UIHome["Home rankings"]
|
||||
end
|
||||
|
||||
GHSearch --> W1
|
||||
GHTrend --> W2
|
||||
W1 --> Dedupe
|
||||
W2 --> Dedupe
|
||||
Dedupe --> Tasks
|
||||
Tasks --> W3
|
||||
W3 --> Complete
|
||||
Complete --> DBProjects
|
||||
Complete --> DBLinks
|
||||
Complete --> DBTags
|
||||
|
||||
DBProjects --> W5
|
||||
Silicon --> W5
|
||||
W5 --> DBProjects
|
||||
|
||||
DBProjects --> W6
|
||||
Silicon --> W6
|
||||
W6 --> APISearch
|
||||
APISearch --> UIProjects
|
||||
|
||||
DBProjects --> W4
|
||||
DBLinks --> W4
|
||||
GHRepo --> W4
|
||||
W4 --> DBProjects
|
||||
DBProjects --> UIHome
|
||||
DBProjects --> UIProjects
|
||||
|
||||
HN --> W7
|
||||
Reddit --> W7
|
||||
Arxiv --> W7
|
||||
PH --> W7
|
||||
HF --> W7
|
||||
W7 --> DBSignals
|
||||
W7 --> Dedupe
|
||||
DBSignals --> APISignals
|
||||
APISignals --> UISignals
|
||||
|
||||
DBTags --> W8
|
||||
DBProjects --> W8
|
||||
W8 --> APITagReset
|
||||
APITagReset --> DBTags
|
||||
APITagReset --> UIProjects
|
||||
```
|
||||
|
||||
## 主链路拆解
|
||||
|
||||
### 1. 项目发现链路
|
||||
|
||||
- `Topic项目计划新增`
|
||||
- `每日Github Trending项目计划新增`
|
||||
|
||||
职责:
|
||||
|
||||
- 从 GitHub 搜索结果和 Trending 列表中找候选项目
|
||||
- 先走 discovery 去重
|
||||
- 再用 LLM 做保留/丢弃判断
|
||||
- 最后把可入库项目写进 discovery 任务队列
|
||||
|
||||
### 2. 项目入库链路
|
||||
|
||||
- `项目分析入库(多源)`
|
||||
|
||||
职责:
|
||||
|
||||
- 轮询 discovery 任务队列
|
||||
- 任务置为 `IN_PROGRESS`
|
||||
- 浏览器/AI 收集事实
|
||||
- 生成标准化项目内容、外链、标签候选
|
||||
- 调用 completion 接口完成入库
|
||||
- 失败时置为 `FAILED`
|
||||
|
||||
### 3. 项目检索链路
|
||||
|
||||
- `项目描述向量化`
|
||||
- `RAG项目搜索`
|
||||
|
||||
职责:
|
||||
|
||||
- 生成并更新 `projects.embedding`
|
||||
- 通过 webhook 对用户查询生成向量
|
||||
- 在 Postgres 中进行向量相似度搜索
|
||||
- 返回项目 ID 与相似度
|
||||
|
||||
### 4. Signals 展示链路
|
||||
|
||||
- `前沿信号聚合(多源+AI Agent过滤)`
|
||||
|
||||
职责:
|
||||
|
||||
- 从 6 类外部源抓取内容
|
||||
- 规则过滤、去重、AI 结构化
|
||||
- 写入 `signals`
|
||||
- 从保留信号中提取 GitHub 仓库,回流 discovery
|
||||
|
||||
### 5. 标签治理链路
|
||||
|
||||
- `项目标签重置`
|
||||
|
||||
职责:
|
||||
|
||||
- 拉取标签池和项目列表
|
||||
- 按 5 类标签做归类
|
||||
- 调用标签重置接口更新项目标签
|
||||
|
||||
### 6. Star 刷新链路
|
||||
|
||||
- `GitHub Star 每日刷新`
|
||||
|
||||
职责:
|
||||
|
||||
- 读取项目 GitHub 链接
|
||||
- 调 GitHub API 获取仓库信息
|
||||
- 更新 `githubStars` 与 `githubStarsUpdatedAt`
|
||||
|
||||
## 当前“就绪状态”定义
|
||||
|
||||
现在仓库已经具备:
|
||||
|
||||
- 8 条生产流程的仓库内登记
|
||||
- 代码触点与 workflow 的映射关系
|
||||
- 从“源头 -> n8n -> DB/API -> 页面”的主链路图
|
||||
|
||||
仍然不在当前仓库闭环的内容:
|
||||
|
||||
- discovery task service
|
||||
- n8n credentials / secrets / runtime env
|
||||
+56
-52
@@ -1,97 +1,101 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-04-18
|
||||
**Analysis Date:** 2026-04-20
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- TypeScript 5.x - Application code, API routes, hooks, Prisma access, and most tooling live in `src/**/*.ts`, `src/**/*.tsx`, `prisma/seed.ts`, `tailwind.config.ts`, and `vitest.config.ts`; the compiler is declared in `package.json`.
|
||||
- TypeScript 5.x - Main application, API, Prisma helpers, hooks, and tooling live in `src/**/*.ts`, `src/**/*.tsx`, `prisma/seed.ts`, `tailwind.config.ts`, and `vitest.config.ts`; the compiler is declared in `package.json`.
|
||||
|
||||
**Secondary:**
|
||||
- JavaScript - Next.js and PostCSS config live in `next.config.js` and `postcss.config.mjs`.
|
||||
- JavaScript - Runtime and framework config live in `next.config.js`, `postcss.config.mjs`, and `scripts/generate-n8n-context.mjs`.
|
||||
- CSS - Global styling and external font/icon imports live in `src/app/globals.css`.
|
||||
- JSON - Locale message catalogs live in `src/messages/en.json` and `src/messages/zh.json`; repo config also uses `.eslintrc.json` and `.prettierrc.json`.
|
||||
- Prisma schema DSL - Database schema and datasource definitions live in `prisma/schema.prisma`.
|
||||
- JSON - Repo and content config live in `package.json`, `tsconfig.json`, `.eslintrc.json`, `.prettierrc.json`, `vercel.json`, `docs/integrations/n8n/registry.json`, and `src/messages/*.json`.
|
||||
- Prisma schema DSL - Database schema, datasource, enums, and vector column definitions live in `prisma/schema.prisma`.
|
||||
- SQL migrations - Prisma migration output lives in `prisma/migrations/*/migration.sql`.
|
||||
- Markdown - Integration contracts and workflow docs live in `docs/integrations/n8n/*.md`, `docs/integrations/n8n/workflows/*.md`, and mirrored GSD docs in `.planning/codebase/N8N-CONTEXT.md` and `.planning/codebase/N8N-DATAFLOW.md`.
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- Node.js - Repo runtime is Node-based because scripts use `next`, `vitest`, `prisma`, and `ts-node` from `package.json`.
|
||||
- Version pinning: Not detected in repo. No `.nvmrc`, `.node-version`, or `.tool-versions` file is present.
|
||||
- Local tool version observed in this workspace: Node.js `v22.21.1`.
|
||||
- Node.js 22.x - Containerized runtime is pinned by `Dockerfile` and `Dockerfile.runtime`, both based on `node:22-bookworm`.
|
||||
- Server runtime is Next.js App Router with standalone output enabled in `next.config.js` and consumed by `Dockerfile.runtime`.
|
||||
- Local runtime version pin file is not committed. No `.nvmrc`, `.node-version`, or `.tool-versions` file is present at repo root.
|
||||
|
||||
**Package Manager:**
|
||||
- `pnpm` - Project commands, lockfile, and Vercel install/build configuration use `pnpm` in `package.json`, `pnpm-lock.yaml`, and `vercel.json`.
|
||||
- Local tool version observed in this workspace: `pnpm 10.27.0`.
|
||||
- `pnpm` - All install/build/test scripts in `package.json`, `vercel.json`, `Dockerfile`, and `nixpacks.toml` use `pnpm`.
|
||||
- Lockfile: present in `pnpm-lock.yaml`.
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- Next.js `15.1.11` - App Router web framework for pages, layouts, metadata, and route handlers in `src/app/**/*`; version declared in `package.json`.
|
||||
- React `19.0.0` and `react-dom` `19.0.0` - UI runtime for components in `src/components/**/*` and route segments in `src/app/**/*`; versions declared in `package.json`.
|
||||
- `next-intl` `4.0.2` - Locale routing and message loading via `next.config.js`, `src/middleware.ts`, `src/i18n/request.ts`, and `src/app/[locale]/layout.tsx`.
|
||||
- Prisma `6.1.0` / `@prisma/client` `6.1.0` - ORM and generated client used in `src/lib/prisma.ts`, `src/hooks/useProjects.ts`, `src/app/api/**/*`, `prisma/schema.prisma`, and `prisma/seed.ts`.
|
||||
- Zod `3.24.1` - Request and payload validation in `src/lib/validations.ts` and multiple route handlers under `src/app/api/**/*`.
|
||||
- Next.js `15.1.11` - App Router pages, layouts, metadata routes, and route handlers live in `src/app/**/*`; framework config is in `next.config.js`.
|
||||
- React `19.0.0` and `react-dom` `19.0.0` - UI runtime for components in `src/components/**/*` and route segments in `src/app/**/*`.
|
||||
- `next-intl` `^4.0.2` - Locale middleware and message loading are wired through `next.config.js`, `src/middleware.ts`, `src/i18n/request.ts`, and localized routes under `src/app/[locale]`.
|
||||
- Prisma `^6.1.0` / `@prisma/client` `^6.1.0` - ORM and generated client used by `src/lib/prisma.ts`, `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, and route handlers under `src/app/api/**/*`.
|
||||
- Zod `^3.24.1` - Runtime validation is centralized in `src/lib/validations.ts` and consumed by API routes such as `src/app/api/search/ai/route.ts` and `src/app/api/webhook/signals/route.ts`.
|
||||
|
||||
**Testing:**
|
||||
- Vitest `2.1.8` - Unit-style test runner configured in `vitest.config.ts` and used by files such as `src/lib/auth.test.ts` and `src/app/api/tags/route.test.ts`.
|
||||
- Vitest `^2.1.8` - Unit-style tests are configured in `vitest.config.ts` and run against `src/**/*.test.ts`.
|
||||
- Playwright: Not detected in committed dependencies or config. No `playwright.config.*` file and no committed `e2e/` directory are present.
|
||||
|
||||
**Build/Dev:**
|
||||
- Tailwind CSS `3.4.17` - Utility-first styling configured in `tailwind.config.ts` and consumed by `src/app/globals.css` and component classes across `src/components/**/*`.
|
||||
- PostCSS `8.x` with `autoprefixer` `10.4.20` - CSS processing configured in `postcss.config.mjs`.
|
||||
- ESLint `9.x` with `eslint-config-next` `15.1.11` and `eslint-config-prettier` `9.1.0` - Linting configured in `.eslintrc.json`.
|
||||
- Prettier `3.4.2` - Formatting configured in `.prettierrc.json`.
|
||||
- `ts-node` `10.9.2` - TypeScript execution for seeding via the `prisma.seed` command in `package.json`.
|
||||
- `tailwindcss-animate` `1.0.7` - Tailwind plugin loaded in `tailwind.config.ts`.
|
||||
- Tailwind CSS `^3.4.17` - Utility styling is configured in `tailwind.config.ts` and loaded from `src/app/globals.css`.
|
||||
- `tailwindcss-animate` `^1.0.7` - Tailwind plugin registered in `tailwind.config.ts`.
|
||||
- PostCSS `^8` with `autoprefixer` `^10.4.20` - CSS processing is configured in `postcss.config.mjs`.
|
||||
- ESLint `^9` with `eslint-config-next` `15.1.11` and `eslint-config-prettier` `^9.1.0` - Linting rules are configured in `.eslintrc.json`.
|
||||
- Prettier `^3.4.2` - Formatting rules are configured in `.prettierrc.json`.
|
||||
- `ts-node` `^10.9.2` - Used by the Prisma seed command configured in `package.json`.
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**Critical:**
|
||||
- `next` `15.1.11` - Main application framework; routes, layouts, metadata, and route handlers all depend on files under `src/app/**/*`.
|
||||
- `react` `19.0.0` / `react-dom` `19.0.0` - Required by all React components in `src/components/**/*` and page/layout files in `src/app/**/*`.
|
||||
- `@prisma/client` `6.1.0` - Database access layer instantiated in `src/lib/prisma.ts` and used heavily in `src/hooks/useProjects.ts`, `src/app/api/projects/route.ts`, `src/app/api/signals/route.ts`, and related files.
|
||||
- `prisma` `6.1.0` - Schema and migration tool backing `prisma/schema.prisma` and `prisma/migrations/*/migration.sql`.
|
||||
- `next-intl` `4.0.2` - Locale middleware and message loading depend on `src/middleware.ts`, `src/i18n/request.ts`, and `src/messages/*.json`.
|
||||
- `zod` `3.24.1` - Input validation for search, tag maintenance, and webhook payloads in `src/app/api/search/ai/route.ts`, `src/app/api/tags/maintenance/route.ts`, and `src/app/api/webhook/signals/route.ts`.
|
||||
- `next` `15.1.11` - Main web framework for `src/app/**/*`.
|
||||
- `react` `19.0.0` and `react-dom` `19.0.0` - Required by all React components in `src/components/**/*`.
|
||||
- `@prisma/client` `^6.1.0` - Data access layer instantiated in `src/lib/prisma.ts`.
|
||||
- `prisma` `^6.1.0` - Schema and migration tooling for `prisma/schema.prisma` and `prisma/migrations/*`.
|
||||
- `next-intl` `^4.0.2` - Locale-aware routing and translations for `src/middleware.ts`, `src/i18n/request.ts`, and `src/messages/*.json`.
|
||||
- `zod` `^3.24.1` - Request validation for search, tags, and webhook contracts in `src/lib/validations.ts`.
|
||||
|
||||
**Infrastructure:**
|
||||
- `@vercel/analytics` `1.6.1` and `@vercel/speed-insights` `1.3.1` - Vercel client telemetry mounted in `src/app/VercelMetrics.tsx` and conditionally included in `src/app/layout.tsx`.
|
||||
- `react-markdown` `10.1.0`, `remark-gfm` `4.0.1`, and `rehype-sanitize` `6.0.0` - Markdown rendering stack used by `src/components/project/MarkdownContent.tsx`.
|
||||
- `lucide-react` `0.468.0` - Icon set optimized through `experimental.optimizePackageImports` in `next.config.js`.
|
||||
- `@vercel/analytics` `^1.6.1` - Client analytics are mounted in `src/app/VercelMetrics.tsx`.
|
||||
- `@vercel/speed-insights` `^1.3.1` - Frontend performance telemetry is mounted in `src/app/VercelMetrics.tsx`.
|
||||
- `react-markdown` `^10.1.0`, `remark-gfm` `^4.0.1`, and `rehype-sanitize` `^6.0.0` - Markdown rendering and sanitization stack used by `src/components/project/MarkdownContent.tsx`.
|
||||
- `lucide-react` `^0.468.0` - Icon package optimized through `experimental.optimizePackageImports` in `next.config.js`.
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- Template env vars are documented in `.env.example`.
|
||||
- Real runtime env files exist as `.env` and `.env.local`; contents were not read.
|
||||
- Code-level env usage is limited to:
|
||||
- `DATABASE_URL` in `prisma/schema.prisma`
|
||||
- `WEBHOOK_API_KEY` in `src/lib/auth.ts`
|
||||
- `N8N_AI_SEARCH_WEBHOOK` in `src/app/api/search/ai/route.ts`
|
||||
- `NEXT_PUBLIC_SITE_URL` in `src/app/robots.ts` and `src/app/sitemap.ts`
|
||||
- `VERCEL_ENV` in `src/app/layout.tsx`
|
||||
- `.env.example` also declares `NEXT_INTL_DEFAULT_LOCALE` and `NEXT_INTL_SUPPORTED_LOCALES`, but locale handling in current code is hard-coded in `src/i18n/request.ts` and `src/middleware.ts`.
|
||||
- Database connectivity is anchored by `DATABASE_URL` in `prisma/schema.prisma` and normalized by `buildPrismaDataSourceUrl()` in `src/lib/prisma-url.ts`.
|
||||
- Optional Postgres mTLS/SSL overlay is configured in `src/lib/prisma-url.ts` using `PG_SSL_ROOT_CERT_B64`, `PG_SSL_IDENTITY_P12_B64`, `PG_SSL_IDENTITY_PASSWORD`, `PG_SSL_CERT_DIR`, and `PG_SSL_MODE`.
|
||||
- Internal machine-to-machine auth uses `WEBHOOK_API_KEY` in `src/lib/auth.ts`.
|
||||
- Outbound AI search proxying uses `N8N_AI_SEARCH_WEBHOOK` in `src/app/api/search/ai/route.ts`.
|
||||
- Canonical URL generation uses `NEXT_PUBLIC_SITE_URL` in `src/app/robots.ts` and `src/app/sitemap.ts`.
|
||||
- Vercel-only telemetry gating uses `VERCEL_ENV` in `src/app/layout.tsx`.
|
||||
- Template env files exist as `.env.example`, `.env.local`, and `.env`; contents were not read.
|
||||
|
||||
**Build:**
|
||||
- `next.config.js` wires `next-intl`, remote image hosts, and `optimizePackageImports`.
|
||||
- `tsconfig.json` enables strict TypeScript, `noUncheckedIndexedAccess`, `noImplicitReturns`, and the `@/*` path alias.
|
||||
- `tailwind.config.ts` and `postcss.config.mjs` define the styling pipeline.
|
||||
- `vercel.json` defines deployment-time build/install commands, Next.js framework selection, region `hkg1`, and disables Git-triggered deployments.
|
||||
- `package.json` scripts expose `dev`, `build`, `start`, `lint`, `test`, and Prisma seeding.
|
||||
- `next.config.js` enables `output: "standalone"`, wires `next-intl`, configures remote image hosts, and optimizes `lucide-react` imports.
|
||||
- `tsconfig.json` enables strict TypeScript, `noUncheckedIndexedAccess`, `noImplicitReturns`, `noFallthroughCasesInSwitch`, and the `@/*` alias.
|
||||
- `tailwind.config.ts` and `postcss.config.mjs` define the frontend styling pipeline.
|
||||
- `vercel.json` defines install/build commands, the `nextjs` framework target, and region `hkg1`.
|
||||
- `Dockerfile`, `Dockerfile.runtime`, and `nixpacks.toml` define container build and runtime packaging.
|
||||
- `scripts/generate-n8n-context.mjs` generates `docs/integrations/n8n/CONTEXT.generated.md` and the GSD mirror `.planning/codebase/N8N-CONTEXT.md`.
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- Node.js and `pnpm` are required to run `package.json` scripts.
|
||||
- PostgreSQL is required because `prisma/schema.prisma` uses the `postgresql` provider and `DATABASE_URL`.
|
||||
- Prisma client generation is required before production builds; `vercel.json` explicitly runs `pnpm prisma generate && pnpm build`.
|
||||
- Node.js 22-compatible runtime and `pnpm` are required to run `package.json` scripts.
|
||||
- PostgreSQL is required because `prisma/schema.prisma` uses the `postgresql` provider.
|
||||
- Prisma client generation is part of the build path in `vercel.json`, `Dockerfile`, and `nixpacks.toml`.
|
||||
- The n8n context refresh flow requires `pnpm n8n:context`, which runs `scripts/generate-n8n-context.mjs`.
|
||||
|
||||
**Production:**
|
||||
- Vercel is the explicit deployment target indicated by `vercel.json` and the Vercel-specific telemetry components in `src/app/VercelMetrics.tsx`.
|
||||
- The app expects Vercel environment semantics for telemetry gating via `process.env.VERCEL_ENV` in `src/app/layout.tsx`.
|
||||
- Server runtime storage is PostgreSQL via Prisma; no alternate production datastore is configured in repo.
|
||||
- Vercel is a first-class deployment target via `vercel.json` and the telemetry components in `src/app/VercelMetrics.tsx`.
|
||||
- Container deployment is also supported through `Dockerfile`, `Dockerfile.runtime`, and `nixpacks.toml`.
|
||||
- Production data storage is PostgreSQL via Prisma, with a `vector` column in `prisma/schema.prisma` used by semantic-search-related workflows documented under `docs/integrations/n8n/`.
|
||||
- Standalone Next.js output is expected by `Dockerfile.runtime`, which boots `.next/standalone/server.js`.
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-04-18*
|
||||
*Stack analysis: 2026-04-20*
|
||||
|
||||
+190
-117
@@ -1,196 +1,266 @@
|
||||
# Codebase Structure
|
||||
|
||||
**Analysis Date:** 2026-04-18
|
||||
**Analysis Date:** 2026-04-20
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```text
|
||||
agent_park/
|
||||
agent-park/
|
||||
├── .planning/codebase/ # Generated codebase maps consumed by GSD
|
||||
├── docs/integrations/n8n/ # Source-of-truth docs for external n8n workflows and dataflow
|
||||
├── prisma/ # Prisma schema, migrations, and seed script
|
||||
├── src/app/ # Next.js App Router entry points, layouts, API routes, global assets
|
||||
├── public/ # Static assets served by Next.js
|
||||
├── scripts/ # Repo maintenance scripts
|
||||
├── src/app/ # App Router pages, layouts, metadata files, and API routes
|
||||
├── src/components/ # Reusable UI grouped by feature area
|
||||
├── src/hooks/ # Server-side query/read-model modules
|
||||
├── src/i18n/ # next-intl request configuration
|
||||
├── src/lib/ # Shared utilities, validation, Prisma client, taxonomy logic
|
||||
├── src/messages/ # Locale message JSON files
|
||||
├── .eslintrc.json # ESLint rules
|
||||
├── .prettierrc.json # Prettier rules
|
||||
├── next.config.js # Next.js config with next-intl plugin
|
||||
├── src/i18n/ # `next-intl` request configuration
|
||||
├── src/lib/ # Shared infrastructure and domain utilities
|
||||
├── src/messages/ # Locale dictionaries
|
||||
├── AGENTS.md # Repo-specific agent instructions
|
||||
├── next.config.js # Next.js config with `next-intl`
|
||||
├── package.json # App scripts and dependency manifest
|
||||
├── tailwind.config.ts # Tailwind theme and content scan config
|
||||
├── tsconfig.json # TypeScript config and `@/*` path alias
|
||||
├── vercel.json # Vercel build/deploy config
|
||||
└── vitest.config.ts # Vitest config for `src/**/*.test.ts`
|
||||
├── tsconfig.json # TypeScript config and `@/*` alias
|
||||
├── vercel.json # Deployment config
|
||||
└── vitest.config.ts # Unit test config
|
||||
```
|
||||
|
||||
## Directory Purposes
|
||||
|
||||
**`.planning/codebase/`:**
|
||||
- Purpose: Store generated architecture, stack, integration, testing, and concern maps for GSD.
|
||||
- Contains: `ARCHITECTURE.md`, `STRUCTURE.md`, `STACK.md`, `INTEGRATIONS.md`, `CONVENTIONS.md`, `TESTING.md`, `CONCERNS.md`, `N8N-CONTEXT.md`, `N8N-DATAFLOW.md`
|
||||
- Key files: `.planning/codebase/ARCHITECTURE.md`, `.planning/codebase/STRUCTURE.md`, `.planning/codebase/N8N-CONTEXT.md`, `.planning/codebase/N8N-DATAFLOW.md`
|
||||
|
||||
**`docs/integrations/n8n/`:**
|
||||
- Purpose: Keep external workflow contracts, workflow inventory, and end-to-end dataflow inside the repository.
|
||||
- Contains: `registry.json`, generated workflow inventory, cross-workflow dataflow docs, exported workflow placeholders, and per-workflow notes.
|
||||
- Key files: `docs/integrations/n8n/README.md`, `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/DATAFLOW.md`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md`
|
||||
|
||||
**`docs/integrations/n8n/workflows/`:**
|
||||
- Purpose: Describe individual external workflows separately from the app code.
|
||||
- Contains: `01-topic-discovery.md` through `08-project-tag-reset.md`, plus `README.md`.
|
||||
- Key files: `docs/integrations/n8n/workflows/01-topic-discovery.md`, `docs/integrations/n8n/workflows/02-github-trending-discovery.md`, `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md`, `docs/integrations/n8n/workflows/06-rag-project-search.md`, `docs/integrations/n8n/workflows/07-signals-aggregation.md`
|
||||
|
||||
**`prisma/`:**
|
||||
- Purpose: Own the database contract and seed path.
|
||||
- Contains: `prisma/schema.prisma`, migration directories under `prisma/migrations/*`, and `prisma/seed.ts`.
|
||||
- Key files: `prisma/schema.prisma`, `prisma/seed.ts`
|
||||
- Purpose: Own the database contract and local bootstrapping path.
|
||||
- Contains: `schema.prisma`, `seed.ts`, and migration directories under `prisma/migrations/`.
|
||||
- Key files: `prisma/schema.prisma`, `prisma/seed.ts`, `prisma/migrations/20260106122028_init/migration.sql`, `prisma/migrations/20260126000000_add_project_embedding/migration.sql`, `prisma/migrations/20260418191500_remove_discovery_pipeline/migration.sql`
|
||||
|
||||
**`scripts/`:**
|
||||
- Purpose: Hold repository maintenance scripts that generate or sync documentation.
|
||||
- Contains: `generate-n8n-context.mjs`
|
||||
- Key files: `scripts/generate-n8n-context.mjs`
|
||||
|
||||
**`src/app/`:**
|
||||
- Purpose: Hold all App Router entry points, route-local components, API handlers, and app-wide assets.
|
||||
- Contains: `layout.tsx`, `page.tsx`, `route.ts`, route-local client components, `globals.css`, `robots.ts`, `sitemap.ts`, and `icon.svg`.
|
||||
- Key files: `src/app/layout.tsx`, `src/app/[locale]/layout.tsx`, `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/api/projects/route.ts`, `src/app/api/signals/route.ts`
|
||||
- Purpose: Own all App Router entry points, route-local client islands, global assets, metadata routes, and API handlers.
|
||||
- Contains: `layout.tsx`, `globals.css`, `robots.ts`, `sitemap.ts`, `VercelMetrics.tsx`, localized routes, and `/api/*`.
|
||||
- Key files: `src/app/layout.tsx`, `src/app/[locale]/layout.tsx`, `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/signals/page.tsx`, `src/app/api/projects/route.ts`, `src/app/api/signals/route.ts`
|
||||
|
||||
**`src/app/[locale]/`:**
|
||||
- Purpose: Group locale-prefixed user-facing routes.
|
||||
- Contains: localized pages such as `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`, and fallbacks like `src/app/[locale]/not-found.tsx`.
|
||||
- Key files: `src/app/[locale]/layout.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`
|
||||
- Purpose: Group all locale-prefixed, user-facing routes under the same shell.
|
||||
- Contains: Home, about, projects, signals, `not-found.tsx`, and a catch-all fallback.
|
||||
- Key files: `src/app/[locale]/layout.tsx`, `src/app/[locale]/page.tsx`, `src/app/[locale]/about/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, `src/app/[locale]/signals/page.tsx`
|
||||
|
||||
**`src/app/[locale]/projects/`:**
|
||||
- Purpose: Own the projects browse route plus the route-local browser islands that manage query-driven browsing.
|
||||
- Contains: `page.tsx`, `ProjectsPageClient.tsx`, `ProjectsResultsClient.tsx`, and the `[id]/` detail route subfolder.
|
||||
- Key files: `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/[locale]/projects/[id]/page.tsx`
|
||||
|
||||
**`src/app/api/`:**
|
||||
- Purpose: Group JSON endpoints and webhook handlers by resource.
|
||||
- Contains: route handlers and a route-local service module at `src/app/api/tags/maintenance/service.ts`.
|
||||
- Key files: `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/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts`
|
||||
- Purpose: Group JSON APIs and webhook handlers by resource area.
|
||||
- Contains: Project list/detail routes, AI search proxy, signals feed, tags endpoints, and signal ingestion webhook.
|
||||
- Key files: `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/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts`
|
||||
|
||||
**`src/components/`:**
|
||||
- Purpose: Store reusable UI by domain area, not by primitive type.
|
||||
- Contains: feature folders `home`, `layout`, `locale`, `project`, `search`, and `signals`.
|
||||
- Key files: `src/components/project/ProjectList.tsx`, `src/components/project/ProjectDetail.tsx`, `src/components/search/AISearchBar.tsx`, `src/components/signals/SignalFeedClient.tsx`
|
||||
- Purpose: Store reusable UI by product area instead of by primitive type.
|
||||
- Contains: `home`, `layout`, `locale`, `project`, `search`, `signals`, and an empty `ui` folder.
|
||||
- Key files: `src/components/home/HomeOverviewStats.tsx`, `src/components/project/ProjectCard.tsx`, `src/components/project/ProjectDetail.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/search/AISearchBar.tsx`, `src/components/signals/SignalFeedClient.tsx`
|
||||
|
||||
**`src/hooks/`:**
|
||||
- Purpose: Hold server-side data-fetching and read-model builders.
|
||||
- Contains: `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`.
|
||||
- Purpose: Hold server-side query/read-model modules.
|
||||
- Contains: `useProjects.ts` and `useHome.ts`
|
||||
- Key files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`
|
||||
|
||||
**`src/i18n/`:**
|
||||
- Purpose: Configure `next-intl` request handling.
|
||||
- Contains: `src/i18n/request.ts`.
|
||||
- Purpose: Configure `next-intl` request resolution and message loading.
|
||||
- Contains: `request.ts`
|
||||
- Key files: `src/i18n/request.ts`
|
||||
|
||||
**`src/lib/`:**
|
||||
- Purpose: Hold cross-cutting domain and infrastructure utilities.
|
||||
- Contains: auth, cache helpers, Prisma client, GitHub link helpers, tag taxonomy, slug generation, hotness scoring, and validation schemas.
|
||||
- Key files: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/signal-hotness.ts`, `src/lib/validations.ts`, `src/lib/github/badges.ts`
|
||||
- Purpose: Hold shared infrastructure, schema validation, and domain helpers used across pages and APIs.
|
||||
- Contains: Prisma bootstrap, API auth, cache fallback, slug generation, tag taxonomy, signal hotness helpers, Zod schemas, and GitHub badge utilities.
|
||||
- Key files: `src/lib/prisma.ts`, `src/lib/prisma-url.ts`, `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/validations.ts`, `src/lib/github/badges.ts`
|
||||
|
||||
**`src/messages/`:**
|
||||
- Purpose: Store locale dictionaries loaded by `next-intl`.
|
||||
- Contains: `src/messages/en.json` and `src/messages/zh.json`.
|
||||
- Purpose: Store locale message bundles consumed by `next-intl`.
|
||||
- Contains: `en.json` and `zh.json`
|
||||
- Key files: `src/messages/en.json`, `src/messages/zh.json`
|
||||
|
||||
**`src/types/`:**
|
||||
- Purpose: Reserved location for shared type modules.
|
||||
- Contains: No files in the inspected tree.
|
||||
- Key files: Not applicable
|
||||
|
||||
## Key File Locations
|
||||
|
||||
**Entry Points:**
|
||||
- `src/app/layout.tsx`: Root HTML/body shell and conditional analytics.
|
||||
- `src/app/[locale]/layout.tsx`: Locale-aware site shell, navigation, footer, and translation provider.
|
||||
- `src/app/[locale]/page.tsx`: Localized home page.
|
||||
- `src/app/[locale]/projects/page.tsx`: Projects search/browse page.
|
||||
- `src/app/[locale]/projects/[id]/page.tsx`: Project detail page.
|
||||
- `src/app/[locale]/signals/page.tsx`: Signals feed page shell.
|
||||
- `src/app/[locale]/about/page.tsx`: About page.
|
||||
- `src/middleware.ts`: Locale routing middleware.
|
||||
- `src/app/layout.tsx`: Root HTML/body shell and Vercel metrics mounting.
|
||||
- `src/middleware.ts`: Locale middleware for all non-API, non-static requests.
|
||||
- `src/app/[locale]/layout.tsx`: Localized shell, navigation, footer, and translation provider.
|
||||
- `src/app/[locale]/page.tsx`: Home route entry.
|
||||
- `src/app/[locale]/projects/page.tsx`: Projects browse entry.
|
||||
- `src/app/[locale]/projects/[id]/page.tsx`: Project detail entry.
|
||||
- `src/app/[locale]/signals/page.tsx`: Signals route entry.
|
||||
- `src/app/sitemap.ts`: Dynamic sitemap generation from project data.
|
||||
- `src/app/robots.ts`: Robots rules and sitemap pointer.
|
||||
- `scripts/generate-n8n-context.mjs`: n8n context generation entry point.
|
||||
|
||||
**API Endpoints:**
|
||||
- `src/app/api/projects/route.ts`: Paginated projects list API.
|
||||
- `src/app/api/projects/route.ts`: Paginated projects list endpoint.
|
||||
- `src/app/api/projects/[slug]/route.ts`: Single-project JSON endpoint.
|
||||
- `src/app/api/search/ai/route.ts`: AI search proxy and hydrator.
|
||||
- `src/app/api/signals/route.ts`: Cursor-paginated signals feed API.
|
||||
- `src/app/api/tags/route.ts`: Tags list API.
|
||||
- `src/app/api/tags/maintenance/route.ts`: Authenticated tag update/merge API.
|
||||
- `src/app/api/tags/reset-projects/route.ts`: Authenticated project-tag replacement API.
|
||||
- `src/app/api/search/ai/route.ts`: AI search proxy to the external n8n webhook plus DB hydration.
|
||||
- `src/app/api/signals/route.ts`: Cursor-paginated signals feed endpoint.
|
||||
- `src/app/api/tags/route.ts`: Cached tag list endpoint.
|
||||
- `src/app/api/tags/maintenance/route.ts`: Authenticated tag merge/update endpoint.
|
||||
- `src/app/api/tags/maintenance/service.ts`: Route-local service module for tag maintenance transactions.
|
||||
- `src/app/api/tags/reset-projects/route.ts`: Authenticated project-tag replacement endpoint.
|
||||
- `src/app/api/webhook/signals/route.ts`: Authenticated signal ingestion webhook.
|
||||
|
||||
**Configuration:**
|
||||
- `next.config.js`: Wraps Next config with the `next-intl` plugin and remote image rules.
|
||||
- `tailwind.config.ts`: Tailwind content paths and theme extension.
|
||||
- `tsconfig.json`: Strict TypeScript settings and the `@/*` alias.
|
||||
- `vitest.config.ts`: Node test environment and `src/**/*.test.ts` inclusion.
|
||||
- `vercel.json`: Build/install commands and region targeting.
|
||||
- `.eslintrc.json`: Lint rules.
|
||||
- `.prettierrc.json`: Formatting rules.
|
||||
- `.env.example`: Template environment file. `.env` and `.env.local` are present in the repository root but were not inspected.
|
||||
- `package.json`: App scripts, dependency graph, and `pnpm n8n:context`.
|
||||
- `next.config.js`: `next-intl` plugin wrapper, standalone output, and image host allowlist.
|
||||
- `tailwind.config.ts`: Theme extension and content scanning.
|
||||
- `tsconfig.json`: TypeScript settings and the `@/*` alias.
|
||||
- `vitest.config.ts`: Node test environment and `src/**/*.test.ts` include pattern.
|
||||
- `vercel.json`: Deployment/build settings.
|
||||
- `.env.example`: Template env file, including webhook-related variables. Real secrets stay outside committed files.
|
||||
- `docs/integrations/n8n/registry.json`: Editable source of truth for external workflow contracts.
|
||||
|
||||
**Core Logic:**
|
||||
- `src/hooks/useProjects.ts`: Project list/detail queries, filter normalization, tag grouping, caching, and retry logic.
|
||||
- `src/hooks/useHome.ts`: Home-page aggregate data builder.
|
||||
- `src/lib/tag-taxonomy.ts`: Tag category metadata and inference rules.
|
||||
- `src/lib/validations.ts`: Zod schemas for API inputs and domain payloads.
|
||||
- `src/app/api/tags/maintenance/service.ts`: Transactional tag maintenance logic.
|
||||
- `src/lib/signal-hotness.ts`: Signal hot-score computation and schema capability detection.
|
||||
- `src/hooks/useProjects.ts`: Project/tag read models, cache wrappers, filter logic, and DB retry handling.
|
||||
- `src/hooks/useHome.ts`: Homepage aggregate data builder.
|
||||
- `src/lib/validations.ts`: Shared Zod schemas for queries, webhooks, and internal mutation payloads.
|
||||
- `src/lib/tag-taxonomy.ts`: Tag category ordering, presets, and inference helpers.
|
||||
- `src/lib/signal-hotness.ts`: Hotness scoring and schema-capability detection.
|
||||
- `src/lib/auth.ts`: Shared internal API-key validation.
|
||||
- `src/app/api/tags/maintenance/service.ts`: Tag merge/update transaction logic.
|
||||
- `scripts/generate-n8n-context.mjs`: Registry-to-doc generation logic for n8n context.
|
||||
|
||||
**Testing:**
|
||||
- `src/lib/auth.test.ts`
|
||||
- `src/lib/prisma-url.test.ts`
|
||||
- `src/lib/validations.test.ts`
|
||||
- `src/lib/validations.tag-maintenance.test.ts`
|
||||
- `src/app/api/tags/route.test.ts`
|
||||
- `src/app/api/tags/maintenance/route.test.ts`
|
||||
- `src/app/api/tags/maintenance/service.test.ts`
|
||||
- `src/app/api/tags/reset-projects/route.test.ts`
|
||||
- `e2e/`: Not present in the inspected repository tree.
|
||||
- `e2e/`: Not present in the inspected tree
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Files:**
|
||||
- Use Next.js route conventions inside `src/app`, for example `page.tsx`, `layout.tsx`, `route.ts`, `not-found.tsx`, `robots.ts`, and `sitemap.ts`.
|
||||
- Use `PascalCase.tsx` for reusable components, for example `src/components/project/ProjectCard.tsx`, `src/components/project/ProjectSidebar.tsx`, and `src/components/home/HomeOverviewStats.tsx`.
|
||||
- Keep route-specific helper components adjacent to the route they support, for example `src/app/[locale]/projects/ProjectsPageClient.tsx` and `src/app/[locale]/projects/ProjectsResultsClient.tsx`.
|
||||
- Use lower-case or kebab-case utility filenames in `src/lib`, for example `src/lib/prisma.ts`, `src/lib/cache.ts`, `src/lib/tag-taxonomy.ts`, and `src/lib/signal-hotness.ts`.
|
||||
- Keep test files adjacent to the code they cover using `*.test.ts`, for example `src/app/api/tags/maintenance/service.test.ts`.
|
||||
- Use Next.js route conventions inside `src/app`, such as `page.tsx`, `layout.tsx`, `route.ts`, `not-found.tsx`, `robots.ts`, and `sitemap.ts`.
|
||||
- Use `PascalCase.tsx` for reusable components under `src/components/`, such as `ProjectCard.tsx`, `ProjectDetail.tsx`, `HomeOverviewStats.tsx`, and `SignalFeedClient.tsx`.
|
||||
- Keep route-local browser components adjacent to their route, such as `src/app/[locale]/projects/ProjectsPageClient.tsx` and `src/app/[locale]/projects/ProjectsResultsClient.tsx`.
|
||||
- Keep shared utility files lower-case or kebab-case under `src/lib/`, such as `prisma.ts`, `cache.ts`, `signal-hotness.ts`, and `tag-taxonomy.ts`.
|
||||
- Keep tests adjacent to the source file they exercise, using `*.test.ts`.
|
||||
|
||||
**Directories:**
|
||||
- Group reusable UI by product area under `src/components`, for example `src/components/project` and `src/components/search`.
|
||||
- Group route files by URL shape under `src/app`, for example `src/app/[locale]/projects/[id]` and `src/app/api/projects/[slug]`.
|
||||
- Keep infrastructure and domain utilities flat under `src/lib` instead of nesting many sublayers. The only nested utility folder detected is `src/lib/github/`.
|
||||
- Group user-facing routes by URL shape under `src/app/`.
|
||||
- Group reusable UI by feature area under `src/components/`, not by HTML primitive.
|
||||
- Keep shared server read-model logic flat in `src/hooks/`.
|
||||
- Keep cross-cutting utilities flat in `src/lib/`, with only narrow nested folders such as `src/lib/github/`.
|
||||
- Keep external automation documentation under `docs/integrations/n8n/`, not mixed into `src/`.
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New User-Facing Page or Route Segment:**
|
||||
- Primary code: add a new route under `src/app/[locale]/...` using Next conventions, for example `src/app/[locale]/new-section/page.tsx`.
|
||||
- Shared shell changes: modify `src/app/[locale]/layout.tsx` only if the new route needs site-wide navigation or footer changes.
|
||||
- Metadata/SEO for the route: colocate `generateMetadata` in the route file, following `src/app/[locale]/about/page.tsx` and `src/app/[locale]/signals/page.tsx`.
|
||||
**New User-Facing Route:**
|
||||
- Primary code: add a route under `src/app/[locale]/...`, for example `src/app/[locale]/new-section/page.tsx`.
|
||||
- Route metadata: colocate `generateMetadata` in that route file.
|
||||
- Shared shell changes: edit `src/app/[locale]/layout.tsx` only if the new route needs global navigation/footer updates.
|
||||
|
||||
**New API Endpoint:**
|
||||
- Primary code: add `route.ts` under `src/app/api/<resource>/`.
|
||||
- Route-local helpers or service logic: colocate them next to the route, following `src/app/api/tags/maintenance/service.ts`.
|
||||
- Validation: add or extend schemas in `src/lib/validations.ts` unless the validation is tightly route-local and one-off.
|
||||
**New Route-Local Browser State:**
|
||||
- Implementation: colocate the client component under the route folder in `src/app/[locale]/...`.
|
||||
- Examples to follow: `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`
|
||||
|
||||
**New Reusable UI Component:**
|
||||
- Implementation: place it in the matching feature folder under `src/components/`.
|
||||
- Examples:
|
||||
- Project-related UI goes in `src/components/project/`.
|
||||
- Search UI goes in `src/components/search/`.
|
||||
- Home-page modules go in `src/components/home/`.
|
||||
- If a component is only used by one route and owns that route’s browser state, colocate it under the route folder in `src/app/[locale]/...`, following `src/app/[locale]/projects/ProjectsPageClient.tsx`.
|
||||
- `src/components/project/` for project cards, details, sidebars, filters, and related UI.
|
||||
- `src/components/search/` for search bars and AI-result renderers.
|
||||
- `src/components/signals/` for signal-specific reusable UI.
|
||||
|
||||
**New Data Query or Read Model:**
|
||||
- Shared project/home reads: extend `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`.
|
||||
- New domain-specific reads: add a new module under `src/hooks/` if the logic becomes large enough to stand alone.
|
||||
- Use `src/lib/prisma.ts` for Prisma access instead of creating new Prisma clients.
|
||||
**New Read Query or Read Model:**
|
||||
- Project and taxonomy reads: extend `src/hooks/useProjects.ts`.
|
||||
- Home aggregate reads: extend `src/hooks/useHome.ts`.
|
||||
- New domain-specific read module: add another file under `src/hooks/` if the logic is large enough to stand alone.
|
||||
|
||||
**New Shared Utility or Domain Rule:**
|
||||
- Shared helpers: add to `src/lib/`.
|
||||
- Taxonomy/tag rules: extend `src/lib/tag-taxonomy.ts`.
|
||||
- Request validation: extend `src/lib/validations.ts`.
|
||||
- Authentication helpers for internal APIs: extend `src/lib/auth.ts`.
|
||||
- Prisma/bootstrap changes: `src/lib/prisma.ts` or `src/lib/prisma-url.ts`.
|
||||
- Validation contracts: `src/lib/validations.ts`.
|
||||
- Tag/category rules: `src/lib/tag-taxonomy.ts`.
|
||||
- Signal hotness logic: `src/lib/signal-hotness.ts`.
|
||||
- Internal API auth helpers: `src/lib/auth.ts`.
|
||||
|
||||
**New API Endpoint:**
|
||||
- Primary code: add `route.ts` under `src/app/api/<resource>/`.
|
||||
- Route-specific service logic: colocate it beside the route, following `src/app/api/tags/maintenance/service.ts`.
|
||||
- Shared payload validation: add or extend schemas in `src/lib/validations.ts`.
|
||||
|
||||
**New n8n Workflow Mapping:**
|
||||
- Workflow contract/source of truth: update `docs/integrations/n8n/registry.json`.
|
||||
- Workflow note: add or update the matching file under `docs/integrations/n8n/workflows/`.
|
||||
- Generated mirrors: run `pnpm n8n:context` so `docs/integrations/n8n/CONTEXT.generated.md` and `.planning/codebase/N8N-CONTEXT.md` stay synchronized.
|
||||
|
||||
**New External Discovery Queue Logic:**
|
||||
- Do not place it under `src/app/api/` unless the discovery service is explicitly being absorbed into this repository.
|
||||
- Current queue/task endpoints described in `docs/integrations/n8n/DATAFLOW.md` are external boundaries, not missing route stubs to extend casually.
|
||||
|
||||
**New Locale Strings:**
|
||||
- Add keys to both `src/messages/en.json` and `src/messages/zh.json`.
|
||||
- Resolve them through `next-intl` in route or component code, following `src/app/[locale]/layout.tsx` and `src/components/project/ProjectList.tsx`.
|
||||
- Resolve them through `next-intl` in route or component code.
|
||||
|
||||
**New Database Model or Field:**
|
||||
- Schema: update `prisma/schema.prisma`.
|
||||
- Migration: create a new directory under `prisma/migrations/`.
|
||||
- Seed updates: modify `prisma/seed.ts` only if the new data is required for local bootstrapping.
|
||||
- Schema: `prisma/schema.prisma`
|
||||
- Migration: add a new directory under `prisma/migrations/`
|
||||
- Seed changes: `prisma/seed.ts` if local bootstrapping requires the new data
|
||||
|
||||
**New Tests:**
|
||||
- API and utility tests: colocate with the source file as `*.test.ts`.
|
||||
- There is no current `e2e/` directory in the inspected tree, so introducing end-to-end tests will require creating that top-level directory explicitly.
|
||||
- Unit-style tests: colocate as `*.test.ts` beside the source file.
|
||||
- Browser/e2e tests: create a top-level `e2e/` directory because it is not present in the current tree.
|
||||
|
||||
## Special Directories
|
||||
|
||||
**`docs/integrations/n8n/`:**
|
||||
- Purpose: Document external workflows that feed or depend on this repo.
|
||||
- Generated: Partially
|
||||
- Committed: Yes
|
||||
|
||||
**`docs/integrations/n8n/exports/`:**
|
||||
- Purpose: Reserved location for exported workflow JSON files.
|
||||
- Generated: Yes
|
||||
- Committed: Yes
|
||||
|
||||
**`.planning/codebase/`:**
|
||||
- Purpose: Store generated repository maps for GSD orchestration and execution.
|
||||
- Generated: Yes
|
||||
- Committed: Yes
|
||||
|
||||
**`src/app/[locale]/projects/`:**
|
||||
- Purpose: Contains one route page plus its route-local client islands.
|
||||
- Purpose: Contains the projects browse route plus its route-local client islands and nested detail route.
|
||||
- Generated: No
|
||||
- Committed: Yes
|
||||
|
||||
**`src/app/api/tags/maintenance/`:**
|
||||
- Purpose: Contains a route handler, a service module, and colocated tests for the tag-maintenance feature.
|
||||
- Purpose: Contains a route handler, a route-local service module, and colocated tests for tag governance.
|
||||
- Generated: No
|
||||
- Committed: Yes
|
||||
|
||||
**`prisma/migrations/`:**
|
||||
- Purpose: Stores schema migration history.
|
||||
- Purpose: Store schema migration history.
|
||||
- Generated: Yes
|
||||
- Committed: Yes
|
||||
|
||||
@@ -199,26 +269,29 @@ agent_park/
|
||||
- Generated: Yes
|
||||
- Committed: No
|
||||
|
||||
**`.planning/codebase/`:**
|
||||
- Purpose: Stores generated repository mapping documents such as this file.
|
||||
- Generated: Yes
|
||||
- Committed: Uncertain from inspected source files alone.
|
||||
|
||||
## Placement Rules
|
||||
|
||||
**Use `src/app` for route ownership:**
|
||||
- Put code in `src/app` only when it directly maps to a URL, metadata document, or route-local UI state.
|
||||
**Use `src/app` only for route ownership:**
|
||||
- Put code under `src/app` when it directly maps to a URL, metadata document, or route-local client island.
|
||||
|
||||
**Use `src/components` for reuse across routes:**
|
||||
- Promote a route-local component into `src/components` only when another route needs it or it becomes generic enough to stand alone.
|
||||
**Use `src/components` for cross-route reuse:**
|
||||
- Promote a route-local component into `src/components/` only when it is reused or clearly generic.
|
||||
|
||||
**Treat `src/hooks` as a server query layer, not browser hooks:**
|
||||
- The existing `useProjects.ts` and `useHome.ts` modules are imported by server components and API handlers. Follow that pattern when adding read models there.
|
||||
**Treat `src/hooks` as server query modules, not browser hooks:**
|
||||
- `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` are imported by server components and route handlers.
|
||||
- Follow that pattern for future read-model modules.
|
||||
|
||||
**Keep Prisma out of most UI files:**
|
||||
- UI code typically consumes data returned by `src/hooks/*` or JSON from `src/app/api/*`.
|
||||
- Direct Prisma calls are concentrated in `src/hooks/*`, `src/app/api/*`, and `src/lib/prisma.ts`.
|
||||
**Keep Prisma concentrated away from most UI files:**
|
||||
- Direct Prisma access lives in `src/hooks/*`, `src/app/api/*`, and `src/lib/prisma.ts`.
|
||||
- Most UI should consume read models or JSON responses, not query the database directly.
|
||||
|
||||
**Keep external workflow context out of `src/`:**
|
||||
- n8n and discovery queue behavior belongs in `docs/integrations/n8n/*` and the generated `.planning/codebase/N8N-*.md` mirrors until runtime code is actually moved into this repository.
|
||||
|
||||
**Do not assume `/api/discovery/*` exists here:**
|
||||
- If a feature depends on discovery tasks, first verify whether it belongs to the external discovery service or the app itself.
|
||||
- The current inspected tree contains no `src/app/api/discovery/` implementation.
|
||||
|
||||
---
|
||||
|
||||
*Structure analysis: 2026-04-18*
|
||||
*Structure analysis: 2026-04-20*
|
||||
|
||||
+76
-130
@@ -1,38 +1,34 @@
|
||||
# Testing Patterns
|
||||
|
||||
**Analysis Date:** 2026-04-18
|
||||
**Analysis Date:** 2026-04-20
|
||||
|
||||
## Test Framework
|
||||
|
||||
**Runner:**
|
||||
- `vitest` via `vitest.config.ts`.
|
||||
- `package.json` declares `vitest` in `devDependencies` and exposes `pnpm test`.
|
||||
- Verified on 2026-04-18: `pnpm test` executed with Vitest `v2.1.9` and passed all current test files.
|
||||
- Use `vitest` via `vitest.config.ts` and `pnpm test` from `package.json`.
|
||||
- `vitest.config.ts` sets `environment: "node"`, resolves `@` to `./src`, disables watch mode, and only includes `src/**/*.test.ts`.
|
||||
- Current verification on 2026-04-20: `pnpm test` passed with 8 test files and 26 tests.
|
||||
|
||||
**Assertion Library:**
|
||||
- Vitest built-ins: `describe`, `it`, `expect`, `beforeEach`, and `vi`, as seen in `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`.
|
||||
|
||||
**Config:**
|
||||
- `vitest.config.ts` sets `environment: "node"`, aliases `@` to `./src`, disables watch mode, and includes only `src/**/*.test.ts`.
|
||||
- No separate setup file, coverage config, browser environment, or integration test project was detected in `vitest.config.ts`.
|
||||
- Use Vitest built-ins from `vitest`, including `describe`, `it`, `expect`, `beforeEach`, `afterEach`, and `vi`, as seen in `src/lib/auth.test.ts`, `src/lib/prisma-url.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`.
|
||||
|
||||
**Run Commands:**
|
||||
```bash
|
||||
pnpm test # Run all configured Vitest suites
|
||||
pnpm lint # Run ESLint quality checks
|
||||
pnpm build # Run production build, lint, and type validation
|
||||
pnpm build # Run production build checks
|
||||
```
|
||||
- `AGENTS.md` also reserves `pnpm test:e2e` for Playwright, but no `test:e2e` script or `playwright.config.*` file is present in the current workspace.
|
||||
|
||||
## Test File Organization
|
||||
|
||||
**Location:**
|
||||
- Tests are co-located beside the source they exercise.
|
||||
- Library tests live next to utilities, for example `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/lib/validations.tag-maintenance.test.ts`.
|
||||
- API tests live next to route or service modules, for example `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, and `src/app/api/tags/maintenance/service.test.ts`.
|
||||
- Keep tests co-located beside the code they exercise under `src`, for example `src/lib/auth.test.ts`, `src/lib/prisma-url.test.ts`, `src/app/api/tags/route.test.ts`, and `src/app/api/tags/maintenance/service.test.ts`.
|
||||
- No `e2e/` directory is present in the repository snapshot analyzed on 2026-04-20.
|
||||
|
||||
**Naming:**
|
||||
- Use `*.test.ts`. No `*.spec.ts` files were detected under `src` on 2026-04-18.
|
||||
- The include pattern in `vitest.config.ts` means `*.test.tsx` and files outside `src` are not picked up by default.
|
||||
- Use `*.test.ts`. No `*.spec.ts` or `*.test.tsx` files were detected by `rg --files` or included by `vitest.config.ts`.
|
||||
- Keep route tests adjacent to `route.ts` or `service.ts` files, for example `src/app/api/tags/reset-projects/route.test.ts` next to `src/app/api/tags/reset-projects/route.ts`.
|
||||
|
||||
**Structure:**
|
||||
```text
|
||||
@@ -40,6 +36,8 @@ src/
|
||||
lib/
|
||||
auth.ts
|
||||
auth.test.ts
|
||||
prisma-url.ts
|
||||
prisma-url.test.ts
|
||||
app/api/tags/
|
||||
route.ts
|
||||
route.test.ts
|
||||
@@ -59,50 +57,34 @@ import { NextRequest } from "next/server";
|
||||
import { POST } from "./route";
|
||||
|
||||
const { transactionMock, revalidatePathMock, txMock } = vi.hoisted(() => {
|
||||
// create hoisted mocks here
|
||||
});
|
||||
const tx = {
|
||||
projectTag: {
|
||||
deleteMany: vi.fn(),
|
||||
createMany: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({ prisma: { $transaction: transactionMock } }));
|
||||
vi.mock("next/cache", () => ({ revalidatePath: revalidatePathMock }));
|
||||
|
||||
function buildRequest(body: unknown): NextRequest {
|
||||
return new NextRequest("http://localhost:3000/api/tags/maintenance", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
describe("POST /api/tags/maintenance", () => {
|
||||
beforeEach(() => {
|
||||
process.env.WEBHOOK_API_KEY = "k".repeat(32);
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns 401 for wrong API key", async () => {
|
||||
const response = await POST(buildRequest({ apiKey: "a".repeat(32), updates: [], merges: [] }));
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
return {
|
||||
transactionMock: vi.fn(async (callback) => callback(tx)),
|
||||
revalidatePathMock: vi.fn(),
|
||||
txMock: tx,
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**Patterns:**
|
||||
- Use a top-level `describe(...)` per module or endpoint, with test names phrased as behavior statements.
|
||||
- Reset spies and mock state in `beforeEach`, as in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`.
|
||||
- For API routes, call the exported `GET` or `POST` function directly and assert on both `response.status` and `await response.json()`.
|
||||
- For pure helpers and schemas, call the function directly and assert return values or thrown errors, as in `src/lib/auth.test.ts` and `src/lib/validations.test.ts`.
|
||||
- Use one top-level `describe(...)` block per module or endpoint, with behavior-based test names such as `returns 401 for wrong API key` in `src/app/api/tags/maintenance/route.test.ts` and `returns 500 when prisma query fails` in `src/app/api/tags/route.test.ts`.
|
||||
- Build local request helpers for route tests, such as `buildRequest(...)` in `src/app/api/tags/maintenance/route.test.ts` and `src/app/api/tags/reset-projects/route.test.ts`.
|
||||
- Use inline fixture builders for reusable payloads, such as `buildValidPayload(...)` in `src/app/api/tags/reset-projects/route.test.ts` and `baseProjectInput` in `src/lib/validations.test.ts`.
|
||||
- Reset mocks and environment state in `beforeEach` or `afterEach`, as seen in `src/app/api/tags/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, and `src/lib/prisma-url.test.ts`.
|
||||
|
||||
## Mocking
|
||||
|
||||
**Framework:**
|
||||
- Vitest mocking via `vi.mock`, `vi.fn`, `vi.hoisted`, `mockResolvedValue`, and `mockRejectedValue`.
|
||||
- Use Vitest mocks via `vi.fn`, `vi.mock`, `vi.hoisted`, `mockResolvedValue`, and `mockRejectedValue`.
|
||||
|
||||
**Patterns:**
|
||||
```typescript
|
||||
const { findManyMock } = vi.hoisted(() => ({
|
||||
findManyMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/prisma", () => ({
|
||||
prisma: {
|
||||
tag: {
|
||||
@@ -111,54 +93,26 @@ vi.mock("@/lib/prisma", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
findManyMock.mockResolvedValue([
|
||||
{
|
||||
id: "tag-1",
|
||||
name: "机器学习",
|
||||
slug: "machine-learning",
|
||||
_count: { projects: 4 },
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
```typescript
|
||||
vi.mock("next/cache", () => ({
|
||||
revalidatePath: revalidatePathMock,
|
||||
}));
|
||||
```
|
||||
- Hoist shared mocks before module import so route modules capture mocked dependencies, as in `src/app/api/tags/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, and `src/app/api/tags/reset-projects/route.test.ts`.
|
||||
- Mock Prisma reads and writes rather than using a real database in route tests.
|
||||
- Mock `next/cache` side effects when mutation routes call `revalidatePath`, as in `src/app/api/tags/maintenance/route.test.ts` and `src/app/api/tags/reset-projects/route.test.ts`.
|
||||
|
||||
**What to Mock:**
|
||||
- Mock Prisma client calls for route and service tests instead of hitting a real database. This is the established pattern in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`.
|
||||
- Mock Next.js side effects such as `revalidatePath` when testing mutation endpoints, as in `src/app/api/tags/reset-projects/route.test.ts` and `src/app/api/tags/maintenance/route.test.ts`.
|
||||
- Set environment variables inline per suite when auth behavior depends on them, as in `process.env.WEBHOOK_API_KEY` usage in `src/app/api/tags/reset-projects/route.test.ts` and `src/app/api/tags/maintenance/route.test.ts`.
|
||||
- Mock Prisma modules for handler and service tests, as in `src/app/api/tags/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, and `src/app/api/tags/reset-projects/route.test.ts`.
|
||||
- Set `process.env.WEBHOOK_API_KEY` inline for auth-path tests, as in `src/app/api/tags/maintenance/route.test.ts` and `src/app/api/tags/reset-projects/route.test.ts`.
|
||||
- Mock cache revalidation side effects instead of asserting real filesystem or ISR behavior.
|
||||
|
||||
**What NOT to Mock:**
|
||||
- Do not mock pure validation or auth utilities when they can be tested directly. `src/lib/auth.test.ts` and `src/lib/validations.tag-maintenance.test.ts` exercise real implementation logic without mocks.
|
||||
- There is no current pattern for browser, React component, or DOM mocking because no component tests are checked in.
|
||||
- Test pure helpers directly without mocks when possible, as in `src/lib/auth.test.ts` and `src/lib/validations.tag-maintenance.test.ts`.
|
||||
- There is no current pattern for DOM, browser, or component mocking because no component tests are checked in.
|
||||
|
||||
## Fixtures and Helpers
|
||||
## Fixtures and Environment Handling
|
||||
|
||||
**Test Data:**
|
||||
```typescript
|
||||
function buildValidPayload(apiKey: string) {
|
||||
return {
|
||||
apiKey,
|
||||
projects: [
|
||||
{
|
||||
projectSlug: "project-one",
|
||||
selectedTagSlugsByCategory: {
|
||||
FIXED_PROJECT_TYPE: ["agent-tooling"],
|
||||
TECH_STACK: ["typescript"],
|
||||
AI_PARADIGM: ["ai-agents"],
|
||||
PRODUCT_FORM: ["web-application"],
|
||||
DOMAIN_SCENARIO: ["code-dev"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
const baseProjectInput = {
|
||||
name: "Agent Park",
|
||||
@@ -167,16 +121,24 @@ const baseProjectInput = {
|
||||
links: [{ type: "GITHUB" as const, url: "https://github.com/example/repo" }],
|
||||
};
|
||||
```
|
||||
|
||||
**Location:**
|
||||
- Builders and fixtures are defined inline in each test file. No shared `test-utils`, factory module, or fixture directory was detected under `src` or repository root.
|
||||
- Reuse small local helpers such as `buildRequest`, `buildValidPayload`, `createTxMock`, and `baseProjectInput` instead of creating cross-suite abstractions.
|
||||
```typescript
|
||||
const ENV_KEYS = [
|
||||
"DATABASE_URL",
|
||||
"PG_SSL_ROOT_CERT_B64",
|
||||
"PG_SSL_IDENTITY_P12_B64",
|
||||
"PG_SSL_IDENTITY_PASSWORD",
|
||||
"PG_SSL_MODE",
|
||||
"PG_SSL_CERT_DIR",
|
||||
] as const;
|
||||
```
|
||||
- `src/lib/prisma-url.test.ts` snapshots selected env vars and restores them in `afterEach`, which is the current pattern for tests that mutate `process.env`.
|
||||
- Fixtures are inline per file. No shared `test-utils` or factory directory exists.
|
||||
|
||||
## Coverage
|
||||
|
||||
**Requirements:**
|
||||
- No coverage thresholds or coverage command are configured in `package.json` or `vitest.config.ts`.
|
||||
- No CI coverage upload or report configuration was detected.
|
||||
- No CI workflow, coverage upload, or Playwright setup was detected under `.github/` or repository root.
|
||||
|
||||
**View Coverage:**
|
||||
```bash
|
||||
@@ -186,23 +148,26 @@ Not configured
|
||||
## Test Types
|
||||
|
||||
**Unit Tests:**
|
||||
- Pure utility tests cover security and schema logic in `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/lib/validations.tag-maintenance.test.ts`.
|
||||
- Service-level unit tests cover tag maintenance merge behavior in `src/app/api/tags/maintenance/service.test.ts`.
|
||||
- Pure utility coverage exists for `src/lib/auth.ts`, `src/lib/prisma-url.ts`, `src/lib/validations.ts`, and tag-maintenance schema rules in `src/lib/validations.tag-maintenance.test.ts`.
|
||||
|
||||
**Service Tests:**
|
||||
- Business logic in `src/app/api/tags/maintenance/service.ts` is tested separately in `src/app/api/tags/maintenance/service.test.ts`, including deduped tag migration and validation failure behavior.
|
||||
|
||||
**Route-Level Tests:**
|
||||
- Route handler tests call Next.js App Router handlers directly with mocked dependencies in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`.
|
||||
- These are closer to isolated handler tests than full integration tests because Prisma and cache modules are mocked.
|
||||
- Direct route-handler tests exist for `src/app/api/tags/route.ts`, `src/app/api/tags/maintenance/route.ts`, and `src/app/api/tags/reset-projects/route.ts`.
|
||||
- These tests assert status codes, JSON bodies, auth failures, validation failures, and `revalidatePath(...)` side effects.
|
||||
|
||||
**Integration Tests:**
|
||||
- Not detected. No test currently exercises a real Prisma client, live database, or full Next.js server.
|
||||
**n8n-Related Coverage:**
|
||||
- `src/app/api/search/ai/route.ts` has no corresponding `src/app/api/search/ai/route.test.ts`.
|
||||
- `src/app/api/webhook/signals/route.ts` has no corresponding `src/app/api/webhook/signals/route.test.ts`.
|
||||
- This means the n8n-facing search proxy and the authenticated signals webhook currently rely on runtime behavior rather than automated handler tests.
|
||||
|
||||
**E2E Tests:**
|
||||
- Not detected in the repository. No `e2e/` directory and no `playwright.config.*` file were found on 2026-04-18.
|
||||
- Repository guidance mentions `pnpm test:e2e`, but `package.json` does not define that script. Treat E2E support as undocumented or not yet checked in.
|
||||
- Not detected. `AGENTS.md` reserves `e2e/` for Playwright, but there is no `e2e/` directory, no Playwright config, and no `pnpm test:e2e` script in `package.json`.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Async Testing:**
|
||||
**Async Route Testing:**
|
||||
```typescript
|
||||
const response = await POST(buildRequest(payload));
|
||||
const json = await response.json();
|
||||
@@ -210,11 +175,9 @@ const json = await response.json();
|
||||
expect(response.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
```
|
||||
|
||||
**Error Testing:**
|
||||
```typescript
|
||||
findManyMock.mockRejectedValue(new Error("db unavailable"));
|
||||
|
||||
const response = await GET();
|
||||
const json = await response.json();
|
||||
|
||||
@@ -223,45 +186,28 @@ expect(json.error).toBe("Internal server error");
|
||||
expect(json.details).toContain("db unavailable");
|
||||
```
|
||||
|
||||
```typescript
|
||||
await expect(
|
||||
executeTagMaintenance(tx as Parameters<typeof executeTagMaintenance>[0], {
|
||||
updates: [{ tagId: "missing-tag", nameEn: "Missing" }],
|
||||
merges: [],
|
||||
})
|
||||
).rejects.toMatchObject({
|
||||
status: 400,
|
||||
message: "Validation error",
|
||||
});
|
||||
```
|
||||
|
||||
## Current Gaps
|
||||
|
||||
**Untested Server Modules:**
|
||||
- No tests were detected for `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`, or `src/app/api/webhook/signals/route.ts`.
|
||||
- No tests were detected for `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, `src/lib/tag-taxonomy.ts`, or `src/lib/github/badges.ts`.
|
||||
**Untested API Routes:**
|
||||
- No tests were detected for `src/app/api/projects/route.ts`, `src/app/api/projects/[slug]/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/search/ai/route.ts`, or `src/app/api/webhook/signals/route.ts`.
|
||||
- The missing n8n-related tests are especially important because `src/app/api/search/ai/route.ts` depends on the `N8N_AI_SEARCH_WEBHOOK` contract and `src/app/api/webhook/signals/route.ts` handles authenticated ingestion plus Prisma upserts.
|
||||
|
||||
**Untested UI:**
|
||||
- No component tests were detected for large client components such as `src/app/[locale]/projects/ProjectsResultsClient.tsx` (784 lines), `src/components/project/TagFilterPanel.tsx` (698 lines), or `src/components/signals/SignalFeedClient.tsx` (603 lines).
|
||||
- No tests were detected for route pages under `src/app/[locale]`.
|
||||
**Untested Query and UI Modules:**
|
||||
- No tests were detected for `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/github/badges.ts`, or large client components under `src/components` and `src/app/[locale]`.
|
||||
|
||||
**Test Infrastructure Gaps:**
|
||||
- No shared test helpers, factories, or fixture libraries are present.
|
||||
- No browser or DOM test environment is configured.
|
||||
- No E2E harness or Playwright configuration is present.
|
||||
- No CI workflow files were detected under `.github/`.
|
||||
**Infrastructure Gaps:**
|
||||
- No shared fixture library, no DOM test environment, no Playwright harness, and no CI workflow files were detected.
|
||||
|
||||
## Verification Signals
|
||||
|
||||
**Current Quality Checks:**
|
||||
- `pnpm test` passed on 2026-04-18 with 7 test files and 24 tests passing.
|
||||
- `pnpm lint` passed on 2026-04-18 with no warnings or errors.
|
||||
- `pnpm build` passed on 2026-04-18. The build completed static generation and route analysis successfully.
|
||||
- `pnpm test` passed on 2026-04-20 with 8 test files and 26 tests.
|
||||
- `pnpm lint` passed on 2026-04-20 with no warnings or errors.
|
||||
|
||||
**Observed Test Output Notes:**
|
||||
- `pnpm test` prints expected stderr from the error-path test in `src/app/api/tags/route.test.ts` because `src/app/api/tags/route.ts` logs with `console.error`.
|
||||
- Vitest emitted a Vite deprecation notice about the CJS Node API during the run. This is a tooling signal, not a failing test.
|
||||
**Observed Output Notes:**
|
||||
- `pnpm test` prints expected stderr from the deliberate error-path assertion in `src/app/api/tags/route.test.ts` because `src/app/api/tags/route.ts` logs failures with `console.error`.
|
||||
- The current Vitest run emits a Vite deprecation notice about the CJS Node API. This is a tooling signal, not a failing test.
|
||||
|
||||
---
|
||||
|
||||
*Testing analysis: 2026-04-18*
|
||||
*Testing analysis: 2026-04-20*
|
||||
|
||||
Reference in New Issue
Block a user