added full api

This commit is contained in:
blaisadmin
2026-04-14 22:41:17 -04:00
parent e0ab66d21a
commit 37c8265320
17 changed files with 3146 additions and 978 deletions
+226
View File
@@ -0,0 +1,226 @@
import { db, type DatabaseClient } from './client.js';
export const ensureSchema = async (database: DatabaseClient = db) => {
await database.query(`
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255),
name VARCHAR(160) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS workspaces (
id INTEGER PRIMARY KEY,
name VARCHAR(160) NOT NULL DEFAULT 'My Flock',
workspace_type VARCHAR(16) NOT NULL DEFAULT 'standard',
billing_email VARCHAR(255),
billing_plan VARCHAR(32) NOT NULL DEFAULT 'household_basic',
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE workspaces
DROP CONSTRAINT IF EXISTS workspaces_id_check;
ALTER TABLE workspaces
ADD COLUMN IF NOT EXISTS billing_email VARCHAR(255),
ADD COLUMN IF NOT EXISTS billing_plan VARCHAR(32) NOT NULL DEFAULT 'household_basic';
INSERT INTO workspaces (id, name, workspace_type, billing_plan)
VALUES (1, 'My Flock', 'standard', 'household_basic')
ON CONFLICT (id) DO NOTHING;
CREATE TABLE IF NOT EXISTS workspace_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
invite_email VARCHAR(255) NOT NULL,
name VARCHAR(160) NOT NULL,
role VARCHAR(16) NOT NULL DEFAULT 'staff',
accepted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE workspace_members
ADD COLUMN IF NOT EXISTS email VARCHAR(255),
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES users(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS invite_email VARCHAR(255),
ADD COLUMN IF NOT EXISTS accepted_at TIMESTAMPTZ;
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'workspace_members'
AND column_name = 'email'
) THEN
UPDATE workspace_members
SET invite_email = COALESCE(invite_email, email)
WHERE invite_email IS NULL;
END IF;
END $$;
UPDATE workspace_members
SET invite_email = ''
WHERE invite_email IS NULL;
UPDATE workspace_members
SET email = invite_email
WHERE email IS NULL;
ALTER TABLE workspace_members
ALTER COLUMN invite_email SET NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_members_workspace_email
ON workspace_members (workspace_id, invite_email);
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_members_workspace_user
ON workspace_members (workspace_id, user_id)
WHERE user_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS auth_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
active_workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
token_hash VARCHAR(255) NOT NULL UNIQUE,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS integration_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
name VARCHAR(160) NOT NULL,
token_hash VARCHAR(255) NOT NULL UNIQUE,
token_prefix VARCHAR(32) NOT NULL,
scope VARCHAR(16) NOT NULL DEFAULT 'read_write',
last_used_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE integration_tokens
ADD COLUMN IF NOT EXISTS workspace_id INTEGER REFERENCES workspaces(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS name VARCHAR(160) NOT NULL DEFAULT 'Integration token',
ADD COLUMN IF NOT EXISTS token_prefix VARCHAR(32),
ADD COLUMN IF NOT EXISTS scope VARCHAR(16) NOT NULL DEFAULT 'read_write',
ADD COLUMN IF NOT EXISTS last_used_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS revoked_at TIMESTAMPTZ;
UPDATE integration_tokens
SET token_prefix = LEFT(token_hash, 12)
WHERE token_prefix IS NULL;
CREATE INDEX IF NOT EXISTS idx_integration_tokens_user_workspace
ON integration_tokens (user_id, workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_integration_tokens_lookup
ON integration_tokens (token_hash)
WHERE revoked_at IS NULL;
CREATE TABLE IF NOT EXISTS auth_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider_key VARCHAR(32) NOT NULL,
provider_subject VARCHAR(255) NOT NULL,
provider_email VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_auth_accounts_provider_subject
ON auth_accounts (provider_key, provider_subject);
CREATE TABLE IF NOT EXISTS oauth_states (
id UUID PRIMARY KEY,
provider_key VARCHAR(32) NOT NULL,
code_verifier VARCHAR(255) NOT NULL,
redirect_to TEXT NOT NULL,
expires_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE IF NOT EXISTS magic_link_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL,
name VARCHAR(160),
token_hash VARCHAR(255) NOT NULL UNIQUE,
redirect_to TEXT NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_magic_link_tokens_email
ON magic_link_tokens (email, created_at DESC);
CREATE TABLE IF NOT EXISTS birds (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id INTEGER NOT NULL DEFAULT 1,
name VARCHAR(120) NOT NULL,
tag_id VARCHAR(80) NOT NULL,
species VARCHAR(120) NOT NULL,
date_of_birth DATE,
gotcha_day DATE,
chart_color VARCHAR(7) NOT NULL DEFAULT '#cb3a35',
photo_data_url TEXT,
notify_on_dob BOOLEAN NOT NULL DEFAULT FALSE,
notify_on_gotcha_day BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE birds
ADD COLUMN IF NOT EXISTS workspace_id INTEGER NOT NULL DEFAULT 1,
ADD COLUMN IF NOT EXISTS date_of_birth DATE,
ADD COLUMN IF NOT EXISTS gotcha_day DATE,
ADD COLUMN IF NOT EXISTS chart_color VARCHAR(7) NOT NULL DEFAULT '#cb3a35',
ADD COLUMN IF NOT EXISTS photo_data_url TEXT,
ADD COLUMN IF NOT EXISTS notify_on_dob BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS notify_on_gotcha_day BOOLEAN NOT NULL DEFAULT FALSE;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'birds_workspace_fk') THEN
ALTER TABLE birds
ADD CONSTRAINT birds_workspace_fk
FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
END IF;
END $$;
ALTER TABLE birds
DROP CONSTRAINT IF EXISTS birds_tag_id_key;
CREATE UNIQUE INDEX IF NOT EXISTS idx_birds_workspace_tag_id
ON birds (workspace_id, tag_id);
CREATE TABLE IF NOT EXISTS weight_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bird_id UUID NOT NULL REFERENCES birds(id) ON DELETE CASCADE,
weight_grams NUMERIC(8, 2) NOT NULL CHECK (weight_grams > 0),
recorded_on DATE NOT NULL,
notes VARCHAR(280),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (bird_id, recorded_on)
);
CREATE TABLE IF NOT EXISTS vet_visits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bird_id UUID NOT NULL REFERENCES birds(id) ON DELETE CASCADE,
visited_on DATE NOT NULL,
clinic_name VARCHAR(160) NOT NULL,
reason VARCHAR(160) NOT NULL,
notes VARCHAR(1000),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_weight_records_bird_recorded_on
ON weight_records (bird_id, recorded_on DESC);
CREATE INDEX IF NOT EXISTS idx_vet_visits_bird_visited_on
ON vet_visits (bird_id, visited_on DESC);
`);
};