added full api
This commit is contained in:
@@ -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);
|
||||
`);
|
||||
};
|
||||
Reference in New Issue
Block a user