chore: init pnpm monorepo workspace

Root config: pnpm workspaces, tsconfig base, docker compose (postgres, redis, minio), .npmrc.
This commit is contained in:
Arnaud
2026-07-01 08:08:29 +02:00
commit ae7c5d943e
10 changed files with 9273 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# Dependencies
node_modules/
.pnpm-store/
# Next.js
.next/
out/
.vercel/
# Build outputs
dist/
build/
# Environment
.env
.env.local
.env.*.local
.env.production
# Drizzle snapshots
packages/db/src/migrations/meta/*.snap
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
pnpm-debug.log*
# TypeScript
*.tsbuildinfo
# Editor
.vscode/
.idea/
*.swp
*.swo
# Test coverage
coverage/
# Misc
.claude/
project_epicure.md
PLAN.md
+2
View File
@@ -0,0 +1,2 @@
approve-builds[]=sharp
approve-builds[]=unrs-resolver
+73
View File
@@ -0,0 +1,73 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Dev Setup
```bash
cp .env.example .env.local # fill in secrets (lives at repo root)
ln -s ../../.env.local apps/web/.env.local # Next.js reads from apps/web/, not root
docker compose -f docker/compose.yml up -d # start postgres, redis, minio
pnpm install
pnpm db:generate # generate migrations from schema (first time)
pnpm db:migrate # apply migrations
pnpm db:seed # seed tier definitions
pnpm dev # start Next.js on :3000
```
MinIO console: http://localhost:9001 (minioadmin / minioadmin)
## Key Commands
```bash
pnpm dev # run web app
pnpm build # production build
pnpm lint # lint all packages
pnpm typecheck # type-check all packages
pnpm db:generate # generate Drizzle migration from schema changes
pnpm db:migrate # apply migrations
pnpm db:seed # seed tier definitions
pnpm db:studio # open Drizzle Studio
```
## Architecture
**Monorepo** (pnpm workspaces):
- `apps/web` — Next.js 15 App Router app (`@epicure/web`)
- `packages/db` — Drizzle ORM schema + client (`@epicure/db`)
- `packages/api-types` — Zod schemas, single source of truth for API types + OpenAPI (`@epicure/api-types`)
**Route groups in `apps/web/app/`**:
- `(auth)/` — login, signup, verify-email (no auth required)
- `(app)/` — main app shell with nav (auth required)
- `admin/` — admin-only, role checked in layout server component
- `api/v1/` — REST API endpoints
- `api/auth/[...all]/` — Better Auth handler
**Auth**: Better Auth with Drizzle adapter. Server: `lib/auth/server.ts`. Client: `lib/auth/client.ts`. Middleware at `middleware.ts` guards all routes.
**DB**: Drizzle ORM on Postgres. Schema in `packages/db/src/schema/` split by domain: `users`, `recipes`, `social`, `meal-planning`, `tiers`. Import from `@epicure/db`.
**AI** (Phase 3): Vercel AI SDK provider factory in `apps/web/lib/ai/`. All AI outputs use `generateObject` + Zod schemas — no free-text parsing.
**Storage**: S3-compatible via `STORAGE_*` env vars. MinIO locally, any S3-compatible provider in prod.
**Tier limits**: `lib/tiers.ts` exports `checkTierLimit(userId, tier, key)` — call before recipe create and AI calls. Throws `TierLimitError` on breach.
## Adding a shadcn/ui Component
```bash
cd apps/web && pnpm dlx shadcn@latest add <component-name>
```
## Schema Changes
1. Edit `packages/db/src/schema/*.ts`
2. `pnpm db:generate` — creates migration file
3. `pnpm db:migrate` — applies it
4. Update corresponding Zod schemas in `packages/api-types/src/`
## Environment Variables
See `.env.example`. Required for dev: `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`. AI keys optional until Phase 3.
+3
View File
@@ -0,0 +1,3 @@
{$DOMAIN} {
reverse_proxy web:3000
}
+77
View File
@@ -0,0 +1,77 @@
services:
postgres:
image: postgres:17-alpine
restart: always
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: always
volumes:
- redis_data:/data
command: redis-server --save 60 1 --requirepass ${REDIS_PASSWORD}
minio:
image: minio/minio:latest
restart: always
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
web:
image: epicure-web:latest
restart: always
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
STORAGE_ENDPOINT: http://minio:9000
STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER}
STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
STORAGE_BUCKET: epicure-uploads
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BETTER_AUTH_URL: ${BETTER_AUTH_URL}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
minio:
condition: service_started
caddy:
image: caddy:2-alpine
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- web
volumes:
postgres_data:
redis_data:
minio_data:
caddy_data:
caddy_config:
+67
View File
@@ -0,0 +1,67 @@
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: epicure
POSTGRES_USER: epicure
POSTGRES_PASSWORD: epicure
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U epicure"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --save 60 1
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
minio:
image: minio/minio:latest
restart: unless-stopped
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 5
minio-init:
image: minio/mc:latest
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set local http://minio:9000 minioadmin minioadmin;
mc mb --ignore-existing local/epicure-uploads;
mc anonymous set download local/epicure-uploads;
exit 0;
"
volumes:
postgres_data:
redis_data:
minio_data:
+22
View File
@@ -0,0 +1,22 @@
{
"name": "epicure",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "pnpm --filter web dev",
"build": "pnpm --filter web build",
"lint": "pnpm -r lint",
"typecheck": "pnpm -r typecheck",
"db:generate": "pnpm --filter @epicure/db generate",
"db:migrate": "pnpm --filter @epicure/db migrate",
"db:seed": "pnpm --filter @epicure/db seed",
"db:studio": "pnpm --filter @epicure/db studio"
},
"devDependencies": {
"typescript": "^5.8.3"
},
"engines": {
"node": ">=22",
"pnpm": ">=11"
}
}
+8958
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
packages:
- "apps/*"
- "packages/*"
allowBuilds:
'@parcel/watcher': true
'@swc/core': true
esbuild: true
sharp: true
unrs-resolver: true
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true,
"resolveJsonModule": true
}
}