Feat/postgres auth#1
Open
VanishJr wants to merge 7 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Roadmap week 1 — the foundation both remaining tracks depend on: E1 (SQLite → PostgreSQL) and B1/B2 (authentication and workspace isolation).
Done criterion from the roadmap is met: two users on one server cannot see each other's data, and the suite is green on Postgres.
Storage: PostgreSQL 16 + Drizzle (E1)
backend/src/db/schema.ts. SQL migrations are generated from it (npm run db:generate) intobackend/drizzle/and applied at startup — the hand-rolled runner insrc/migrations/and itsmeta.schema_versioncounter are gone, as isbetter-sqlite3as a runtime dependency (it stays in devDependencies for the import script below).001-initial/002-money-centsmigrations are folded into a single generated0000_initial.sql. Nothing is deployed anywhere yet, so there is no history to preserve; from the first real deployment on, migrations become append-only.db.tswas synchronous, the pg driver is not). That rippled through every caller:index.ts,classify,sync,fxrates,csv,settings,independence, all providers.db.backup()andATTACH, both SQLite-only — it is a JSON export/import now, and the Overview screen button keeps working.docker-compose.ymlgains apostgres:16-alpineservice with a healthcheck; 5432 is published so a non-Dockernpm run devtalks to the same database.POSTGRES_PASSWORDis read from the repo-root.envby both the database and the backend'sDATABASE_URL, so it changes in one place.Auth: better-auth, email + password (B1)
x-api-keyshared secret andVITE_API_KEYare removed, CORS switched to an origin allowlist withcredentials, andweb/src/api.tssendscredentials: 'include'./api/*route requires a session.backend/src/index.tsis now a thin bootstrap; routes moved intobackend/src/app.tsso tests can mount the app without starting a listener.SIGNUP_INVITE_CODEgates registration. Empty by default (fine on a laptop, not fine on a public host); the register form asks for the code when the server requires it.assertProductionConfigrefuses to start underNODE_ENV=productionon the dev auth secret or the dev database password — the failure mode this replaces was silent.web/src/screens/AuthScreen.tsxfor login/register.Workspaces:
workspace_ideverywhere (B2)workspace_id; registration seeds a personal workspace via the better-auth organization plugin in a minimal configuration (rolesowner/member, no permission matrix — companies are out of scope for the prototype).backend/src/db/workspace.ts): session middleware sets it per request, background jobs set it per workspace. A db call with no scope throws rather than quietly running unscoped — the failure is loud instead of being a data leak.MONOBANK_TOKEN, Enable Banking keys), so Monobank syncs only into the oldest workspace on the server and auto-sync serves that one alone. Otherwise other users would receive the owner's bank data. Documented in the README; per-workspace credentials are a later decision.Tests
backend/test/setup.tsspins up PGlite (embedded Postgres, no Docker) with a fresh instance per test file, replacingDB_PATH=':memory:'. Existing tests are ported toawait;restore-legacy.test.tsis dropped along with the SQLite backup format it covered.New coverage:
workspace.test.ts— an unscoped db call throws.isolation.test.ts— two workspaces over the real HTTP surface: neither sees the other's accounts, transactions, settings, rules or budgets.http-auth.test.ts— register/login/logout, unauthenticated/api/*rejection, invite-code gating.config.test.ts— production config assertions.Migrating an existing database
npm run migrate:sqliteimports a pre-Postgres SQLite file into a single workspace (the oldest by default,--workspace <id>to pick another) and prints a per-table row count. One-off script, not part of the startup path.Verification
backend:npm run typecheckclean,npm test— 13 files / 80 tests passing.web:npm run buildpassing.Review notes
db/index.ts,app.tsandindex.tseach carry storage, auth and scoping changes at once, and splitting them further would have meant fictional intermediate states. Reviewing the diff as a whole is the honest path.backend/src/db/index.ts; most of it is the mechanical raw-SQL → Drizzle port, with the workspace predicate added to every query.