feat: full plan execution - CI/CD, critical fixes, UX polish, secondary/advanced features, E2E + docs
Phase 0 (CI/CD): fix root typecheck to cover api+worker+web; reconcile migration story into idempotent db:migrate (db:sync + db:triggers); add Gitea Actions quality/deploy/smoke workflow; rewrite README/AGENTS/DEPLOY docs; add requireWorkspaceAccess + recordActivityForEntity conventions. Phase 1 (critical fixes): calendar delete + drag/resize DnD; canvas card CRUD + bulk save + debounced autosave; logout route; graph edge workspaceId derivation; real analytics endpoints (drop Math.random); task board droppable columns + reorder persistence; Tiptap notes editor with sanitized HTML rendering; remove insecure passkey auth; domain/owner scoping (IDOR) on all by-ID routes + search/ export/realtime scoping; command palette routing + agent mention fetch; agent activity SSE handler; graph fly-to with tracked positions. Phase 2 (UX polish): login on design system; Sonner toasts app-wide; shared Loading/Empty/Error state components; working density/sidebarPos/reduce-motion settings; Inter typography; consolidated status-colors lib; unified detail routes; dashboard sort/realtime/responsive fixes; mobile responsive; a11y (radiogroups, sanitized snippets, badge labels). Phase 3 (features): daily notes timezone fix + delete + autosave + mood/energy create; active-domain store + topbar picker; graph domain picker + navigable entity links; tag assign/remove UI + server-side tag filter; real CSV export + import validation; custom fields on tasks. Phase 4 (advanced): migrate job worker into apps/worker (webhook delivery with HMAC, recurring spawn, ai_dispatch disabled); webhook queue helper + entity event enqueuing + test endpoint fix; recurring scheduledJobs pipeline; agents CRUD + permission editing + activity filters; real notifications feed; MCP polish (validation, error codes, domain scoping, dead sql leftover). Phase 5 (E2E + docs): rewrite Playwright suite for the Vite SPA (15 specs, new auth helpers, chromium-only in CI); add ephemeral-Postgres e2e CI job; rewrite docs/API.md for the real Hono API.
This commit is contained in:
@@ -1,39 +1,109 @@
|
||||
# Project E — Deploy Guide
|
||||
|
||||
## How to redeploy
|
||||
Production runs as a docker-compose stack on the deploy host (`10.0.0.204`). The stack has four services: PostgreSQL, the Hono API, the Vite SPA served by Caddy, and the Bun worker. Deploys are normally driven by Gitea Actions, but every step below can be run by hand.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet → :3000 (SPA container, Caddy)
|
||||
├── /api/* → api:3000 (Hono/Bun)
|
||||
├── /mcp* → api:3000 (Hono/Bun)
|
||||
└── /* → index.html (SPA fallback)
|
||||
|
||||
API (:3001, direct) → PostgreSQL (:5432)
|
||||
Worker → PostgreSQL
|
||||
```
|
||||
|
||||
Caddy (in the `spa` container) serves the built SPA and reverse-proxies `/api/*` and `/mcp*` to the `api` service. The API is also exposed directly on :3001 for debugging.
|
||||
|
||||
## Services
|
||||
|
||||
| Service | Container | Image / Build | Ports | Notes |
|
||||
|---------|-----------|---------------|-------|-------|
|
||||
| `db` | `project-e-db` | `postgres:16-alpine` | 5432 | Data in the `project-e-pg-data` volume |
|
||||
| `api` | `project-e-api` | `Dockerfile.api` | 3001 → 3000 | Hono on Bun |
|
||||
| `spa` | `project-e-spa` | `Dockerfile.spa` | 3000 → 80 | Built Vite SPA + Caddy |
|
||||
| `worker` | `project-e-worker` | `Dockerfile.worker` | none | Bun worker |
|
||||
|
||||
All services share the `project-e-network` bridge and restart unless stopped.
|
||||
|
||||
## CI/CD pipeline (Gitea Actions)
|
||||
|
||||
The workflow lives at `.gitea/workflows/ci.yml` and runs on the self-hosted runner `projecte-runner`, which is colocated with the deploy host.
|
||||
|
||||
- **`quality`** — runs on every push and pull request: `bun install --frozen-lockfile` → `bun run typecheck` → web build (`cd apps/web && bun run build`) → `docker compose build`. A failed quality gate blocks the deploy job.
|
||||
- **`deploy`** — runs on pushes to `main` and on `workflow_dispatch`. It checks out the code and runs `bash script/deploy.sh` with `DEPLOY_DIR` defaulting to `/home/projecte/ProjectE`.
|
||||
- **`smoke`** — runs after `deploy` (also on deploy failure): API health at `http://localhost:3000/api/health`, SPA root returns HTML, and a login POST to `/api/auth/credentials` using `INITIAL_ADMIN_EMAIL`/`INITIAL_ADMIN_PASSWORD` from the host `.env`.
|
||||
|
||||
### What `script/deploy.sh` does
|
||||
|
||||
Idempotent, safe to re-run. It:
|
||||
|
||||
1. Syncs the CI checkout into `DEPLOY_DIR` (rsync, excluding `.git`, `node_modules`, build artifacts, and `.env`)
|
||||
2. Loads secrets from the host `.env` (never overwrites it)
|
||||
3. Installs dependencies with `bun install --frozen-lockfile`
|
||||
4. Runs `bun run db:migrate`
|
||||
5. Runs `docker compose build` and `docker compose up -d`
|
||||
6. Waits up to 60s for `http://localhost:3000/api/health` to return HTTP 200, dumping recent API logs if it times out
|
||||
|
||||
## Secrets
|
||||
|
||||
Secrets live in the host `.env` at `/home/projecte/ProjectE/.env`. This file is gitignored; never commit it. To set it up:
|
||||
|
||||
```bash
|
||||
cd ~/ProjectE
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Fill in `POSTGRES_PASSWORD`, `DATABASE_URL`, `AUTH_SECRET` (or `NEXTAUTH_SECRET`), `INITIAL_ADMIN_EMAIL`, `INITIAL_ADMIN_PASSWORD`, and, as needed, `NODE_ENV`, `PUBLIC_URL`, `COOKIE_SECURE`, and `ALLOWED_HOSTS`. `deploy.sh` sources it, and docker-compose reads the `POSTGRES_PASSWORD` and `DATABASE_URL` values from it.
|
||||
|
||||
## Manual deploy
|
||||
|
||||
On the deploy host:
|
||||
|
||||
```bash
|
||||
cd /home/projecte/ProjectE
|
||||
|
||||
# Pull latest
|
||||
git pull origin redesign/ui-v2
|
||||
git pull origin main
|
||||
|
||||
# Rebuild images
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Apply schema + triggers (idempotent)
|
||||
bun run db:migrate
|
||||
|
||||
# Build images
|
||||
docker compose build
|
||||
|
||||
# Restart stack
|
||||
# Restart the stack
|
||||
docker compose up -d
|
||||
|
||||
# Wait for API health
|
||||
until curl -s http://localhost:3000/api/health | grep -q '"status"'; do sleep 2; done
|
||||
|
||||
# Check status
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
## How to roll back
|
||||
## Database migrations
|
||||
|
||||
If the new stack fails:
|
||||
`bun run db:migrate` chains two idempotent steps:
|
||||
|
||||
```bash
|
||||
cd ~/ProjectE
|
||||
- `db:sync` — `drizzle-kit push --force`, which syncs the schema in `packages/db/src/schema.ts` to the database
|
||||
- `db:triggers` — `script/apply-triggers.ts`, which applies the search-vector triggers from `drizzle/0005_search_vector_trigger.sql` (`CREATE OR REPLACE FUNCTION` + `DROP TRIGGER IF EXISTS`)
|
||||
|
||||
# Stop the new stack
|
||||
docker compose down
|
||||
Both are safe to run on every deploy. Schema changes go through `bun run db:generate` in development, then land in `drizzle/` before the next deploy.
|
||||
|
||||
# Restart the old worker (Node) from the legacy compose
|
||||
# (The old compose file is preserved in git history)
|
||||
# docker compose -f docker-compose.legacy.yml up -d worker
|
||||
```
|
||||
## Rollback
|
||||
|
||||
## How to view logs
|
||||
Compose images are rebuilt from the checkout, so there are no pinned image tags to restore. To roll back a bad release:
|
||||
|
||||
1. Revert the checkout to the previous good commit: `git revert <sha>` (or `git checkout <sha>`) and push to `main`
|
||||
2. Re-run the manual deploy steps (`docker compose build`, `docker compose up -d`)
|
||||
|
||||
The `project-e-pg-data` volume is untouched by deploys and rollbacks, so the database survives both. If a deploy failed, `deploy.sh` exits non-zero with the recent API logs; do not force it past a failing health check.
|
||||
|
||||
## Logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
@@ -46,174 +116,56 @@ docker compose logs --tail=50 -f worker
|
||||
docker compose logs --tail=50 -f db
|
||||
```
|
||||
|
||||
## How to debug
|
||||
## Debugging
|
||||
|
||||
### API health (direct, :3001)
|
||||
|
||||
### API health check
|
||||
```bash
|
||||
curl http://localhost:3001/api/health
|
||||
```
|
||||
|
||||
### SPA health check
|
||||
```bash
|
||||
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/
|
||||
```
|
||||
Returns `{"status":"ok", ...}` with a database ping (`database.connected`, `database.ping_ms`).
|
||||
|
||||
### API health (through Caddy)
|
||||
|
||||
### API through reverse proxy
|
||||
```bash
|
||||
curl http://localhost:3000/api/health
|
||||
```
|
||||
|
||||
### Login test
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/credentials \
|
||||
-H "Content-Type: application/json" \
|
||||
-d email:user@example.com | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get(token,))")
|
||||
echo "Token: $TOKEN"
|
||||
```
|
||||
|
||||
### Check container health
|
||||
```bash
|
||||
docker inspect project-e-db --format {{.State.Health.Status}}
|
||||
```
|
||||
|
||||
### Restart a single service
|
||||
```bash
|
||||
docker compose restart api
|
||||
docker compose restart spa
|
||||
```
|
||||
|
||||
### Rebuild a single service
|
||||
```bash
|
||||
docker compose build spa
|
||||
docker compose up -d --force-recreate spa
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet → :3000 → Caddy (SPA container)
|
||||
├── /api/* → api:3000 (Hono/Bun)
|
||||
├── /mcp* → api:3000 (Hono/Bun)
|
||||
└── /* → index.html (SPA fallback)
|
||||
|
||||
API (:3001, direct) → PostgreSQL (:5432)
|
||||
Worker → PostgreSQL
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
- The `project-e-pg-data` Docker volume contains the live database. **Do not delete it.**
|
||||
- The `apps/web-legacy/` directory contains the old Next.js app for reference. **Do not delete it.**
|
||||
- Port 3000 is the SPA (Caddy), port 3001 is the API directly (for debugging).
|
||||
- The MCP endpoint requires a valid API key (separate from JWT auth).
|
||||
EOF cd ~/ProjectE && cat > DEPLOY.md << 'EOF'
|
||||
# Project E — Deploy Guide
|
||||
|
||||
## How to redeploy
|
||||
|
||||
```bash
|
||||
cd ~/ProjectE
|
||||
|
||||
# Pull latest
|
||||
git pull origin redesign/ui-v2
|
||||
|
||||
# Rebuild images
|
||||
docker compose build
|
||||
|
||||
# Restart stack
|
||||
docker compose up -d
|
||||
|
||||
# Check status
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
## How to roll back
|
||||
|
||||
If the new stack fails:
|
||||
|
||||
```bash
|
||||
cd ~/ProjectE
|
||||
|
||||
# Stop the new stack
|
||||
docker compose down
|
||||
|
||||
# Restart the old worker (Node) from the legacy compose
|
||||
# (The old compose file is preserved in git history)
|
||||
# docker compose -f docker-compose.legacy.yml up -d worker
|
||||
```
|
||||
|
||||
## How to view logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
docker compose logs --tail=50 -f
|
||||
|
||||
# Specific service
|
||||
docker compose logs --tail=50 -f api
|
||||
docker compose logs --tail=50 -f spa
|
||||
docker compose logs --tail=50 -f worker
|
||||
docker compose logs --tail=50 -f db
|
||||
```
|
||||
|
||||
## How to debug
|
||||
|
||||
### API health check
|
||||
```bash
|
||||
curl http://localhost:3001/api/health
|
||||
```
|
||||
|
||||
### SPA health check
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/
|
||||
```
|
||||
|
||||
### API through reverse proxy
|
||||
```bash
|
||||
curl http://localhost:3000/api/health
|
||||
```
|
||||
|
||||
### Login test
|
||||
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/credentials \
|
||||
curl -s -X POST http://localhost:3000/api/auth/credentials \
|
||||
-H "Content-Type: application/json" \
|
||||
-d password:<password> | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get(token,))")
|
||||
echo "Token: $TOKEN"
|
||||
-d '{"email":"<INITIAL_ADMIN_EMAIL>","password":"<INITIAL_ADMIN_PASSWORD>"}'
|
||||
```
|
||||
|
||||
### Check container health
|
||||
Expect HTTP 200 and a `session` cookie.
|
||||
|
||||
### Container health
|
||||
|
||||
```bash
|
||||
docker inspect project-e-db --format {{.State.Health.Status}}
|
||||
docker inspect project-e-db --format '{{.State.Health.Status}}'
|
||||
```
|
||||
|
||||
### Restart a single service
|
||||
### Restart or rebuild one service
|
||||
|
||||
```bash
|
||||
docker compose restart api
|
||||
docker compose restart spa
|
||||
```
|
||||
|
||||
### Rebuild a single service
|
||||
```bash
|
||||
docker compose build spa
|
||||
docker compose up -d --force-recreate spa
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet → :3000 → Caddy (SPA container)
|
||||
├── /api/* → api:3000 (Hono/Bun)
|
||||
├── /mcp* → api:3000 (Hono/Bun)
|
||||
└── /* → index.html (SPA fallback)
|
||||
|
||||
API (:3001, direct) → PostgreSQL (:5432)
|
||||
Worker → PostgreSQL
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
- The `project-e-pg-data` Docker volume contains the live database. **Do not delete it.**
|
||||
- The `apps/web-legacy/` directory contains the old Next.js app for reference. **Do not delete it.**
|
||||
- Port 3000 is the SPA (Caddy), port 3001 is the API directly (for debugging).
|
||||
- The `project-e-pg-data` Docker volume contains the live database. **Do not delete it.** Back it up (volume snapshot or `pg_dump`) before major schema work.
|
||||
- Port 3000 is the SPA (Caddy); port 3001 is the API directly (for debugging).
|
||||
- The MCP endpoint requires a valid API key (separate from JWT auth).
|
||||
- The root `worker/` directory is legacy. The active worker is `apps/worker`.
|
||||
|
||||
Reference in New Issue
Block a user