Add a prepare script that runs 'git config core.hooksPath .githooks' on every npm install, so the commit-msg style gate and pre-push test gate turn on automatically per clone with nothing to remember. Guarded with '|| true' so installs never fail where git is absent (e.g. tarball deploys). Document that the deploy server never needs it since it only pulls.
157 lines
8.5 KiB
Markdown
157 lines
8.5 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# Development
|
|
npm run dev # Start Next.js dev server
|
|
npm run build # Build for production (--webpack flag applied in package.json)
|
|
npm run lint # Run ESLint
|
|
npm run test # Run all tests with Vitest
|
|
npx vitest run tests/some-file.test.ts # Run a single test file
|
|
|
|
# Database (Drizzle ORM + drizzle-kit)
|
|
npm run db:generate # Generate a migration from schema changes
|
|
npm run db:migrate # Apply migrations
|
|
npm run db:push # Push schema to DB directly (dev shortcut, no migration)
|
|
npm run db:studio # Open Drizzle Studio to browse the database
|
|
```
|
|
|
|
### Docker / task runner (see Makefile)
|
|
|
|
```bash
|
|
make start # Start DB container + dev server (http://localhost:3014)
|
|
make stop # Stop containers
|
|
make db-up # Start only the database container
|
|
make migrate # Apply Drizzle migrations
|
|
make studio # Open Drizzle Studio
|
|
make psql # Open a psql shell on the database
|
|
make test # Run the whole test suite + copy-paste summary
|
|
make deploy # (on server) Pull + rebuild + restart
|
|
make health # Hit /api/health via public URL
|
|
```
|
|
|
|
## Architecture
|
|
|
|
This is a multilingual Next.js (App Router) portfolio site with an admin workspace. Stack: TypeScript, next-intl, Drizzle ORM + PostgreSQL, Tailwind CSS, Radix UI, framer-motion, nodemailer.
|
|
|
|
### Routing overview
|
|
|
|
There are two applications sharing one Next.js instance:
|
|
|
|
**Public site** — `app/[locale]/(site)/`
|
|
Localized routes for `de`, `en`, `ar`. Default locale is dynamic (stored in `AppConfig`), read at request time via `/api/site/default-locale`. Maintenance mode redirects visitors to `/coming-soon`.
|
|
|
|
**Admin workspace** — `app/_admin/` (canonical source)
|
|
Accessed via a dedicated subdomain (`root.mohfarawati.de`) in production, or via the `/root` path prefix in development. The middleware rewrites both to `app/admin-internal/`. The `app/root/` and `app/admin-internal/` directories mirror `app/_admin/` — treat `app/_admin/` as the source of truth.
|
|
|
|
The full routing rewrite logic lives in `lib/admin-routing.ts` and `proxy.ts` (the Next.js 16 middleware entry point — this project has no `middleware.ts`).
|
|
|
|
### i18n
|
|
|
|
- Locales: `de`, `en`, `ar` — defined in `i18n/routing.ts`
|
|
- Default locale is configurable at runtime via `AppConfig` (key: `default_locale`)
|
|
- `localePrefix: "as-needed"` — default locale has no prefix in URLs
|
|
- No locale cookie or browser detection; locale is set explicitly by user
|
|
- Translation messages live in `messages/{locale}.json`
|
|
|
|
### Persistence
|
|
|
|
The Drizzle client is in `lib/db/index.ts` (postgres.js driver); the schema is in `lib/db/schema.ts` and DB enums in `lib/db/enums.ts`. All DB access must go through server-side modules in `lib/`. Client components must never access the database.
|
|
|
|
`AppConfig` is a key-value table used for all runtime configuration: site settings, SMTP, marquee, maintenance mode, default locale. `lib/app-config.ts` is the aggregate entry point; individual settings are in `lib/site-settings.ts`, `lib/mail-settings.ts`, `lib/marquee-settings.ts`.
|
|
|
|
### Module boundaries
|
|
|
|
- `lib/*` — server-side application logic (queries, services, config)
|
|
- `components/ui/` — shared Radix UI primitives (design system base)
|
|
- `components/layout/`, `components/site/` — public site UI
|
|
- `components/admin/`, `components/dashboard/` — admin UI
|
|
- Server actions (`actions.ts` files in page directories) are the entry points for form submissions; they call `lib/*` modules
|
|
- Business logic must not live inside UI components
|
|
|
|
### Key canonical files
|
|
|
|
| Concern | File |
|
|
|---|---|
|
|
| i18n routing | `i18n/routing.ts` |
|
|
| Admin routing logic | `lib/admin-routing.ts` |
|
|
| Middleware (routing + auth) | `proxy.ts` |
|
|
| DB client (Drizzle) | `lib/db/index.ts` |
|
|
| DB schema | `lib/db/schema.ts` |
|
|
| AppConfig aggregate | `lib/app-config.ts` |
|
|
| Portfolio queries | `lib/portfolio.ts` |
|
|
| Media handling | `lib/media.ts` |
|
|
| Contact flow | `lib/mail.ts` |
|
|
|
|
### Documentation to read by task scope
|
|
|
|
- **Small UI/copy/style fixes**: read only the relevant files
|
|
- **Feature changes**: read `specs/<feature>.md` + `docs/ARCHITECTURE.md` if structure is affected
|
|
- **Cross-cutting/architecture changes**: read `docs/ARCHITECTURE.md`, `docs/DOMAIN_RULES.md`, `docs/FEATURES.md`, and the relevant `specs/` file
|
|
|
|
Update `docs/` and `specs/` only when the change affects feature scope, business rules, architecture, or public behavior.
|
|
|
|
## Environment variables
|
|
|
|
Key variables (see `.env.example` for full list):
|
|
|
|
```
|
|
DATABASE_URL PostgreSQL connection string
|
|
NEXT_PUBLIC_SITE_URL Public site URL
|
|
NEXT_PUBLIC_ADMIN_URL Admin subdomain URL
|
|
ADMIN_HOST Admin hostname (used by middleware for host-based routing)
|
|
ADMIN_PASSWORD In-app admin session password
|
|
ADMIN_AUTH_SECRET JWT/cookie secret for admin session
|
|
ADMIN_BASIC_AUTH_USER HTTP Basic Auth user (optional, adds middleware-level protection)
|
|
ADMIN_BASIC_AUTH_PASS HTTP Basic Auth password
|
|
SITE_RUNTIME_ORIGIN Internal origin for middleware to fetch runtime state (defaults to http://127.0.0.1:3000 in production)
|
|
```
|
|
|
|
## Working rules
|
|
|
|
- **Tests are mandatory for every logic change, in the SAME change.** New behaviour → new tests covering the intent (positive **and** negative cases), not one happy example. Deliberate change → update the affected tests and say which/why. A test that fails unexpectedly is a real bug → fix the code, not the test. Test the real thing — integration tests use a real (in-process PGlite) database, so do NOT mock our own `lib/`/DB layer; only true external boundaries (the admin session, third-party APIs, SMTP) may be substituted. Keep the suite green (`make test`); the `pre-push` hook (`.githooks/pre-push`) enforces it. Frontend/UI is verified manually; add component tests only for components with real logic.
|
|
- Before making any change, first explain the plan briefly and list the files that will be touched.
|
|
- Make the smallest safe change that solves the task.
|
|
- Do not modify unrelated files.
|
|
- Preserve existing architecture, naming, and folder conventions.
|
|
- Prefer server-side logic in `lib/*` and keep business logic out of UI components.
|
|
- Never access the database (Drizzle) from client components.
|
|
- For admin-related changes, treat `app/_admin/` as the canonical source of truth unless explicitly told otherwise.
|
|
- Do not add new dependencies unless absolutely necessary and explicitly justified.
|
|
- After code changes, run only the minimum relevant checks (for example: targeted test, lint on changed files, or build if necessary).
|
|
- If a task may affect routing, auth, i18n, or runtime config, inspect `proxy.ts`, `lib/admin-routing.ts`, `i18n/routing.ts`, and the relevant `lib/app-config.ts` modules first.
|
|
- For schema or database changes, inspect the Drizzle schema (`lib/db/schema.ts`), the drizzle-kit migration flow, and migration impact before editing.
|
|
- Ask before performing large refactors, file moves, destructive changes, or broad formatting changes.
|
|
- When updating behavior, also update docs/specs if the change affects public behavior, business rules, or architecture.
|
|
|
|
## Git commit messages (MANDATORY)
|
|
|
|
Every commit subject **must** follow this exact style, or the `commit-msg` hook
|
|
(`.githooks/commit-msg`) will reject the commit:
|
|
|
|
```
|
|
VERB - Short description
|
|
```
|
|
|
|
- `VERB` is an ALL-CAPS verb, at least 3 letters — e.g. `ADDED`, `FIXED`,
|
|
`STYLED`, `REMOVED`, `RENAMED`, `REFACTORED`, `IMPROVED`, `DOCUMENTED`,
|
|
`CLEANED`, `REVERTED`, `CONFIGURED`, `POLISHED`.
|
|
- Then exactly `" - "` (space, hyphen, space).
|
|
- Then a capitalized, imperative description with no trailing period.
|
|
|
|
Good: `FIXED - Load .env in drizzle.config so drizzle-kit targets the right DB`
|
|
Bad (rejected): `Flatten portfolio category routes` — no `VERB` prefix.
|
|
Bad (rejected): `feat(hero): add backdrop` — Conventional Commits is NOT used here.
|
|
|
|
Do **not** add any `Co-Authored-By` / "Generated with Claude Code" attribution
|
|
lines to commits in this repo. Write the body (when useful) as wrapped prose or
|
|
bullet points explaining the *why*, matching the existing history.
|
|
|
|
The hook (and the `pre-push` test gate) live in `.githooks/` and are activated
|
|
per clone via `core.hooksPath`. You don't need to remember this: the `prepare`
|
|
npm script runs `git config core.hooksPath .githooks` automatically on every
|
|
`npm install`. The deploy server does not need it — it only pulls, never commits
|
|
or pushes, so no hook ever fires there. Emergency skip: `git commit --no-verify`. |