115 lines
3.5 KiB
SQL
115 lines
3.5 KiB
SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email TEXT NOT NULL,
|
|
password_hash TEXT NOT NULL,
|
|
email_verified_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS users_email_normalized_key
|
|
ON users (LOWER(BTRIM(email)));
|
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
csrf_token_hash TEXT NOT NULL,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
revoked_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS sessions_user_id_idx ON sessions(user_id);
|
|
CREATE INDEX IF NOT EXISTS sessions_expires_at_idx ON sessions(expires_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS email_verification_tokens (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS email_verification_tokens_user_id_idx
|
|
ON email_verification_tokens(user_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS password_reset_tokens_user_id_idx
|
|
ON password_reset_tokens(user_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS app_settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
DO $$
|
|
DECLARE
|
|
id_type TEXT;
|
|
pk_name TEXT;
|
|
BEGIN
|
|
SELECT data_type INTO id_type
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'races' AND column_name = 'id';
|
|
|
|
IF id_type IS NOT NULL AND id_type <> 'uuid' THEN
|
|
SELECT conname INTO pk_name
|
|
FROM pg_constraint
|
|
WHERE conrelid = 'races'::regclass AND contype = 'p'
|
|
LIMIT 1;
|
|
|
|
IF pk_name IS NOT NULL THEN
|
|
EXECUTE format('ALTER TABLE races DROP CONSTRAINT %I', pk_name);
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name = 'races' AND column_name = 'slug'
|
|
) THEN
|
|
ALTER TABLE races RENAME COLUMN id TO slug;
|
|
ELSE
|
|
ALTER TABLE races DROP COLUMN id;
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
ALTER TABLE races ADD COLUMN IF NOT EXISTS id UUID DEFAULT gen_random_uuid();
|
|
UPDATE races SET id = gen_random_uuid() WHERE id IS NULL;
|
|
ALTER TABLE races ALTER COLUMN id SET NOT NULL;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conrelid = 'races'::regclass AND contype = 'p'
|
|
) THEN
|
|
ALTER TABLE races ADD CONSTRAINT races_pkey PRIMARY KEY (id);
|
|
END IF;
|
|
END $$;
|
|
|
|
ALTER TABLE races ADD COLUMN IF NOT EXISTS slug TEXT;
|
|
UPDATE races SET slug = id::text WHERE slug IS NULL OR BTRIM(slug) = '';
|
|
ALTER TABLE races ALTER COLUMN slug SET NOT NULL;
|
|
|
|
ALTER TABLE races ADD COLUMN IF NOT EXISTS owner_user_id UUID REFERENCES users(id) ON DELETE CASCADE;
|
|
ALTER TABLE races ADD COLUMN IF NOT EXISTS source TEXT NOT NULL DEFAULT 'user';
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS races_owner_slug_key
|
|
ON races(owner_user_id, slug)
|
|
WHERE owner_user_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS races_owner_user_id_idx ON races(owner_user_id);
|