~comcloudway/ansible-srht

bba65935ec49a2441f3c4d6e8ce0526f065ff722 — Jakob Meier a year ago abdc937
Fetch database schema from project repo instead of using local copy

When installing the latest version of a given package (instead of
upgrading), sourcehut assumes that you also use the newest schema.sql
file.
This means that when you are using an older schema version, database
migrations won't create the missing tables.
Instead we now fetch the latest database schema before running the
playbook to match the latest version.
8 files changed, 12 insertions(+), 775 deletions(-)

M roles/builds.sr.ht/tasks/db.yml
D roles/builds.sr.ht/templates/schema.psql
M roles/git.sr.ht/tasks/db.yml
D roles/git.sr.ht/templates/schema.psql
M roles/hub.sr.ht/tasks/db.yml
D roles/hub.sr.ht/templates/schema.psql
M roles/meta.sr.ht/tasks/db.yml
D roles/meta.sr.ht/templates/schema.psql
M roles/builds.sr.ht/tasks/db.yml => roles/builds.sr.ht/tasks/db.yml +3 -3
@@ 1,7 1,7 @@
---
- name: Copy database schema to host
  ansible.builtin.template:
    src: schema.psql
- name: Download database schema from git.sr.ht
  ansible.builtin.get_url:
    url: https://git.sr.ht/~sircmpwn/builds.sr.ht/blob/master/schema.sql
    dest: /tmp/buildssrht.psql

- name: Create database

D roles/builds.sr.ht/templates/schema.psql => roles/builds.sr.ht/templates/schema.psql +0 -168
@@ 1,168 0,0 @@
CREATE TYPE auth_method AS ENUM (
	'OAUTH_LEGACY',
	'OAUTH2',
	'COOKIE',
	'INTERNAL',
	'WEBHOOK'
);

CREATE TYPE webhook_event AS ENUM (
	'JOB_CREATED'
);

CREATE TYPE visibility AS ENUM (
	'PUBLIC',
	'UNLISTED',
	'PRIVATE'
);

CREATE TABLE "user" (
	id serial PRIMARY KEY,
	username character varying(256) UNIQUE,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	oauth_token character varying(256),
	oauth_token_expires timestamp without time zone,
	email character varying(256) NOT NULL,
	user_type character varying DEFAULT 'active_non_paying'::character varying NOT NULL,
	url character varying(256),
	location character varying(256),
	bio character varying(4096),
	oauth_token_scopes character varying DEFAULT 'profile'::character varying,
	oauth_revocation_token character varying(256),
	suspension_notice character varying(4096)
);

CREATE TABLE secret (
	id serial PRIMARY KEY,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	uuid uuid NOT NULL,
	name character varying(512),
	from_user_id integer REFERENCES "user"(id) ON DELETE SET NULL,
	-- Key secrets:
	secret_type character varying NOT NULL,
	secret bytea NOT NULL,
	-- File secrets:
	path character varying(512),
	mode integer,
	CONSTRAINT secret_user_id_uuid_unique UNIQUE (user_id, uuid)
);

CREATE INDEX ix_user_username ON "user" USING btree (username);

CREATE TABLE job_group (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	note character varying(4096)
);

CREATE TABLE job (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	manifest character varying(16384) NOT NULL,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	job_group_id integer REFERENCES job_group(id) ON DELETE SET NULL,
	note character varying(4096),
	tags character varying,
	runner character varying,
	status character varying NOT NULL,
	secrets boolean DEFAULT true NOT NULL,
	image character varying(128),
	visibility visibility NOT NULL
);

CREATE INDEX ix_job_owner_id ON job USING btree (owner_id);

CREATE INDEX ix_job_job_group_id ON job USING btree (job_group_id);

CREATE TABLE artifact (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	job_id integer NOT NULL REFERENCES job(id) ON DELETE CASCADE,
	name character varying NOT NULL,
	path character varying NOT NULL,
	url character varying NOT NULL,
	size integer NOT NULL
);

CREATE TABLE task (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	name character varying(256) NOT NULL,
	status character varying NOT NULL,
	job_id integer NOT NULL REFERENCES job(id) ON DELETE CASCADE
);

CREATE INDEX ix_task_job_id ON task USING btree (job_id);

CREATE TABLE trigger (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	details character varying(4096) NOT NULL,
	condition character varying NOT NULL,
	trigger_type character varying NOT NULL,
	job_id integer REFERENCES job(id) ON DELETE CASCADE,
	job_group_id integer REFERENCES job_group(id) ON DELETE CASCADE
);

-- GraphQL webhooks
CREATE TABLE gql_user_wh_sub (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	events webhook_event[] NOT NULL,
	url character varying NOT NULL,
	query character varying NOT NULL,
	auth_method auth_method NOT NULL,
	token_hash character varying(128),
	grants character varying,
	client_id uuid,
	expires timestamp without time zone,
	node_id character varying,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	CONSTRAINT gql_user_wh_sub_auth_method_check
		CHECK ((auth_method = ANY (ARRAY['OAUTH2'::auth_method, 'INTERNAL'::public.auth_method]))),
	CONSTRAINT gql_user_wh_sub_check
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (token_hash IS NOT NULL))),
	CONSTRAINT gql_user_wh_sub_check1
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (expires IS NOT NULL))),
	CONSTRAINT gql_user_wh_sub_check2
		CHECK (((auth_method = 'INTERNAL'::auth_method) = (node_id IS NOT NULL))),
	CONSTRAINT gql_user_wh_sub_events_check
		CHECK ((array_length(events, 1) > 0))
);

CREATE INDEX gql_user_wh_sub_token_hash_idx
	ON gql_user_wh_sub
	USING btree (token_hash);

CREATE TABLE gql_user_wh_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	date timestamp without time zone NOT NULL,
	event webhook_event NOT NULL,
	subscription_id integer NOT NULL
		REFERENCES gql_user_wh_sub(id) ON DELETE CASCADE,
	request_body character varying NOT NULL,
	response_body character varying,
	response_headers character varying,
	response_status integer
);

-- Legacy OAuth
CREATE TABLE oauthtoken (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	expires timestamp without time zone NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	token_hash character varying(128) NOT NULL,
	token_partial character varying(8) NOT NULL,
	scopes character varying(512) NOT NULL
);

M roles/git.sr.ht/tasks/db.yml => roles/git.sr.ht/tasks/db.yml +3 -3
@@ 1,7 1,7 @@
---
- name: Copy database schema to host
  ansible.builtin.template:
    src: schema.psql
- name: Download database schema from git.sr.ht
  ansible.builtin.get_url:
    url: https://git.sr.ht/~sircmpwn/git.sr.ht/blob/master/schema.sql
    dest: /tmp/gitsrht.psql

- name: Create database

D roles/git.sr.ht/templates/schema.psql => roles/git.sr.ht/templates/schema.psql +0 -227
@@ 1,227 0,0 @@
CREATE TYPE auth_method AS ENUM (
	'OAUTH_LEGACY',
	'OAUTH2',
	'COOKIE',
	'INTERNAL',
	'WEBHOOK'
);

CREATE TYPE clone_status AS ENUM (
	'NONE',
	'IN_PROGRESS',
	'COMPLETE',
	'ERROR'
);

CREATE TYPE visibility AS ENUM (
	'PUBLIC',
	'PRIVATE',
	'UNLISTED'
);

CREATE TYPE webhook_event AS ENUM (
	'REPO_CREATED',
	'REPO_UPDATE',
	'REPO_DELETED'
);

CREATE TYPE owner_repo_name AS (
	owner text,
	repo_name text
);

CREATE TYPE owner_id_repo_name AS (
	owner_id integer,
	repo_name text
);

CREATE TABLE "user" (
	id serial PRIMARY KEY,
	username character varying(256) UNIQUE,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	email character varying(256) NOT NULL,
	user_type character varying NOT NULL DEFAULT 'active_non_paying'::character varying,
	url character varying(256),
	location character varying(256),
	bio character varying(4096),
	suspension_notice character varying(4096),
	-- TODO: Delete these
	oauth_token character varying(256),
	oauth_token_expires timestamp without time zone,
	oauth_token_scopes character varying DEFAULT ''::character varying,
	oauth_revocation_token character varying(256)
);

CREATE INDEX ix_user_username ON "user" USING btree (username);

CREATE TABLE repository (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	name character varying(256) NOT NULL,
	description character varying(1024),
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	path character varying(1024),
	visibility visibility NOT NULL,
	readme character varying,
	clone_status clone_status NOT NULL,
	clone_error character varying,
	CONSTRAINT repository_check
		CHECK (((clone_status = 'ERROR'::clone_status) <> (clone_error IS NULL))),
	CONSTRAINT uq_repo_owner_id_name UNIQUE (owner_id, name)
);

CREATE TABLE access (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	repo_id integer NOT NULL REFERENCES repository(id) ON DELETE CASCADE,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	mode character varying NOT NULL,
	CONSTRAINT uq_access_user_id_repo_id UNIQUE (user_id, repo_id)
);

CREATE TABLE artifacts (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	repo_id integer NOT NULL REFERENCES repository(id) ON DELETE CASCADE,
	commit character varying NOT NULL,
	filename character varying NOT NULL,
	checksum character varying NOT NULL,
	size integer NOT NULL,
	CONSTRAINT repo_artifact_filename_unique UNIQUE (repo_id, filename)
);

CREATE TABLE redirect (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	name character varying(256) NOT NULL,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	path character varying(1024),
	new_repo_id integer NOT NULL REFERENCES repository(id) ON DELETE CASCADE
);

-- GraphQL webhooks
CREATE TABLE gql_user_wh_sub (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	events webhook_event[] NOT NULL,
	url character varying NOT NULL,
	query character varying NOT NULL,
	auth_method auth_method NOT NULL,
	token_hash character varying(128),
	grants character varying,
	client_id uuid,
	expires timestamp without time zone,
	node_id character varying,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	CONSTRAINT gql_user_wh_sub_auth_method_check
		CHECK ((auth_method = ANY (ARRAY['OAUTH2'::auth_method, 'INTERNAL'::auth_method]))),
	CONSTRAINT gql_user_wh_sub_check
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (token_hash IS NOT NULL))),
	CONSTRAINT gql_user_wh_sub_check1
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (expires IS NOT NULL))),
	CONSTRAINT gql_user_wh_sub_check2
		CHECK (((auth_method = 'INTERNAL'::auth_method) = (node_id IS NOT NULL))),
	CONSTRAINT gql_user_wh_sub_events_check
		CHECK ((array_length(events, 1) > 0))
);

CREATE INDEX gql_user_wh_sub_token_hash_idx
	ON gql_user_wh_sub
	USING btree (token_hash);

CREATE TABLE gql_user_wh_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	date timestamp without time zone NOT NULL,
	event webhook_event NOT NULL,
	subscription_id integer NOT NULL
		REFERENCES gql_user_wh_sub(id) ON DELETE CASCADE,
	request_body character varying NOT NULL,
	response_body character varying,
	response_headers character varying,
	response_status integer
);

-- Legacy SSH key table, to be fetched from meta.sr.ht instead (TODO: Remove)
CREATE TABLE sshkey (
	id serial PRIMARY KEY,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	meta_id integer NOT NULL,
	key character varying(4096) NOT NULL,
	fingerprint character varying(512) NOT NULL
);

CREATE INDEX ix_sshkey_key
	ON sshkey
	USING btree (md5((key)::text));

CREATE UNIQUE INDEX ix_sshkey_meta_id
	ON sshkey
	USING btree (meta_id);

-- Legacy OAuth (TODO: Remove)
CREATE TABLE oauthtoken (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	expires timestamp without time zone NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	token_hash character varying(128) NOT NULL,
	token_partial character varying(8) NOT NULL,
	scopes character varying(512) NOT NULL
);

-- Legacy webhooks (TODO: Remove)
CREATE TABLE user_webhook_subscription (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	url character varying(2048) NOT NULL,
	events character varying NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	token_id integer REFERENCES oauthtoken(id) ON DELETE CASCADE
);

CREATE TABLE user_webhook_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	created timestamp without time zone NOT NULL,
	event character varying(256) NOT NULL,
	url character varying(2048) NOT NULL,
	payload character varying(65536) NOT NULL,
	payload_headers character varying(16384) NOT NULL,
	response character varying(65536),
	response_status integer NOT NULL,
	response_headers character varying(16384),
	subscription_id integer NOT NULL
		REFERENCES user_webhook_subscription(id) ON DELETE CASCADE
);

CREATE TABLE repo_webhook_subscription (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	url character varying(2048) NOT NULL,
	events character varying NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	token_id integer REFERENCES oauthtoken(id) ON DELETE CASCADE,
	repo_id integer REFERENCES repository(id) ON DELETE CASCADE,
	sync boolean DEFAULT false NOT NULL
);

CREATE TABLE repo_webhook_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	created timestamp without time zone NOT NULL,
	event character varying(256) NOT NULL,
	url character varying(2048) NOT NULL,
	payload character varying(65536) NOT NULL,
	payload_headers character varying(16384) NOT NULL,
	response character varying(65536),
	response_status integer NOT NULL,
	response_headers character varying(16384),
	subscription_id integer NOT NULL
		REFERENCES repo_webhook_subscription(id) ON DELETE CASCADE
);

M roles/hub.sr.ht/tasks/db.yml => roles/hub.sr.ht/tasks/db.yml +3 -3
@@ 1,7 1,7 @@
---
- name: Copy database schema to host
  ansible.builtin.template:
    src: schema.psql
- name: Download database schema from git.sr.ht
  ansible.builtin.get_url:
    url: https://git.sr.ht/~sircmpwn/hub.sr.ht/blob/master/schema.sql
    dest: /tmp/hubsrht.psql

- name: Create database

D roles/hub.sr.ht/templates/schema.psql => roles/hub.sr.ht/templates/schema.psql +0 -103
@@ 1,103 0,0 @@
CREATE TYPE visibility AS ENUM (
	'PUBLIC',
	'PRIVATE',
	'UNLISTED'
);

CREATE TABLE "user" (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	username character varying(256),
	email character varying(256) NOT NULL,
	user_type character varying NOT NULL,
	url character varying(256),
	location character varying(256),
	bio character varying(4096),
	suspension_notice character varying(4096),
	oauth_token character varying(256),
	oauth_token_expires timestamp without time zone,
	oauth_token_scopes character varying,
	oauth_revocation_token character varying(256)
);

CREATE UNIQUE INDEX ix_user_username ON "user" USING btree (username);

CREATE TABLE project (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	name character varying(128) NOT NULL,
	description character varying(512) NOT NULL,
	website character varying,
	visibility visibility DEFAULT 'UNLISTED'::visibility NOT NULL,
	checklist_complete boolean DEFAULT false NOT NULL,
	summary_repo_id integer,
	tags character varying(16)[] DEFAULT '{}'::character varying[] NOT NULL
);

CREATE TABLE features (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	project_id integer NOT NULL REFERENCES project(id) ON DELETE CASCADE,
	summary character varying NOT NULL
);

CREATE TABLE mailing_list (
	id serial PRIMARY KEY,
	remote_id integer NOT NULL,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	project_id integer NOT NULL REFERENCES project(id) ON DELETE CASCADE,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	name character varying(128) NOT NULL,
	description character varying,
	visibility visibility DEFAULT 'UNLISTED'::visibility NOT NULL
);

CREATE TABLE source_repo (
	id serial PRIMARY KEY,
	remote_id integer NOT NULL,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	project_id integer NOT NULL REFERENCES project(id) ON DELETE CASCADE,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	name character varying(128) NOT NULL,
	description character varying,
	repo_type character varying NOT NULL,
	visibility visibility DEFAULT 'UNLISTED'::visibility NOT NULL,
	CONSTRAINT project_source_repo_unique UNIQUE (project_id, remote_id, repo_type)
);

ALTER TABLE project
	ADD CONSTRAINT project_summary_repo_id_fkey FOREIGN KEY (summary_repo_id) REFERENCES source_repo(id) ON DELETE CASCADE;

CREATE TABLE tracker (
	id serial PRIMARY KEY,
	remote_id integer NOT NULL,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	project_id integer NOT NULL REFERENCES project(id) ON DELETE CASCADE,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	name character varying(128) NOT NULL,
	description character varying,
	visibility visibility DEFAULT 'UNLISTED'::visibility NOT NULL
);

CREATE TABLE event (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	project_id integer NOT NULL REFERENCES project(id) ON DELETE CASCADE,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	event_type character varying NOT NULL,
	source_repo_id integer REFERENCES source_repo(id) ON DELETE CASCADE,
	mailing_list_id integer REFERENCES mailing_list(id) ON DELETE CASCADE,
	tracker_id integer REFERENCES tracker(id) ON DELETE CASCADE,
	external_source character varying,
	external_summary character varying,
	external_details character varying,
	external_summary_plain character varying,
	external_details_plain character varying,
	external_url character varying
);

M roles/meta.sr.ht/tasks/db.yml => roles/meta.sr.ht/tasks/db.yml +3 -3
@@ 1,7 1,7 @@
---
- name: Copy database schema to host
  ansible.builtin.template:
    src: schema.psql
- name: Download database schema from git.sr.ht
  ansible.builtin.get_url:
    url: https://git.sr.ht/~sircmpwn/meta.sr.ht/blob/master/schema.sql
    dest: /tmp/metasrht.psql

- name: Create database

D roles/meta.sr.ht/templates/schema.psql => roles/meta.sr.ht/templates/schema.psql +0 -265
@@ 1,265 0,0 @@
CREATE TYPE auth_method AS ENUM (
	'OAUTH_LEGACY',
	'OAUTH2',
	'COOKIE',
	'INTERNAL',
	'WEBHOOK'
);

CREATE TYPE webhook_event AS ENUM (
	'PROFILE_UPDATE',
	'PGP_KEY_ADDED',
	'PGP_KEY_REMOVED',
	'SSH_KEY_ADDED',
	'SSH_KEY_REMOVED'
);

CREATE TABLE "user" (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	username character varying(256) NOT NULL UNIQUE,
	password character varying(256) NOT NULL,
	email character varying(256) NOT NULL UNIQUE,
	new_email character varying(256),
	user_type character varying NOT NULL,
	confirmation_hash character varying(128),
	url character varying(256),
	location character varying(256),
	bio character varying(4096),
	pgp_key_id integer,
	reset_hash character varying(128),
	reset_expiry timestamp without time zone,
	stripe_customer character varying(256),
	payment_cents integer DEFAULT 0 NOT NULL,
	payment_interval character varying DEFAULT 'monthly'::character varying,
	payment_due timestamp without time zone,
	welcome_emails integer DEFAULT 0 NOT NULL,
	oauth_revocation_token character varying(256),
	suspension_notice character varying(4096)
);

CREATE TABLE audit_log_entry (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	ip_address character varying(50) NOT NULL,
	event_type character varying(256) NOT NULL,
	details character varying(512)
);

CREATE TABLE invoice (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	cents integer NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	valid_thru timestamp without time zone NOT NULL,
	source character varying(256) NOT NULL
);

CREATE TABLE pgpkey (
	id serial PRIMARY KEY,
	created timestamp without time zone,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	key character varying(32768) NOT NULL,
	fingerprint bytea NOT NULL UNIQUE,
	expiration timestamp without time zone
);

ALTER TABLE "user"
	ADD CONSTRAINT user_pgp_key_id_fkey
	FOREIGN KEY (pgp_key_id) REFERENCES pgpkey(id) ON DELETE SET NULL;

CREATE TABLE sshkey (
	id serial PRIMARY KEY,
	created timestamp without time zone,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	key character varying(4096) NOT NULL,
	fingerprint character varying(512) NOT NULL UNIQUE,
	comment character varying(256),
	last_used timestamp without time zone,
	b64_key character varying(4096),
	key_type character varying(256)
);

CREATE INDEX sshkey_md5_idx ON sshkey USING btree (md5((key)::text));

CREATE TABLE user_auth_factor (
	id serial PRIMARY KEY,
	user_id integer NOT NULL UNIQUE REFERENCES "user"(id) ON DELETE CASCADE,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	factor_type character varying NOT NULL,
	secret bytea,
	extra json
);

CREATE TABLE user_notes (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	note character varying
);

CREATE TABLE reserved_usernames (
    username varchar NOT NULL
);

CREATE INDEX reserved_usernames_ix ON reserved_usernames(username);

-- OAuth 2.0
CREATE TABLE oauth2_client (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	owner_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	client_uuid uuid NOT NULL,
	client_secret_hash character varying(128) NOT NULL,
	client_secret_partial character varying(8) NOT NULL,
	redirect_url character varying,
	client_name character varying(256) NOT NULL,
	client_description character varying,
	client_url character varying,
	revoked boolean DEFAULT false NOT NULL
);

CREATE TABLE oauth2_grant (
	id serial PRIMARY KEY,
	issued timestamp without time zone NOT NULL,
	expires timestamp without time zone NOT NULL,
	comment character varying,
	token_hash character varying(128) NOT NULL,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	client_id integer REFERENCES oauth2_client(id) ON DELETE CASCADE
);

-- GraphQL webhooks
CREATE TABLE gql_profile_wh_sub (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	events webhook_event[] NOT NULL,
	url character varying NOT NULL,
	query character varying NOT NULL,
	auth_method auth_method NOT NULL,
	token_hash character varying(128),
	grants character varying,
	client_id uuid,
	expires timestamp without time zone,
	node_id character varying,
	user_id integer NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
	CONSTRAINT gql_profile_wh_sub_auth_method_check
		CHECK ((auth_method = ANY(ARRAY['OAUTH2'::auth_method, 'INTERNAL'::auth_method]))),
	CONSTRAINT gql_profile_wh_sub_check
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (token_hash IS NOT NULL))),
	CONSTRAINT gql_profile_wh_sub_check1
		CHECK (((auth_method = 'OAUTH2'::auth_method) = (expires IS NOT NULL))),
	CONSTRAINT gql_profile_wh_sub_check2
		CHECK (((auth_method = 'INTERNAL'::auth_method) = (node_id IS NOT NULL))),
	CONSTRAINT gql_profile_wh_sub_events_check
		CHECK ((array_length(events, 1) > 0))
);

CREATE INDEX gql_profile_wh_sub_token_hash_idx ON gql_profile_wh_sub USING btree (token_hash);

CREATE TABLE gql_profile_wh_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	date timestamp without time zone NOT NULL,
	event webhook_event NOT NULL,
	subscription_id integer NOT NULL
		REFERENCES gql_profile_wh_sub(id) ON DELETE CASCADE,
	request_body character varying NOT NULL,
	response_body character varying,
	response_headers character varying,
	response_status integer
);

-- Legacy OAuth (TODO: Remove these)
CREATE TABLE oauthclient (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	client_name character varying(256) NOT NULL,
	client_id character varying(16) NOT NULL,
	client_secret_hash character varying(128) NOT NULL,
	client_secret_partial character varying(8) NOT NULL,
	redirect_uri character varying(256),
	preauthorized boolean DEFAULT false NOT NULL
);

CREATE TABLE oauthscope (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	client_id integer NOT NULL REFERENCES oauthclient(id) ON DELETE CASCADE,
	name character varying(256) NOT NULL,
	description character varying(512) NOT NULL,
	write boolean NOT NULL
);

CREATE TABLE oauthtoken (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	expires timestamp without time zone NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	client_id integer REFERENCES oauthclient(id) ON DELETE CASCADE,
	token_hash character varying(128) NOT NULL,
	token_partial character varying(8) NOT NULL,
	scopes character varying(512) NOT NULL,
	comment character varying(128)
);

CREATE TABLE revocationurl (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	token_id integer NOT NULL REFERENCES oauthtoken(id),
	client_id integer NOT NULL REFERENCES oauthclient(id),
	url character varying(2048) NOT NULL
);

CREATE TABLE delegatedscope (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	updated timestamp without time zone NOT NULL,
	client_id integer NOT NULL REFERENCES oauthclient(id) ON DELETE CASCADE,
	name character varying(256) NOT NULL,
	description character varying(512) NOT NULL,
	write boolean NOT NULL
);

-- Legacy webhooks (TODO: Remove these)
CREATE TABLE user_webhook_subscription (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	url character varying(2048) NOT NULL,
	events character varying NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	token_id integer REFERENCES oauthtoken(id) ON DELETE CASCADE
);

CREATE TABLE user_webhook_delivery (
	id serial PRIMARY KEY,
	uuid uuid NOT NULL,
	created timestamp without time zone NOT NULL,
	event character varying(256) NOT NULL,
	url character varying(2048) NOT NULL,
	payload character varying(16384) NOT NULL,
	payload_headers character varying(16384) NOT NULL,
	response character varying(16384),
	response_status integer NOT NULL,
	response_headers character varying(16384),
	subscription_id integer REFERENCES user_webhook_subscription(id) ON DELETE CASCADE
);

CREATE TABLE webhook_subscription (
	id serial PRIMARY KEY,
	created timestamp without time zone NOT NULL,
	url character varying(2048) NOT NULL,
	events character varying NOT NULL,
	user_id integer REFERENCES "user"(id) ON DELETE CASCADE,
	client_id integer REFERENCES oauthclient(id) ON DELETE CASCADE
);