initial commit

This commit is contained in:
Daniel Noulet 2026-10-01 23:15:57 +02:00
commit 54576d9d33
61 changed files with 5569 additions and 0 deletions

View file

@ -0,0 +1,101 @@
-- +goose Up
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE tenants (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
slug text NOT NULL UNIQUE CHECK (slug ~ '^[a-z0-9][a-z0-9-]{1,62}$'),
name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
subject text NOT NULL UNIQUE,
email text NOT NULL,
display_name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
last_login_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE memberships (
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role text NOT NULL CHECK (role IN ('owner', 'admin', 'viewer')),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, user_id)
);
CREATE TABLE applications (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
slug text NOT NULL CHECK (slug ~ '^[a-z0-9][a-z0-9-]{1,62}$'),
name text NOT NULL,
description text NOT NULL DEFAULT '',
allowed_hosts text[] NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, slug)
);
CREATE TABLE api_keys (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
application_id uuid NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
name text NOT NULL,
prefix text NOT NULL,
secret_hash bytea NOT NULL UNIQUE,
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
expires_at timestamptz,
last_used_at timestamptz,
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
revoked_at timestamptz
);
CREATE INDEX api_keys_lookup_idx ON api_keys(secret_hash) WHERE status = 'active';
CREATE INDEX api_keys_application_idx ON api_keys(application_id);
CREATE TABLE openapi_contracts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
application_id uuid NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
version text NOT NULL,
source_url text,
document jsonb NOT NULL,
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE contract_endpoints (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
contract_id uuid NOT NULL REFERENCES openapi_contracts(id) ON DELETE CASCADE,
method text NOT NULL,
path text NOT NULL,
operation_id text NOT NULL DEFAULT '',
UNIQUE (contract_id, method, path)
);
CREATE TABLE authorization_events (
id bigserial PRIMARY KEY,
tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL,
application_id uuid REFERENCES applications(id) ON DELETE SET NULL,
api_key_id uuid REFERENCES api_keys(id) ON DELETE SET NULL,
method text NOT NULL,
path text NOT NULL,
host text NOT NULL,
allowed boolean NOT NULL,
reason text NOT NULL,
occurred_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX authorization_events_occurred_idx ON authorization_events(occurred_at DESC);
-- +goose Down
DROP TABLE IF EXISTS authorization_events;
DROP TABLE IF EXISTS contract_endpoints;
DROP TABLE IF EXISTS openapi_contracts;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS applications;
DROP TABLE IF EXISTS memberships;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS tenants;

View file

@ -0,0 +1,54 @@
-- +goose Up
ALTER TABLE applications
ADD COLUMN client_id text NOT NULL UNIQUE DEFAULT ('glc_' || encode(gen_random_bytes(18), 'hex')),
ADD COLUMN client_secret_hash bytea,
ADD COLUMN redirect_uris text[] NOT NULL DEFAULT '{}';
CREATE UNIQUE INDEX users_email_unique_idx ON users (lower(email));
CREATE TABLE user_application_access (
application_id uuid NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
granted_by uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (application_id, user_id)
);
INSERT INTO user_application_access(application_id, user_id, granted_by)
SELECT a.id, m.user_id, m.user_id
FROM applications a
JOIN memberships m ON m.tenant_id = a.tenant_id
ON CONFLICT DO NOTHING;
CREATE TABLE client_authorization_codes (
code_hash bytea PRIMARY KEY,
application_id uuid NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
redirect_uri text NOT NULL,
expires_at timestamptz NOT NULL,
used_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE client_user_sessions (
token_hash bytea PRIMARY KEY,
application_id uuid NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expires_at timestamptz NOT NULL,
last_used_at timestamptz,
revoked_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX client_user_sessions_user_idx ON client_user_sessions(user_id);
CREATE INDEX client_authorization_codes_expiry_idx ON client_authorization_codes(expires_at);
-- +goose Down
DROP TABLE IF EXISTS client_user_sessions;
DROP TABLE IF EXISTS client_authorization_codes;
DROP TABLE IF EXISTS user_application_access;
DROP INDEX IF EXISTS users_email_unique_idx;
ALTER TABLE applications
DROP COLUMN IF EXISTS redirect_uris,
DROP COLUMN IF EXISTS client_secret_hash,
DROP COLUMN IF EXISTS client_id;

View file

@ -0,0 +1,23 @@
-- +goose Up
ALTER TABLE users
ADD COLUMN password_hash text,
ADD COLUMN password_changed_at timestamptz;
CREATE TABLE user_login_events (
id bigserial PRIMARY KEY,
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
email text NOT NULL,
remote_address text NOT NULL,
successful boolean NOT NULL,
reason text NOT NULL,
occurred_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX user_login_events_occurred_idx ON user_login_events(occurred_at DESC);
CREATE INDEX user_login_events_email_idx ON user_login_events(lower(email), occurred_at DESC);
-- +goose Down
DROP TABLE IF EXISTS user_login_events;
ALTER TABLE users
DROP COLUMN IF EXISTS password_changed_at,
DROP COLUMN IF EXISTS password_hash;

View file

@ -0,0 +1,18 @@
-- +goose Up
CREATE TABLE installation_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
installed_at timestamptz NOT NULL DEFAULT now(),
oidc_issuer text NOT NULL,
oidc_client_id text NOT NULL,
oidc_client_secret_ciphertext bytea NOT NULL,
seeded_demo boolean NOT NULL DEFAULT false
);
CREATE TABLE system_administrators (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE IF EXISTS system_administrators;
DROP TABLE IF EXISTS installation_settings;