Skip to content

Commit 8fb0a36

Browse files
committed
feat: continue restart plan
1 parent 4db36d8 commit 8fb0a36

18 files changed

Lines changed: 2441 additions & 40 deletions

.github/workflows/new-deepnotes-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ jobs:
3333
--health-retries 10
3434
env:
3535
DATABASE_URL: postgresql://deepnotes:deepnotes@127.0.0.1:5433/deepnotes
36+
# CREATEDB-capable catalog connection for @deepnotes/db template-clone tests (RESTART_PLAN §5.7)
37+
DATABASE_ADMIN_URL: postgresql://deepnotes:deepnotes@127.0.0.1:5433/postgres
3638
steps:
3739
- uses: actions/checkout@v4
3840

new-deepnotes/PLAN_PROGRESS.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Living checklist for the greenfield work described in [docs/RESTART_PLAN.md](../
1010

1111
| Phase | Status | Notes |
1212
|-------|--------|--------|
13-
| **0** — OpenAPI + Drizzle inventory | **In progress** | Minimal OpenAPI (`/api/health`) and Drizzle chain exist; full tRPC→REST map, `postgres-init.sql` transcription, auth env doc still open. |
13+
| **0** — OpenAPI + Drizzle inventory | **In progress** | tRPC→REST/WS map: [docs/TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md). Drizzle + migration `0000_legacy_baseline` match `postgres-init.sql` core tables. Auth/CORS/forks: [docs/AUTH_AND_CORS.md](./docs/AUTH_AND_CORS.md), [docs/CLIENT_FORKS.md](./docs/CLIENT_FORKS.md). |
1414
| **1** — Legacy repo hygiene | **Optional / n/a** | Parallel track only if still editing the old monorepo. |
15-
| **2** — Repo bootstrap | **Mostly done** | pnpm + Turbo 2, Node ≥22, Docker Postgres/Redis, `template.env`, Hono worker + Wrangler + Hyperdrive stub, Vue+Vite web shell, root CI (`new-deepnotes-ci.yml`). Missing: Pages/preview env doc, **CREATEDB** + template-DB integration tests (§5.7). |
15+
| **2** — Repo bootstrap | **Mostly done** | Template DB integration test + CI `DATABASE_ADMIN_URL`; deploy doc: [docs/DEPLOY_CLOUDFLARE.md](./docs/DEPLOY_CLOUDFLARE.md). Optional: Wrangler deploy job. |
1616
| **3** — REST + Drizzle features | **Not started** | Auth/sessions, pages/groups, realtime/collab, Stripe (no RevenueCat). |
1717
| **4** — Client MVP | **Not started** | Auth → list → page → Yjs → groups; crypto/libs port as needed. |
1818
| **5** — Cutover | **Not started** | Canary, redirect, retire `/trpc` when safe. |
@@ -21,11 +21,11 @@ Living checklist for the greenfield work described in [docs/RESTART_PLAN.md](../
2121

2222
## Phase 0 checklist (exit: OpenAPI v0 + Drizzle in repo + feature checklist)
2323

24-
- [ ] Map legacy **tRPC** procedures + **WebSocket** handlers → proposed REST/WS names (skeleton routes may return `501`).
24+
- [x] Map legacy **tRPC** procedures + **WebSocket** handlers → proposed REST/WS names (skeleton routes may return `501`).
2525
- [x] **OpenAPI** published from code (v0: health + spec endpoint); expand paths as features land.
26-
- [ ] Transcribe **`postgres-init.sql`** → Drizzle schema + follow-on migrations (bootstrap `app_meta` / `0000` is only a placeholder).
27-
- [ ] Document **cookie names**, **JWT** claims, **CORS** origins.
28-
- [ ] List **`@deepnotes/*` forks** the new client will not use (exception list with owners if any remain).
26+
- [x] Transcribe **`postgres-init.sql`** → Drizzle schema + baseline migration (`0000_legacy_baseline`: `pgcrypto`, `nanoid()`, core tables, FKs aligned with Drizzle; legacy `NOT VALID` FKs omitted for fresh installs).
27+
- [x] Document **cookie names**, **JWT** claims, **CORS** origins[docs/AUTH_AND_CORS.md](./docs/AUTH_AND_CORS.md).
28+
- [x] List **`@deepnotes/*` forks** the new client will not use (exception list with owners if any remain) → [docs/CLIENT_FORKS.md](./docs/CLIENT_FORKS.md).
2929

3030
---
3131

@@ -34,9 +34,9 @@ Living checklist for the greenfield work described in [docs/RESTART_PLAN.md](../
3434
- [x] pnpm + Turborepo 2, Node 22+.
3535
- [x] Docker Compose: Postgres + Redis (`REDIS_URL`-style in `template.env`).
3636
- [x] Cloudflare: `wrangler.toml`, Hyperdrive binding (replace placeholder `id` before prod).
37-
- [ ] Document **Pages** / preview vs production env vars; optional deploy job to CF preview.
37+
- [x] Document **Pages** / preview vs production env vars; optional deploy job to CF preview[docs/DEPLOY_CLOUDFLARE.md](./docs/DEPLOY_CLOUDFLARE.md).
3838
- [x] CI: lint, typecheck, tests, `drizzle-kit check`, build (Postgres service present for future migrate/tests).
39-
- [ ] CI: Postgres role with **CREATEDB** + **template DB** integration tests (RESTART_PLAN §5.7).
39+
- [x] CI: Postgres role with **CREATEDB** + **template DB** integration tests (RESTART_PLAN §5.7)`DATABASE_ADMIN_URL` + `src/template-db.test.ts`.
4040

4141
---
4242

@@ -46,10 +46,10 @@ Living checklist for the greenfield work described in [docs/RESTART_PLAN.md](../
4646
- [ ] Drizzle migrations from empty DB documented for production upgrades.
4747
- [ ] Cold API dev start under **2 s** (no `inspect-brk` by default) — validate on a typical laptop.
4848
- [ ] Collab + realtime: at least one integration test each (Redis + deps).
49-
- [ ] SQL-heavy paths: real Postgres tests; prefer **template DB** cloning (§5.7).
49+
- [x] SQL-heavy paths: real Postgres tests; prefer **template DB** cloning (§5.7)`@deepnotes/db` template test.
5050
- [ ] Auth, crypto, Stripe: automated coverage beyond smoke; **no** generic repository layer (§5.0).
5151
- [x] No tRPC / superjson / RevenueCat / key-rotation in **this** tree (keep absent); product sign-off for IAP/Stripe when billing ships.
52-
- [ ] Client: zero undocumented forks, or a short owned exception list.
52+
- [x] Client: zero undocumented forks, or a short owned exception list — see [docs/CLIENT_FORKS.md](./docs/CLIENT_FORKS.md).
5353
- [ ] Cloudflare: deploy runbook; Hyperdrive + Postgres + Redis proven in staging; collab/realtime topology chosen and load-tested.
5454

5555
---
@@ -58,6 +58,7 @@ Living checklist for the greenfield work described in [docs/RESTART_PLAN.md](../
5858

5959
| Date | Change |
6060
|------|--------|
61+
| 2026-04-26 | Phase 0 docs (TRPC_REST_MAP, AUTH_AND_CORS, CLIENT_FORKS); Phase 2 deploy doc; Drizzle legacy baseline from `postgres-init.sql`; Vitest template-DB integration test + CI `DATABASE_ADMIN_URL`. |
6162
| 2026-04-26 | Initial `new-deepnotes` monorepo: `@deepnotes/api`, `@deepnotes/db`, `@deepnotes/api-worker`, `@deepnotes/web`, CI workflow. |
6263

6364
Add a row here for meaningful milestones (e.g. “auth MVP”, “first Drizzle migration from legacy schema”).
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Auth cookies, JWT claims, and CORS (legacy → new stack)
2+
3+
This document captures **current legacy** behavior from `apps/app-server` so the new API can align deliberately (names may stay; paths and bodies are new per RESTART_PLAN).
4+
5+
## HTTP cookies
6+
7+
| Cookie | Purpose | Notes |
8+
|--------|---------|--------|
9+
| `accessToken` | JWT access token | `httpOnly`, `secure` when not `DEV`, `sameSite: 'strict'`, path `/` |
10+
| `refreshToken` | JWT refresh token | same defaults as access |
11+
| `loggedIn` | Client hint (`"true"`) | **`httpOnly: false`** so the SPA can branch UI; sameSite/path as above |
12+
13+
Clearing session clears all three (`src/utils/cookies.ts`).
14+
15+
## JWT signing
16+
17+
| Token | Env secret | Durations (from `@deeplib/misc`) |
18+
|-------|------------|-----------------------------------|
19+
| Access | `ACCESS_SECRET` | 30 minutes |
20+
| Refresh (short session) | `REFRESH_SECRET` | 1 hour |
21+
| Refresh (“remember me”) | `REFRESH_SECRET` | 7 days |
22+
23+
## Access token payload (`AccessTokenPayload`)
24+
25+
| Claim | Meaning |
26+
|-------|---------|
27+
| `uid` | User id (nanoid-style string) |
28+
| `sid` | Session id |
29+
30+
## Refresh token payload (`RefreshTokenPayload`)
31+
32+
| Claim | Meaning |
33+
|-------|---------|
34+
| `sid` | Session id |
35+
| `rfc` | Refresh code (server-side validation) |
36+
| `rms` | Remember-session flag |
37+
38+
## New stack env (target)
39+
40+
Use **`JWT_SECRET`** (or split **`ACCESS_SECRET`** / **`REFRESH_SECRET`**) in Wrangler secrets and local `.env`; document final names when the auth package lands. Do not ship secrets to the client bundle.
41+
42+
## CORS (legacy reference)
43+
44+
Legacy `@fastify/cors` (`apps/app-server/src/fastify/server.ts`):
45+
46+
- `credentials: true`
47+
- Allowed origins when **not** `DEV`: `process.env.CLIENT_URL`, `capacitor://deepnotes.app`, `http://localhost`, or `undefined` (non-browser)
48+
- In `DEV`, all origins allowed
49+
50+
The new stack should set an explicit allowlist for production: **web app origin** (e.g. Pages URL), **API subdomain** if distinct, and any **Capacitor** scheme you still support. Stripe dashboard webhook URL is server-to-server (no browser CORS).

new-deepnotes/docs/CLIENT_FORKS.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Legacy `@deepnotes/*` forks — new client stance
2+
3+
The greenfield SPA under `apps/web` uses **stock Vite 6 + Vue 3** with **no** workspace-scoped Quasar/Vite forks. This list records what the **legacy** client depended on so we do **not** reintroduce them by accident.
4+
5+
## Not used in `new-deepnotes` (default)
6+
7+
| Legacy package / area | Role in old client | New approach |
8+
|----------------------|-------------------|----------------|
9+
| `@deepnotes/quasar`, `@deepnotes/quasar-app-vite` | UI shell, build | Plain Vue + Vite; no Quasar |
10+
| `@deepnotes/app-server` / `AppRouter` | tRPC types | OpenAPI + `fetch` / generated client |
11+
| `superjson` | tRPC serialization | JSON + explicit schemas |
12+
| Forked `ioredis`, `html2canvas`, Tiptap collaboration cursor, `dotenv-expand` | Various | Use upstream npm unless a **documented** exception is required |
13+
14+
## Exception process
15+
16+
If a fork is unavoidable, add a row here with **owner**, **reason**, and **upgrade plan**.
17+
18+
| Package | Owner | Reason | Plan |
19+
|---------|-------|--------|------|
20+
|||||
21+
22+
Crypto and domain libraries (`@stdlib/crypto`, `@deeplib/misc`, etc.) are **separate** from Quasar/Vite forks; port only what Phase 4 needs, as shared packages or vendored modules with tests.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Cloudflare deploy — preview vs production
2+
3+
High-level runbook aligned with [docs/RESTART_PLAN.md](../../docs/RESTART_PLAN.md).
4+
5+
## Components
6+
7+
| Piece | Target | Config |
8+
|-------|--------|--------|
9+
| HTTP API | Cloudflare Workers | `apps/api-worker/wrangler.toml` |
10+
| SPA | Cloudflare Pages (or Workers static assets) | Build `apps/web` `dist/` |
11+
| Postgres | Managed Postgres (external) | **Hyperdrive** binding → same logical DB as local |
12+
| Redis | Upstash / Redis Cloud / TCP-capable provider | `REDIS_URL` (or vendor HTTP API) via secrets |
13+
14+
Replace the **Hyperdrive id placeholder** in `wrangler.toml` before production.
15+
16+
## Environment variables
17+
18+
### Production (Workers)
19+
20+
Set via **Wrangler secrets** or dashboard (never commit):
21+
22+
- Database: Hyperdrive handles pooling; app reads Hyperdrive binding, not raw remote URL in Worker code paths that should use the binding.
23+
- `JWT_SECRET` (or `ACCESS_SECRET` / `REFRESH_SECRET` if split to match legacy semantics)
24+
- `STRIPE_WEBHOOK_SECRET` when billing is wired
25+
- `REDIS_URL` or vendor-specific vars for rate limits / sessions
26+
27+
### Preview (per PR / branch)
28+
29+
Typical pattern:
30+
31+
- **Preview Worker**: separate environment in Wrangler (`env.preview`) or a second Worker name; secrets scoped to a **branch database** or read-only clone.
32+
- **Preview Pages**: branch deployments; set **environment variables** in Pages project for **public** config only (e.g. `VITE_API_URL=https://api-preview.example.com`).
33+
- **Never** put DB passwords or signing keys in `VITE_*` client variables.
34+
35+
### Local
36+
37+
See `template.env` and `docker-compose.yml`: Postgres on `5433`, Redis on `6380`, `DATABASE_URL`, optional `DATABASE_ADMIN_URL` for template DB tests.
38+
39+
## CI
40+
41+
GitHub Actions (`.github/workflows/new-deepnotes-ci.yml`) runs lint, typecheck, tests (including Postgres-backed `@deepnotes/db` tests), `drizzle-kit check`, and build. Optional follow-up: add a **deploy** job that runs `wrangler deploy` with Cloudflare API token stored as a repo secret.
42+
43+
## DNS and cookies
44+
45+
Serve the API from a stable host (e.g. `api.deepnotes.example`) and the SPA from `app.` or apex; set cookie **`Domain`** / **`Secure`** / **`SameSite`** to match that split. Document the chosen pairing in `AUTH_AND_CORS.md` when auth ships.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Legacy tRPC / WebSocket → new HTTP map
2+
3+
Working checklist for Phase 0 of [docs/RESTART_PLAN.md](../../docs/RESTART_PLAN.md). Proposed paths are **suggestions**; wire `501 Not Implemented` until handlers exist. **Out of scope** for the new product: user/group **rotate keys** (WebSocket) and **RevenueCat**.
4+
5+
## Sessions (`sessionsRouter`)
6+
7+
| Legacy procedure | Proposed REST / notes |
8+
|------------------|----------------------|
9+
| `sessions.startDemo` | `POST /api/sessions/demo` |
10+
| `sessions.login` | `POST /api/sessions/login` |
11+
| `sessions.refresh` | `POST /api/sessions/refresh` |
12+
| `sessions.logout` | `POST /api/sessions/logout` |
13+
14+
## Users — account (`users.account`)
15+
16+
| Legacy procedure | Proposed REST / notes |
17+
|------------------|----------------------|
18+
| `users.account.register` | `POST /api/users` |
19+
| `users.account.resendVerificationEmail` | `POST /api/users/me/email-verification/resend` |
20+
| `users.account.verifyEmail` | `POST /api/users/me/email-verification/confirm` |
21+
| `users.account.emailChange.request` | `POST /api/users/me/email-change` |
22+
| `users.account.twoFactorAuth.enable.request` | `POST /api/users/me/2fa/enable/request` |
23+
| `users.account.twoFactorAuth.enable.finish` | `POST /api/users/me/2fa/enable/finish` |
24+
| `users.account.twoFactorAuth.load` | `GET /api/users/me/2fa` |
25+
| `users.account.twoFactorAuth.generateRecoveryCodes` | `POST /api/users/me/2fa/recovery-codes` |
26+
| `users.account.twoFactorAuth.forgetTrustedDevices` | `POST /api/users/me/2fa/devices/forget` |
27+
| `users.account.twoFactorAuth.disable` | `POST /api/users/me/2fa/disable` |
28+
| `users.account.stripe.createCheckoutSession` | `POST /api/billing/stripe/checkout-session` |
29+
| `users.account.stripe.createPortalSession` | `POST /api/billing/stripe/portal-session` |
30+
| `users.account.delete` | `DELETE /api/users/me` |
31+
32+
## Users — pages (`users.pages`)
33+
34+
| Legacy procedure | Proposed REST / notes |
35+
|------------------|----------------------|
36+
| `users.pages.notifications.load` | `GET /api/users/me/notifications` |
37+
| `users.pages.notifications.markAsRead` | `POST /api/users/me/notifications/read` |
38+
| `users.pages.getStartingPageId` | `GET /api/users/me/pages/starting` |
39+
| `users.pages.getCurrentPath` | `GET /api/users/me/pages/path` |
40+
| `users.pages.removeRecentPages` | `POST /api/users/me/pages/recent/remove` |
41+
| `users.pages.clearRecentPages` | `POST /api/users/me/pages/recent/clear` |
42+
| `users.pages.addFavoritePages` | `POST /api/users/me/pages/favorites` |
43+
| `users.pages.removeFavoritePages` | `POST /api/users/me/pages/favorites/remove` |
44+
| `users.pages.clearFavoritePages` | `POST /api/users/me/pages/favorites/clear` |
45+
| `users.pages.setEncryptedDefaultNote` | `PATCH /api/users/me/defaults/note` |
46+
| `users.pages.setEncryptedDefaultArrow` | `PATCH /api/users/me/defaults/arrow` |
47+
| `users.pages.getGroupIds` | `GET /api/users/me/groups` |
48+
49+
## Groups (`groupsRouter`)
50+
51+
| Legacy procedure | Proposed REST / notes |
52+
|------------------|----------------------|
53+
| `groups.getMainPageId` | `GET /api/groups/:groupId/main-page` |
54+
| `groups.getUserIds` | `GET /api/groups/:groupId/members` (ids / minimal DTO) |
55+
| `groups.getPages` | `GET /api/groups/:groupId/pages` |
56+
| `groups.password.enable` | `POST /api/groups/:groupId/password` |
57+
| `groups.password.change` | `PATCH /api/groups/:groupId/password` |
58+
| `groups.password.disable` | `DELETE /api/groups/:groupId/password` |
59+
| `groups.privacy.makePublic` | `POST /api/groups/:groupId/privacy/public` |
60+
| `groups.privacy.setJoinRequestsAllowed` | `PATCH /api/groups/:groupId/privacy/join-requests` |
61+
| `groups.deletion.delete` | `DELETE /api/groups/:groupId` (soft) |
62+
| `groups.deletion.restore` | `POST /api/groups/:groupId/restore` |
63+
| `groups.deletion.deletePermanently` | `POST /api/groups/:groupId/purge` |
64+
65+
## Pages (`pagesRouter`)
66+
67+
| Legacy procedure | Proposed REST / notes |
68+
|------------------|----------------------|
69+
| `pages.create` | `POST /api/groups/:groupId/pages` |
70+
| `pages.bump` | `POST /api/pages/:pageId/bump` |
71+
| `pages.backlinks.create` | `POST /api/pages/:pageId/backlinks` |
72+
| `pages.backlinks.delete` | `DELETE /api/pages/:pageId/backlinks/:targetPageId` |
73+
| `pages.snapshots.save` | `POST /api/pages/:pageId/snapshots` |
74+
| `pages.snapshots.load` | `GET /api/pages/:pageId/snapshots/:snapshotId` |
75+
| `pages.snapshots.delete` | `DELETE /api/pages/:pageId/snapshots/:snapshotId` |
76+
| `pages.deletion.delete` | `DELETE /api/pages/:pageId` (soft) |
77+
| `pages.deletion.restore` | `POST /api/pages/:pageId/restore` |
78+
| `pages.deletion.deletePermanently` | `POST /api/pages/:pageId/purge` |
79+
80+
## Legacy app-server WebSocket → target
81+
82+
| Legacy handler | New surface | Notes |
83+
|----------------|-------------|--------|
84+
| `websocket/groups/join-invitations/*` | `WS /api/ws/groups/...` or REST for low-frequency | send / accept / reject / cancel |
85+
| `websocket/groups/join-requests/*` | same | send / accept / reject / cancel |
86+
| `websocket/groups/change-user-role` | `PATCH /api/groups/:groupId/members/:userId` | prefer REST if acceptable |
87+
| `websocket/groups/remove-user` | `DELETE /api/groups/:groupId/members/:userId` | |
88+
| `websocket/groups/privacy/make-private` | `POST /api/groups/:groupId/privacy/private` | |
89+
| `websocket/groups/rotate-keys` || **removed** per RESTART_PLAN |
90+
| `websocket/pages/move` | `POST /api/pages/:pageId/move` | |
91+
| `websocket/users/account/change-password` | `POST /api/users/me/password` | |
92+
| `websocket/users/account/email-change/finish` | `POST /api/users/me/email-change/confirm` | |
93+
| `websocket/users/account/rotate-keys` || **removed** |
94+
95+
## Webhooks (not tRPC)
96+
97+
| Legacy | New |
98+
|--------|-----|
99+
| Stripe webhook (Fastify) | `POST /api/webhooks/stripe` |
100+
| RevenueCat webhook | **not implemented** |
101+
102+
Reference routers: `apps/app-server/src/trpc/router.ts`, `apps/app-server/src/trpc/api/**`, `apps/app-server/src/websocket/**`.

new-deepnotes/packages/db/migrations/0000_init.sql

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)