refactor(db): replace sqlc with handwritten pgx repos

- Finish Paddle-to-Stripe billing migration; drop paddle/stripe libs
- Renumber migrations; add 00012 memberships+manage_token,
  00013 location_type_capacity
- Add tests: admin, auth, bookings customer service
- Remove graphify-out artifacts and stale env files

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-17 20:21:34 +02:00
1 parent 2039669e2c
commit f2f7439050
93 files changed
+6987 -69060

No files matched your search

@@ -8,6 +8,7 @@ ALTER TABLE billing_snapshots
ALTER TABLE subscription_events
ADD COLUMN IF NOT EXISTS billing_provider text NOT NULL DEFAULT 'paddle';
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -18,7 +19,9 @@ BEGIN
ALTER TABLE tenants RENAME COLUMN stripe_customer_id TO billing_customer_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -29,7 +32,9 @@ BEGIN
ALTER TABLE tenants RENAME COLUMN stripe_subscription_id TO billing_subscription_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -40,7 +45,9 @@ BEGIN
ALTER TABLE billing_snapshots RENAME COLUMN stripe_customer_id TO billing_customer_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -51,7 +58,9 @@ BEGIN
ALTER TABLE billing_snapshots RENAME COLUMN stripe_subscription_id TO billing_subscription_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -62,6 +71,7 @@ BEGIN
ALTER TABLE subscription_events RENAME COLUMN stripe_event_id TO billing_provider_event_id;
END IF;
END $$;
-- +goose StatementEnd
ALTER TABLE subscription_events DROP CONSTRAINT IF EXISTS subscription_events_stripe_event_id_key;
ALTER TABLE subscription_events
@@ -70,6 +80,7 @@ ALTER TABLE subscription_events
-- +goose Down
ALTER TABLE subscription_events DROP CONSTRAINT IF EXISTS subscription_events_provider_event_key;
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -80,7 +91,9 @@ BEGIN
ALTER TABLE subscription_events RENAME COLUMN billing_provider_event_id TO stripe_event_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -91,7 +104,9 @@ BEGIN
ALTER TABLE billing_snapshots RENAME COLUMN billing_subscription_id TO stripe_subscription_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -102,7 +117,9 @@ BEGIN
ALTER TABLE billing_snapshots RENAME COLUMN billing_customer_id TO stripe_customer_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -113,7 +130,9 @@ BEGIN
ALTER TABLE tenants RENAME COLUMN billing_subscription_id TO stripe_subscription_id;
END IF;
END $$;
-- +goose StatementEnd
-- +goose StatementBegin
DO $$
BEGIN
IF EXISTS (
@@ -124,6 +143,7 @@ BEGIN
ALTER TABLE tenants RENAME COLUMN billing_customer_id TO stripe_customer_id;
END IF;
END $$;
-- +goose StatementEnd
ALTER TABLE subscription_events DROP COLUMN IF EXISTS billing_provider;
ALTER TABLE billing_snapshots DROP COLUMN IF EXISTS billing_provider;
@@ -1,22 +1,21 @@
-- +goose Up
-- +goose StatementBegin
-- Users table for authentication (migrated from auth-service)
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255),
password_hash VARCHAR(255),
email_verified BOOLEAN DEFAULT FALSE,
provider VARCHAR(50) NOT NULL DEFAULT 'email',
provider_id VARCHAR(255),
role VARCHAR(50) NOT NULL DEFAULT 'user',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_login_at TIMESTAMP WITH TIME ZONE
);
-- Authentication columns for the users table (migrated from auth-service).
-- The users table itself is created in 00001_init; this migration adds the
-- columns the auth and admin code paths require.
ALTER TABLE users
ADD COLUMN IF NOT EXISTS name VARCHAR(255),
ADD COLUMN IF NOT EXISTS password_hash VARCHAR(255),
ADD COLUMN IF NOT EXISTS email_verified BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS provider VARCHAR(50) NOT NULL DEFAULT 'email',
ADD COLUMN IF NOT EXISTS provider_id VARCHAR(255),
ADD COLUMN IF NOT EXISTS role VARCHAR(50) NOT NULL DEFAULT 'user',
ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMP WITH TIME ZONE;
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
ALTER TABLE users ALTER COLUMN id SET DEFAULT gen_random_uuid();
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email_unique ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_provider ON users(provider, provider_id);
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
@@ -92,6 +91,18 @@ DROP TABLE IF EXISTS admin_audit_log;
DROP TABLE IF EXISTS oauth_states;
DROP TABLE IF EXISTS password_resets;
DROP TABLE IF EXISTS magic_links;
DROP TABLE IF EXISTS users;
DROP INDEX IF EXISTS idx_users_role;
DROP INDEX IF EXISTS idx_users_provider;
DROP INDEX IF EXISTS idx_users_email_unique;
ALTER TABLE users
DROP COLUMN IF EXISTS last_login_at,
DROP COLUMN IF EXISTS role,
DROP COLUMN IF EXISTS provider_id,
DROP COLUMN IF EXISTS provider,
DROP COLUMN IF EXISTS email_verified,
DROP COLUMN IF EXISTS password_hash,
DROP COLUMN IF EXISTS name;
-- +goose StatementEnd
@@ -27,11 +27,15 @@ CREATE TABLE IF NOT EXISTS team_invites (
email text NOT NULL,
role text NOT NULL DEFAULT 'staff',
status text NOT NULL DEFAULT 'pending',
expires_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, email)
);
ALTER TABLE team_invites
ADD COLUMN IF NOT EXISTS expires_at timestamptz;
UPDATE tenants SET plan_code = 'pro' WHERE plan_code = 'growth';
UPDATE tenants SET plan_code = 'business' WHERE plan_code = 'multi-location';
UPDATE billing_snapshots SET plan_code = 'pro' WHERE plan_code = 'growth';
@@ -34,14 +34,12 @@ CREATE TABLE IF NOT EXISTS sms_monthly_reports (
UNIQUE (tenant_id, year_month)
);
CREATE INDEX IF NOT EXISTS idx_sms_usage_tenant_month ON sms_usage_logs (tenant_id, date_trunc('month', created_at));
CREATE INDEX IF NOT EXISTS idx_sms_usage_tenant_created ON sms_usage_logs (tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_sms_reports_tenant ON sms_monthly_reports (tenant_id, year_month DESC);
-- +goose Down
DROP INDEX IF EXISTS idx_sms_reports_tenant;
DROP INDEX IF EXISTS idx_sms_usage_tenant_created;
DROP INDEX IF EXISTS idx_sms_usage_tenant_month;
DROP TABLE IF EXISTS sms_monthly_reports;
DROP TABLE IF EXISTS sms_usage_logs;
DROP TABLE IF EXISTS tenant_sms_settings;
@@ -0,0 +1,50 @@
-- +goose Up
-- Tenant memberships keyed by the auth subject (Neon Auth `sub`).
-- The auth middleware resolves JWT subjects to memberships through users.neon_subject.
CREATE TABLE IF NOT EXISTS tenant_memberships (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
user_neon_subject text NOT NULL,
role text NOT NULL DEFAULT 'staff',
joined_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, user_neon_subject)
);
CREATE INDEX IF NOT EXISTS idx_tenant_memberships_subject ON tenant_memberships (user_neon_subject);
-- Unguessable per-booking management token used by /v1/public/bookings/:reference endpoints.
ALTER TABLE bookings
ADD COLUMN IF NOT EXISTS manage_token text;
CREATE UNIQUE INDEX IF NOT EXISTS idx_bookings_manage_token
ON bookings (manage_token)
WHERE manage_token IS NOT NULL;
-- Backfill tokens for pre-existing bookings.
UPDATE bookings
SET manage_token = replace(gen_random_uuid()::text, '-', '') || replace(gen_random_uuid()::text, '-', '')
WHERE manage_token IS NULL;
-- locations.updated_at is written by the repository update path.
ALTER TABLE locations
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
-- Demo tenant membership so demo-owner resolves to Studio Atelier.
INSERT INTO tenant_memberships (tenant_id, user_neon_subject, role)
SELECT t.id, u.neon_subject, 'owner'
FROM tenants t
JOIN users u ON u.neon_subject = 'demo-owner'
WHERE t.slug = 'studio-atelier'
ON CONFLICT (tenant_id, user_neon_subject) DO NOTHING;
-- +goose Down
ALTER TABLE locations
DROP COLUMN IF EXISTS updated_at;
DROP INDEX IF EXISTS idx_bookings_manage_token;
ALTER TABLE bookings
DROP COLUMN IF EXISTS manage_token;
DROP TABLE IF EXISTS tenant_memberships;
@@ -0,0 +1,25 @@
-- +goose Up
ALTER TABLE locations
ADD COLUMN IF NOT EXISTS type text NOT NULL DEFAULT 'room',
ADD COLUMN IF NOT EXISTS capacity integer NOT NULL DEFAULT 1;
-- Per-tenant custom email templates (overrides for built-in subjects/bodies).
CREATE TABLE IF NOT EXISTS email_templates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
type text NOT NULL,
subject text NOT NULL DEFAULT '',
body_html text NOT NULL DEFAULT '',
body_text text NOT NULL DEFAULT '',
is_enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, type)
);
-- +goose Down
DROP TABLE IF EXISTS email_templates;
ALTER TABLE locations
DROP COLUMN IF EXISTS type,
DROP COLUMN IF EXISTS capacity;