Compare commits

...

No commits in common. "main" and "migrations" have entirely different histories.

182 changed files with 9473 additions and 1415 deletions

View File

@ -1,3 +0,0 @@
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DB=dnim

View File

@ -1,2 +0,0 @@
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password

View File

@ -1,23 +0,0 @@
FROM node:20-bookworm
ENV PIP_BREAK_SYSTEM_PACKAGES 1
RUN apt-get update && apt-get upgrade -y
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata \
&& apt-get install -y curl ca-certificates gnupg postgresql-common \
&& YES=y sh /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh \
&& apt-get install -y postgresql-15
RUN apt-get update \
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& chmod a+r /etc/apt/keyrings/docker.gpg \
&& echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \
&& apt-get install -y docker-ce-cli docker-compose-plugin
RUN set -x \
&& apt-get install -y python3 python3-pip \
&& pip install psycopg2-binary \
&& pip install pg8000 \
&& pip install migra

View File

@ -1,44 +0,0 @@
name: 'gen-migrations'
on: { push: { branches: ['main'] } }
jobs:
gen-migrations:
if: |
!startsWith(gitea.event.head_commit.message, 'chore')
container:
image: 'git.orionkindel.com/dnim/db-ci-runner:specified-policeman-saw-kindly'
volumes:
- '/run/user/1001/docker.sock:/run/user/1001/docker.sock'
- '/var/run/postgresql:/var/run/postgresql'
steps:
- uses: 'actions/checkout@v3'
with: { fetch-depth: 0, submodules: 'recursive', token: '${{ secrets._GITEA_TOKEN }}' }
- name: 'git config'
run: |
git config --global user.email 'noreply@dnim.org'
git config --global user.name '🤖'
- name: 'update migrations submodule (and push if needed)'
run: |
set -ex
git fetch --all --recurse-submodules
git submodule update --init --remote migrations;
git add -A
git commit -m 'chore: update migrations' || true
git push || true
- name: 'gen migrations (and push if needed)'
run: |
set -ex
./scripts/gen_migrations.sh
if ! (git diff-index --quiet HEAD --ignore-submodules=none); then
cd migrations
git switch migrations
git add -A
git commit -m 'chore: babe wake up new migrations just dropped'
git push -u origin migrations
cd ../
git add -A
git commit -m 'chore: update migrations'
git push
fi
env:
DOCKER_HOST: 'unix:///run/user/1001/docker.sock' # HACK: rootless docker on gitea action runner

View File

@ -1,15 +0,0 @@
name: 'migrate-devel'
on: { push: { branches: ['main'], paths: ['migrations'] } }
jobs:
migrate-devel:
container:
image: 'git.orionkindel.com/dnim/db-ci-runner:specified-policeman-saw-kindly'
volumes: ['/run/user/1001/docker.sock:/run/user/1001/docker.sock']
steps:
- uses: 'actions/checkout@v3'
with: { fetch-depth: 0, ref: 'main', submodules: 'recursive' }
- run: './scripts/migrate.sh'
env:
DOCKER_HOST: 'unix:///run/user/1001/docker.sock' # HACK: rootless docker on gitea action runner
POSTGRES_URI: '${{ secrets.POSTGRES_DEVEL_URI }}'

View File

@ -1,15 +0,0 @@
name: 'migrate-stage'
on: { push: { branches: ['main'], paths: ['migrations'] } }
jobs:
migrate-stage:
container:
image: 'git.orionkindel.com/dnim/db-ci-runner:specified-policeman-saw-kindly'
volumes: ['/run/user/1001/docker.sock:/run/user/1001/docker.sock']
steps:
- uses: 'actions/checkout@v3'
with: { fetch-depth: 0, ref: 'main', submodules: 'recursive' }
- run: './scripts/migrate.sh'
env:
DOCKER_HOST: 'unix:///run/user/1001/docker.sock' # HACK: rootless docker on gitea action runner
POSTGRES_URI: '${{ secrets.POSTGRES_STAGE_URI }}'

5
.gitignore vendored
View File

@ -1,5 +0,0 @@
.env
data
tmp
head
base

4
.gitmodules vendored
View File

@ -1,4 +0,0 @@
[submodule "migrations"]
branch = migrations
path = migrations
url = git@git.orionkindel.com:dnim/db

View File

@ -1 +0,0 @@
postgres 15.3

1
01ee432_to_658ad01.sql Normal file
View File

@ -0,0 +1 @@

1
032c1f7_to_3c7189c.sql Normal file
View File

@ -0,0 +1 @@

33
047a51b_to_49bed63.sql Normal file
View File

@ -0,0 +1,33 @@
alter type "public"."usr_username" rename attribute "username" to "str" cascade;
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.create_newtype_text(qualified_name text)
RETURNS void
LANGUAGE plpgsql
AS $function$
begin
execute concat('create type ', qualified_name, ' as (str text);');
execute concat( 'create function '
, qualified_name || '_to_string(val ' || qualified_name || ')'
, E' returns text language sql as \'select (val.str);\';'
);
execute concat( 'create function '
, qualified_name || '_of_string(val text)'
, ' returns ' || qualified_name || E' language sql as \'select row(val);\';'
);
end;
$function$
;
CREATE OR REPLACE FUNCTION public.usr_username_of_string(val text)
RETURNS usr_username
LANGUAGE sql
AS $function$select row(val);$function$
;
CREATE OR REPLACE FUNCTION public.usr_username_to_string(val usr_username)
RETURNS text
LANGUAGE sql
AS $function$select (val.str);$function$
;

1
04ffd5d_to_d89a4fa.sql Normal file
View File

@ -0,0 +1 @@

1
0502154_to_87f7a4d.sql Normal file
View File

@ -0,0 +1 @@

1
053511d_to_70a8bca.sql Normal file
View File

@ -0,0 +1 @@

1
0728ba9_to_442d4e8.sql Normal file
View File

@ -0,0 +1 @@

192
0b9c597_to_0502154.sql Normal file
View File

@ -0,0 +1,192 @@
create type "public"."usr_session_device" as enum ('linux', 'macos', 'win', 'android', 'ios', 'other');
create type "public"."usr_tag_or_email" as ("str" text);
CREATE OR REPLACE FUNCTION public.usr_tag_or_email_of_string(val text)
RETURNS usr_tag_or_email
LANGUAGE sql
AS $function$select row(val);$function$
;
CREATE OR REPLACE FUNCTION public.usr_tag_or_email_to_email(toe usr_tag_or_email)
RETURNS email
LANGUAGE plpgsql
IMMUTABLE
AS $function$
begin
if position('@' in usr_tag_or_email_to_string(toe)) > 0 then
return email_of_string(usr_tag_or_email_to_string(toe));
else
return null;
end if;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.usr_tag_or_email_to_string(val usr_tag_or_email)
RETURNS text
LANGUAGE sql
AS $function$select (val.str);$function$
;
CREATE OR REPLACE FUNCTION public.usr_tag_or_email_to_tag(toe usr_tag_or_email)
RETURNS usr_tag
LANGUAGE plpgsql
IMMUTABLE
AS $function$
begin
if usr_tag_or_email_to_email(toe) is null then
return usr_tag_of_string(usr_tag_or_email_to_string(toe));
else
return null;
end if;
end;
$function$
;
create type "public"."usr_session_key" as ("str" text);
CREATE OR REPLACE FUNCTION public.usr_session_key_of_string(val text)
RETURNS usr_session_key
LANGUAGE sql
AS $function$select row(val);$function$
;
CREATE OR REPLACE FUNCTION public.usr_session_key_to_string(val usr_session_key)
RETURNS text
LANGUAGE sql
AS $function$select (val.str);$function$
;
CREATE OR REPLACE FUNCTION public.usr_session_key_gen()
RETURNS usr_session_key
LANGUAGE sql
AS $function$
select usr_session_key_of_string(
md5(extract(epoch from now()) || gen_random_bytes(32) :: text)
);
$function$
;
create table "public"."usr_session" (
"id" integer generated always as identity not null,
"key" usr_session_key not null default usr_session_key_gen(),
"expired" boolean not null default false,
"expires_at" timestamp without time zone not null,
"usr" integer not null,
"location" text,
"device" usr_session_device,
"ip" inet
);
CREATE UNIQUE INDEX usr_session_key_key ON public.usr_session USING btree (key);
CREATE UNIQUE INDEX usr_session_pkey ON public.usr_session USING btree (id);
alter table "public"."usr_session" add constraint "usr_session_pkey" PRIMARY KEY using index "usr_session_pkey";
alter table "public"."usr_session" add constraint "usr_session_key_key" UNIQUE using index "usr_session_key_key";
alter table "public"."usr_session" add constraint "usr_session_usr_fkey" FOREIGN KEY (usr) REFERENCES usr(id) not valid;
alter table "public"."usr_session" validate constraint "usr_session_usr_fkey";
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.do_usr_session_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'public.usr_session.id is immutable' using errcode = 'restrict_violation';
elsif OLD.key <> NEW.key then
raise exception 'public.usr_session.key is immutable' using errcode = 'restrict_violation';
elsif OLD.expires_at <> NEW.expires_at then
raise exception 'public.usr_session.expires_at is immutable' using errcode = 'restrict_violation';
elsif OLD.usr <> NEW.usr then
raise exception 'public.usr_session.usr is immutable' using errcode = 'restrict_violation';
elsif OLD.location <> NEW.location then
raise exception 'public.usr_session.location is immutable' using errcode = 'restrict_violation';
elsif OLD.device <> NEW.device then
raise exception 'public.usr_session.device is immutable' using errcode = 'restrict_violation';
elsif OLD.ip <> NEW.ip then
raise exception 'public.usr_session.ip is immutable' using errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.usr_session_login(tag_or_email usr_tag_or_email, password text, remember boolean DEFAULT false, location text DEFAULT NULL::text, device usr_session_device DEFAULT NULL::usr_session_device, ip inet DEFAULT NULL::inet)
RETURNS usr_session_key
LANGUAGE plpgsql
AS $function$
declare
usr public.usr;
key public.usr_session_key;
expires_at timestamp;
begin
usr := public.usr_session_login_validate(tag_or_email, password);
if remember then
expires_at := now() + interval '1 week';
else
expires_at := now() + interval '1 hour';
end if;
insert into public.usr_session
(expires_at, usr, location, device, ip)
values
(expires_at, usr.id, location, device, ip)
returning usr_session.key
into key;
return key;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.usr_session_login_validate(tag_or_email usr_tag_or_email, password text)
RETURNS usr
LANGUAGE plpgsql
STABLE
AS $function$
declare
usr_email public.email := public.usr_tag_or_email_to_email(tag_or_email);
usr_tag public.usr_tag := public.usr_tag_or_email_to_tag(tag_or_email);
usr public.usr;
begin
select *
from public.usr as u
where u.email = usr_email
or u.tag = usr_tag
into usr;
if usr.id = 1 or usr.tag = usr_tag_of_string('system') then
raise notice 'system user may not be logged into';
raise exception 'incorrect password for user %', usr_tag_or_email_to_string(tag_or_email);
end if;
if usr is null then
-- prevent email guess bruteforces by raising the same exception
-- for invalid password and user not found
raise notice 'user % not found', usr_tag_or_email_to_string(tag_or_email);
raise exception 'incorrect password for user %', usr_tag_or_email_to_string(tag_or_email);
end if;
if not hashed_text_matches(password, usr.password) then
raise notice 'password does not match for user %', usr_tag_or_email_to_string(tag_or_email);
raise exception 'incorrect password for user %', usr_tag_or_email_to_string(tag_or_email);
end if;
return usr;
end;
$function$
;
CREATE TRIGGER trigger_usr_session_immutable_columns BEFORE UPDATE ON public.usr_session FOR EACH ROW EXECUTE FUNCTION do_usr_session_immutable_columns();

1
0d639e8_to_bfbd8ff.sql Normal file
View File

@ -0,0 +1 @@

1
0db5ca4_to_0b9c597.sql Normal file
View File

@ -0,0 +1 @@

1
0e93478_to_1590c59.sql Normal file
View File

@ -0,0 +1 @@

1
0eb9b42_to_2c288ef.sql Normal file
View File

@ -0,0 +1 @@

1
111338a_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
1590c59_to_01ee432.sql Normal file
View File

@ -0,0 +1 @@

1
1652073_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
16a20c2_to_dcd1193.sql Normal file
View File

@ -0,0 +1 @@

1
19fc181_to_d94188f.sql Normal file
View File

@ -0,0 +1 @@

91
1bf3d29_to_c83a4ce.sql Normal file
View File

@ -0,0 +1,91 @@
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.grp_admins()
RETURNS grp
LANGUAGE sql
STABLE
AS $function$select * from public.grp where tag = public.grp_tag_of_string('admins')$function$
;
CREATE OR REPLACE FUNCTION public.usr_root()
RETURNS usr
LANGUAGE sql
STABLE
AS $function$select * from public.usr where tag = public.usr_tag_of_string('root')$function$
;
CREATE OR REPLACE FUNCTION public.do_insert_usr_perm()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
admins int;
begin
admins := public.grp_admins();
insert into public.perm
(path, owner_user, owner_group, owner_user_mode, owner_group_mode, everyone_mode)
values
('/users/' || NEW.id || '/tag', NEW.id, admins, 'w', 'w', 'r')
, ('/users/' || NEW.id || '/email', NEW.id, admins, 'w', 'w', '-')
, ('/users/' || NEW.id || '/deleted', NEW.id, admins, 'w', 'w', '-')
, ('/users/' || NEW.id || '/password', NEW.id, admins, 'w', 'w', '-')
;
return new;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_usr_create_default_grp()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
new_grp int;
begin
insert into public.grp (tag)
values (grp_tag_of_string('usr_' || new.uid))
returning id into new_grp;
perform public.grp_add_member(to_grp => new_grp, add_usr => new.id);
update public.perm
set owner_user = public.usr_root()
, owner_group = public.grp_admins()
where path = '/groups/' || new_grp || '/members'
or path = '/groups/' || new_grp || '/tag';
return null;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.get_acting_usr()
RETURNS usr
LANGUAGE plpgsql
AS $function$
declare
uid text;
acting_usr public.usr;
begin
if nullif(current_setting('dnim.usr_uid', true), '') is null then
acting_usr := public.usr_root();
else
select u.*
from public.usr u
where u.uid = current_setting('dnim.usr_uid', true) :: uuid
into acting_usr;
end if;
return acting_usr;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.grp_members_admins()
RETURNS SETOF usr
LANGUAGE sql
STABLE
AS $function$select * from public.grp_members((public.grp_admins()).id)$function$
;

1
1c76bcb_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
1dae8d2_to_e758bc6.sql Normal file
View File

@ -0,0 +1 @@

440
202de79_to_5f9c3c7.sql Normal file
View File

@ -0,0 +1,440 @@
alter table "public"."usr_session" drop column "expired";
alter table "public"."usr_session" add column "remembered" boolean not null default false;
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.usr_session_touch(session usr_session_key)
RETURNS usr
LANGUAGE plpgsql
AS $function$
declare
session public.usr_session;
session_usr public.usr;
new_exp timestamp;
begin
select s
from public.usr_session s
where s.key = session
into session;
if session is null then
raise exception 'usr_session_invalid';
end if;
if session.expires_at <= now() then
raise exception 'usr_session_expired';
end if;
if session.remembered then
new_exp := now() + interval '1 week';
else
new_exp := now() + interval '1 hour';
end if;
update public.usr_session s
set s.expires_at = new_exp
where s.id = session.id;
select u.*
from public.usr u
where u.id = session.usr
into session_usr;
return session_usr;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_check_usr_tag()
RETURNS trigger
LANGUAGE plpgsql
STABLE
AS $function$
begin
if new.tag = usr_tag_of_string('root') then
raise exception 'tag_invalid';
else
return new;
end if;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_community_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.community.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.community.uid is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_grp_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.grp.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.grp.uid is immutable',
errcode = 'restrict_violation';
elsif OLD.tag <> NEW.tag then
raise exception 'immutable_field'
using detail = 'public.grp.tag is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_perm_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.perm.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.perm.uid is immutable',
errcode = 'restrict_violation';
elsif OLD.path <> NEW.path then
raise exception 'immutable_field'
using detail = 'public.perm.path is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_thread_attachment_emoji_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_emoji.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_emoji.uid is immutable',
errcode = 'restrict_violation';
elsif OLD.thread_attachment <> NEW.thread_attachment then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_emoji.thread_attachment is immutable',
errcode = 'restrict_violation';
elsif OLD.emoji <> NEW.emoji then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_emoji.emoji is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_thread_attachment_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.thread_attachment.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.thread_attachment.uid is immutable',
errcode = 'restrict_violation';
elsif OLD.thread <> NEW.thread then
raise exception 'immutable_field'
using detail = 'public.thread_attachment.thread is immutable',
errcode = 'restrict_violation';
elsif OLD.kind <> NEW.kind then
raise exception 'immutable_field'
using detail = 'public.thread_attachment.kind is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_thread_attachment_vote_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_vote.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_vote.uid is immutable',
errcode = 'restrict_violation';
elsif OLD.thread_attachment <> NEW.thread_attachment then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_vote.thread_attachment is immutable',
errcode = 'restrict_violation';
elsif OLD.direction <> NEW.direction then
raise exception 'immutable_field'
using detail = 'public.thread_attachment_vote.direction is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_thread_feed_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.thread_feed.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.thread_feed.uid is immutable',
errcode = 'restrict_violation';
elsif OLD.thread <> NEW.thread then
raise exception 'immutable_field'
using detail = 'public.thread_feed.thread is immutable',
errcode = 'restrict_violation';
elsif OLD.community <> NEW.community then
raise exception 'immutable_field'
using detail = 'public.thread_feed.community is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_thread_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.thread.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.thread.uid is immutable',
errcode = 'restrict_violation';
elsif OLD.kind <> NEW.kind then
raise exception 'immutable_field'
using detail = 'public.thread.kind is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_usr_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.usr.id is immutable',
errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'immutable_field'
using detail = 'public.usr.uid is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_usr_session_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'immutable_field'
using detail = 'public.usr_session.id is immutable',
errcode = 'restrict_violation';
elsif OLD.key <> NEW.key then
raise exception 'immutable_field'
using detail = 'public.usr_session.key is immutable',
errcode = 'restrict_violation';
elsif OLD.expires_at <> NEW.expires_at then
raise exception 'immutable_field'
using detail = 'public.usr_session.expires_at is immutable',
errcode = 'restrict_violation';
elsif OLD.usr <> NEW.usr then
raise exception 'immutable_field'
using detail = 'public.usr_session.usr is immutable',
errcode = 'restrict_violation';
elsif OLD.location <> NEW.location then
raise exception 'immutable_field'
using detail = 'public.usr_session.location is immutable',
errcode = 'restrict_violation';
elsif OLD.device <> NEW.device then
raise exception 'immutable_field'
using detail = 'public.usr_session.device is immutable',
errcode = 'restrict_violation';
elsif OLD.ip <> NEW.ip then
raise exception 'immutable_field'
using detail = 'public.usr_session.ip is immutable',
errcode = 'restrict_violation';
end if;
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.immutable(schema text, table_ text, columns text[])
RETURNS void
LANGUAGE plpgsql
AS $function$
declare
col text;
qualified_name text := concat(schema, '.', table_);
do_table_immutable_columns text := concat('do_', table_, '_immutable_columns');
trigger_table_immutable_columns text := concat('trigger_', table_, '_immutable_columns');
create_do_table_immutable_columns text;
create_trigger_table_immutable_columns text;
begin
create_do_table_immutable_columns := concat( create_do_table_immutable_columns
, 'create function ', schema, '.', do_table_immutable_columns, E'() returns trigger language plpgsql as \$\$\n'
, E'begin\n'
, ' if OLD.', columns[1], ' <> NEW.', columns[1], E' then\n'
, E' raise exception \'immutable_field\'\n'
, E' using detail = \'', qualified_name, '.', columns[1], E' is immutable\',\n'
, E' errcode = \'restrict_violation\';\n'
);
foreach col in array columns[2:] loop
create_do_table_immutable_columns := concat( create_do_table_immutable_columns
, E' elsif OLD.', col, ' <> NEW.', col, E' then\n'
, E' raise exception \'immutable_field\'\n'
, E' using detail = \'', qualified_name, '.', col, E' is immutable\',\n'
, E' errcode = \'restrict_violation\';\n'
);
end loop;
create_do_table_immutable_columns := concat( create_do_table_immutable_columns
, E' end if;\n'
, E'\n'
, E' return NEW;\n'
, E'end;\n'
, E'\$\$;\n'
);
create_trigger_table_immutable_columns := concat( create_trigger_table_immutable_columns
, 'create trigger ', trigger_table_immutable_columns, E'\n'
, 'before update on ', qualified_name, E'\n'
, 'for each row execute function ', do_table_immutable_columns, '();'
);
execute create_do_table_immutable_columns;
execute create_trigger_table_immutable_columns;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.usr_session_login(tag_or_email usr_tag_or_email, password text, remember boolean DEFAULT false, location text DEFAULT NULL::text, device usr_session_device DEFAULT NULL::usr_session_device, ip inet DEFAULT NULL::inet)
RETURNS usr_session_key
LANGUAGE plpgsql
AS $function$
declare
usr public.usr;
key public.usr_session_key := usr_session_key_gen();
expires_at timestamp;
begin
usr := public.usr_session_login_validate(tag_or_email, password);
if remember then
expires_at := now() + interval '1 week';
else
expires_at := now() + interval '1 hour';
end if;
insert into public.usr_session
(key, expires_at, usr, remembered, location, device, ip)
values
(key, expires_at, usr.id, remember, location, device, ip);
return key;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.usr_session_login_validate(tag_or_email usr_tag_or_email, password text)
RETURNS usr
LANGUAGE plpgsql
STABLE
AS $function$
declare
usr_email public.email := public.usr_tag_or_email_to_email(tag_or_email);
usr_tag public.usr_tag := public.usr_tag_or_email_to_tag(tag_or_email);
usr public.usr;
begin
select *
from public.usr as u
where u.email = usr_email
or u.tag = usr_tag
into usr;
if usr.id = 1 or usr.tag = usr_tag_of_string('root') then
raise notice 'root user may not be logged into';
raise exception 'incorrect_password';
end if;
if usr is null then
-- prevent email guess bruteforces by raising the same exception
-- for invalid password and user not found
raise notice 'user % not found', usr_tag_or_email_to_string(tag_or_email);
raise exception 'incorrect_password';
end if;
if not hashed_text_matches(password, usr.password) then
raise notice 'password does not match for user %', usr_tag_or_email_to_string(tag_or_email);
raise exception 'incorrect_password';
end if;
return usr;
end;
$function$
;

1
2080a44_to_0e93478.sql Normal file
View File

@ -0,0 +1 @@

1
21957cf_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
2345c61_to_8b08d9f.sql Normal file
View File

@ -0,0 +1 @@

1
26c2c9c_to_b9d80dd.sql Normal file
View File

@ -0,0 +1 @@

1
2704c5b_skipped.sql Normal file
View File

@ -0,0 +1 @@

View File

@ -1,30 +1,19 @@
create function public.set_acting_usr(uid text)
returns void
language plpgsql
volatile
as $$
begin
if uid is not null and uid != '' then
perform set_config('dnim.usr_uid', uid, false);
end if;
end;
$$;
set check_function_bodies = off;
create function public.unset_acting_usr()
returns void
language plpgsql
volatile
as $$
CREATE OR REPLACE FUNCTION public.unset_acting_usr()
RETURNS void
LANGUAGE plpgsql
AS $function$
begin
perform set_config('dnim.usr_uid', '', false);
end;
$$;
$function$
;
create function public.get_acting_usr()
returns public.usr
language plpgsql
volatile
as $$
CREATE OR REPLACE FUNCTION public.get_acting_usr()
RETURNS usr
LANGUAGE plpgsql
AS $function$
declare
acting_usr public.usr;
begin
@ -39,4 +28,17 @@ begin
return coalesce(acting_usr, public.usr_root());
end;
$$;
$function$
;
CREATE OR REPLACE FUNCTION public.set_acting_usr(uid text)
RETURNS void
LANGUAGE plpgsql
AS $function$
begin
if uid is not null and uid != '' then
perform set_config('dnim.usr_uid', uid, false);
end if;
end;
$function$
;

1
28b6544_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
2c288ef_to_5169bcb.sql Normal file
View File

@ -0,0 +1 @@

View File

@ -1,162 +1,311 @@
create type public.audit_kind as enum
( 'modify'
, 'delete'
, 'create'
);
alter table "public"."community_audit" drop column "prev_role_nonmember";
create type audited_column as (column_ text, type_ text);
alter table "public"."community_member" add column "community" integer not null;
create function audit(schema text, table_ text, tracked_columns audited_column[], soft_delete boolean)
returns void
language plpgsql as $$
begin
if soft_delete then
perform setup_audit_and_soft_delete(schema, table_, tracked_columns);
else
perform setup_audit_and_hard_delete(schema, table_, tracked_columns);
end if;
end;
$$;
alter table "public"."community_member_role" add column "community" integer not null;
create function setup_audit_and_soft_delete( schema text
, table_ text
, tracked_columns audited_column[]
)
returns void
language plpgsql as $$
declare
col audited_column;
audit_table text := concat(schema, '.', table_, '_audit');
do_table_audit text := concat(schema, '.do_', table_, '_audit');
do_table_soft_delete text := concat(schema, '.do_', table_, '_soft_delete');
create_audit_table text := '';
create_do_table_audit text := '';
create_do_table_soft_delete text := '';
create_trigger_table_audit text := '';
create_trigger_table_soft_delete text := '';
begin
-- create table X_audit() >>>
create_audit_table := concat( create_audit_table
, 'create table ', audit_table, E'\n'
, ' ( ', table_, ' int not null references ', table_, '(id)', E'\n'
, ' , kind public.audit_kind not null', E'\n'
, ' , actor int not null references public.usr (id)', E'\n'
);
alter table "public"."community_member" add constraint "community_member_community_fkey" FOREIGN KEY (community) REFERENCES community(id) not valid;
foreach col in array tracked_columns loop
create_audit_table := concat( create_audit_table
, E' , prev_', (col.column_), ' ', (col.type_), E' null\n'
);
end loop;
alter table "public"."community_member" validate constraint "community_member_community_fkey";
create_audit_table := concat( create_audit_table
, ' );'
);
-- <<< create table X_audit()
alter table "public"."community_member_role" add constraint "community_member_role_community_fkey" FOREIGN KEY (community) REFERENCES community(id) not valid;
-- create function do_X_soft_delete() >>>
create_do_table_soft_delete := concat( create_do_table_soft_delete
, 'create function ', do_table_soft_delete, E'() returns trigger language plpgsql as \$\$\n'
, E'begin\n'
, E' insert into ', audit_table, E'\n'
, E' (', table_, E', kind, actor)\n'
, E' values\n'
, E' (OLD.id, \'delete\', (select (public.get_acting_usr()).id));\n'
, E'\n'
, E' update ', schema, '.', table_, E'\n'
, E' set deleted = true\n'
, E' where id = OLD.id;\n'
, E'\n'
, E' return null;\n'
, E'end;\n'
, E'\$\$;'
);
-- <<< create function do_X_soft_delete()
alter table "public"."community_member_role" validate constraint "community_member_role_community_fkey";
-- create function do_X_audit() >>>
create_do_table_audit := concat( create_do_table_audit
, 'create function ', do_table_audit, E'() returns trigger language plpgsql as \$\$\n'
, E'declare\n'
, E' audit_kind public.audit_kind;\n'
, E' id int;\n'
);
set check_function_bodies = off;
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, 'prev_', (col.column_), ' ', (col.type_), E';\n'
);
end loop;
CREATE OR REPLACE FUNCTION public.do_community_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
prev_tag public.community_tag;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
prev_tag := OLD.tag;
end if;
insert into public.community_audit
(kind, community, actor, prev_tag )
values
(audit_kind, id, 1, prev_tag);
return NEW;
end;
$function$
;
create_do_table_audit := concat( create_do_table_audit
, E'begin\n'
, E' if (TG_OP = \'INSERT\') then\n'
, E' id := NEW.id;\n'
, E' audit_kind := \'create\';\n'
, E' elsif (TG_OP = \'UPDATE\') then\n'
, E' id := OLD.id;\n'
, E' audit_kind := \'modify\';\n'
);
CREATE OR REPLACE FUNCTION public.do_community_member_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
prev_usr int;
prev_role_ int;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'DELETE') then
id := OLD.id;
audit_kind := 'delete';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
prev_usr := OLD.usr;
prev_role_ := OLD.role_;
end if;
insert into public.community_member_audit
(kind, community_member, actor, prev_usr, prev_role_ )
values
(audit_kind, id, 1, prev_usr, prev_role_);
return NEW;
end;
$function$
;
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ' prev_', (col.column_), ' := OLD.', (col.column_), E';\n'
);
end loop;
CREATE OR REPLACE FUNCTION public.do_community_member_role_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
prev_title text;
prev_description text;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'DELETE') then
id := OLD.id;
audit_kind := 'delete';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
prev_title := OLD.title;
prev_description := OLD.description;
end if;
insert into public.community_member_role_audit
(kind, community_member_role, actor, prev_title, prev_description )
values
(audit_kind, id, 1, prev_title, prev_description);
return NEW;
end;
$function$
;
create_do_table_audit := concat( create_do_table_audit
, E' end if;\n'
, E' insert into ', audit_table, E'\n'
, ' (kind, ', table_, ', actor'
);
CREATE OR REPLACE FUNCTION public.do_community_member_role_immutable_columns()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if OLD.id <> NEW.id then
raise exception 'public.community_member_role.id is immutable' using errcode = 'restrict_violation';
elsif OLD.uid <> NEW.uid then
raise exception 'public.community_member_role.uid is immutable' using errcode = 'restrict_violation';
elsif OLD.community <> NEW.community then
raise exception 'public.community_member_role.community is immutable' using errcode = 'restrict_violation';
end if;
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ', prev_', (col.column_)
);
end loop;
return NEW;
end;
$function$
;
create_do_table_audit := concat( create_do_table_audit
, E' )\n'
, E' values\n'
, E' (audit_kind, id, (public.get_acting_usr()).id'
);
CREATE OR REPLACE FUNCTION public.do_community_member_role_scope_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
prev_role_ int;
prev_scope public.authz_scope;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'DELETE') then
id := OLD.id;
audit_kind := 'delete';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
prev_role_ := OLD.role_;
prev_scope := OLD.scope;
end if;
insert into public.community_member_role_scope_audit
(kind, community_member_role_scope, actor, prev_role_, prev_scope )
values
(audit_kind, id, 1, prev_role_, prev_scope);
return NEW;
end;
$function$
;
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ', prev_', (col.column_)
);
end loop;
CREATE OR REPLACE FUNCTION public.do_community_nonmember_scope_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
prev_community int;
prev_scope public.authz_scope;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'DELETE') then
id := OLD.id;
audit_kind := 'delete';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
prev_community := OLD.community;
prev_scope := OLD.scope;
end if;
insert into public.community_nonmember_scope_audit
(kind, community_nonmember_scope, actor, prev_community, prev_scope )
values
(audit_kind, id, 1, prev_community, prev_scope);
return NEW;
end;
$function$
;
create_do_table_audit := concat( create_do_table_audit
, E');\n'
, E' return NEW;\n'
, E'end;\n'
, E'\$\$;'
);
-- <<< create function do_X_audit()
CREATE OR REPLACE FUNCTION public.do_thread_attachment_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
end if;
insert into public.thread_attachment_audit
(kind, thread_attachment, actor )
values
(audit_kind, id, 1);
return NEW;
end;
$function$
;
create_trigger_table_audit := concat( 'create trigger trigger_', table_, E'_audit\n'
, 'after insert or update on ', schema, '.', table_, E'\n'
, 'for each row execute function ', do_table_audit, '();'
);
create_trigger_table_soft_delete := concat( 'create trigger trigger_', table_, E'_soft_delete\n'
, 'before delete on ', schema, '.', table_, E'\n'
, 'for each row execute function ', do_table_soft_delete, '();'
);
execute create_audit_table;
execute create_do_table_audit;
execute create_do_table_soft_delete;
execute create_trigger_table_audit;
execute create_trigger_table_soft_delete;
end;
$$;
CREATE OR REPLACE FUNCTION public.do_thread_attachment_emoji_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
end if;
insert into public.thread_attachment_emoji_audit
(kind, thread_attachment_emoji, actor )
values
(audit_kind, id, 1);
return NEW;
end;
$function$
;
create function setup_audit_and_hard_delete( schema text
, table_ text
, tracked_columns audited_column[]
)
returns void
language plpgsql as $$
CREATE OR REPLACE FUNCTION public.do_thread_attachment_vote_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
end if;
insert into public.thread_attachment_vote_audit
(kind, thread_attachment_vote, actor )
values
(audit_kind, id, 1);
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_thread_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
end if;
insert into public.thread_audit
(kind, thread, actor )
values
(audit_kind, id, 1);
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_thread_feed_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
end if;
insert into public.thread_feed_audit
(kind, thread_feed, actor )
values
(audit_kind, id, 1);
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.setup_audit_and_hard_delete(schema text, table_ text, tracked_columns audited_column[])
RETURNS void
LANGUAGE plpgsql
AS $function$
declare
col audited_column;
audit_table text := concat(schema, '.', table_, '_audit');
@ -235,7 +384,7 @@ create function setup_audit_and_hard_delete( schema text
create_do_table_audit := concat( create_do_table_audit
, E' )\n'
, E' values\n'
, E' (audit_kind, id, (public.get_acting_usr()).id'
, E' (audit_kind, id, 1'
);
foreach col in array tracked_columns loop
@ -262,4 +411,137 @@ create function setup_audit_and_hard_delete( schema text
execute create_trigger_table_audit;
execute create_trigger_table_soft_delete;
end;
$$;
$function$
;
CREATE OR REPLACE FUNCTION public.setup_audit_and_soft_delete(schema text, table_ text, tracked_columns audited_column[])
RETURNS void
LANGUAGE plpgsql
AS $function$
declare
col audited_column;
audit_table text := concat(schema, '.', table_, '_audit');
do_table_audit text := concat(schema, '.do_', table_, '_audit');
do_table_soft_delete text := concat(schema, '.do_', table_, '_soft_delete');
create_audit_table text := '';
create_do_table_audit text := '';
create_do_table_soft_delete text := '';
create_trigger_table_audit text := '';
create_trigger_table_soft_delete text := '';
begin
-- create table X_audit() >>>
create_audit_table := concat( create_audit_table
, 'create table ', audit_table, E'\n'
, ' ( ', table_, ' int not null references ', table_, '(id)', E'\n'
, ' , kind public.audit_kind not null', E'\n'
, ' , actor int not null references public.usr (id)', E'\n'
);
foreach col in array tracked_columns loop
create_audit_table := concat( create_audit_table
, E' , prev_', (col.column_), ' ', (col.type_), E' null\n'
);
end loop;
create_audit_table := concat( create_audit_table
, ' );'
);
-- <<< create table X_audit()
-- create function do_X_soft_delete() >>>
create_do_table_soft_delete := concat( create_do_table_soft_delete
, 'create function ', do_table_soft_delete, E'() returns trigger language plpgsql as \$\$\n'
, E'begin\n'
, E' insert into ', audit_table, E'\n'
, E' (', table_, E', kind, actor)\n'
, E' values\n'
, E' (OLD.id, \'delete\', 1);\n'
, E'\n'
, E' update ', schema, '.', table_, E'\n'
, E' set deleted = true\n'
, E' where id = OLD.id;\n'
, E'\n'
, E' return null;\n'
, E'end;\n'
, E'\$\$;'
);
-- <<< create function do_X_soft_delete()
-- create function do_X_audit() >>>
create_do_table_audit := concat( create_do_table_audit
, 'create function ', do_table_audit, E'() returns trigger language plpgsql as \$\$\n'
, E'declare\n'
, E' audit_kind public.audit_kind;\n'
, E' id int;\n'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, 'prev_', (col.column_), ' ', (col.type_), E';\n'
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E'begin\n'
, E' if (TG_OP = \'INSERT\') then\n'
, E' id := NEW.id;\n'
, E' audit_kind := \'create\';\n'
, E' elsif (TG_OP = \'UPDATE\') then\n'
, E' id := OLD.id;\n'
, E' audit_kind := \'modify\';\n'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ' prev_', (col.column_), ' := OLD.', (col.column_), E';\n'
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E' end if;\n'
, E' insert into ', audit_table, E'\n'
, ' (kind, ', table_, ', actor'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ', prev_', (col.column_)
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E' )\n'
, E' values\n'
, E' (audit_kind, id, 1'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ', prev_', (col.column_)
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E');\n'
, E' return NEW;\n'
, E'end;\n'
, E'\$\$;'
);
-- <<< create function do_X_audit()
create_trigger_table_audit := concat( 'create trigger trigger_', table_, E'_audit\n'
, 'after insert or update on ', schema, '.', table_, E'\n'
, 'for each row execute function ', do_table_audit, '();'
);
create_trigger_table_soft_delete := concat( 'create trigger trigger_', table_, E'_soft_delete\n'
, 'before delete on ', schema, '.', table_, E'\n'
, 'for each row execute function ', do_table_soft_delete, '();'
);
execute create_audit_table;
execute create_do_table_audit;
execute create_do_table_soft_delete;
execute create_trigger_table_audit;
execute create_trigger_table_soft_delete;
end;
$function$
;

1
309bbd5_to_0eb9b42.sql Normal file
View File

@ -0,0 +1 @@

1
3234a35_to_835faa5.sql Normal file
View File

@ -0,0 +1 @@

35
3712154_to_38893b5.sql Normal file
View File

@ -0,0 +1,35 @@
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.do_grp_add_admins()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
admins int[];
begin
admins := ( select array_agg(usr.id)
from public.grp_members_admins() as usr
);
perform public.grp_add_members( to_grp => NEW.id
, add_usrs => admins
);
return null;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.grp_members(of_grp integer)
RETURNS SETOF usr
LANGUAGE plpgsql
STABLE
AS $function$
begin
return query select u.*
from public.grp_usr gu
inner join public.usr u on gu.usr = u.id
where gu.grp = of_grp;
end;
$function$
;

1
38893b5_to_fe4288d.sql Normal file
View File

@ -0,0 +1 @@

1
3a14f00_to_d0526f0.sql Normal file
View File

@ -0,0 +1 @@

1
3c7189c_to_d5749b0.sql Normal file
View File

@ -0,0 +1 @@

1
3e57563_to_e92e56d.sql Normal file
View File

@ -0,0 +1 @@

1
43d2f59_to_c462e62.sql Normal file
View File

@ -0,0 +1 @@

1
442d4e8_to_5da43dd.sql Normal file
View File

@ -0,0 +1 @@

1
46b4bc6_to_309bbd5.sql Normal file
View File

@ -0,0 +1 @@

200
49bed63_to_7a4d728.sql Normal file
View File

@ -0,0 +1,200 @@
alter table "public"."usr" drop constraint "usr_username_key";
alter table "public"."usr_audit" drop constraint "fk_usr_audit_actor";
alter table "public"."usr_audit" drop constraint "fk_usr_audit_usr";
drop index if exists "public"."usr_username_key";
alter table "public"."usr_audit" add constraint "usr_audit_actor_fkey" FOREIGN KEY (actor) REFERENCES usr(id) not valid;
alter table "public"."usr_audit" validate constraint "usr_audit_actor_fkey";
alter table "public"."usr_audit" add constraint "usr_audit_usr_fkey" FOREIGN KEY (usr) REFERENCES usr(id) not valid;
alter table "public"."usr_audit" validate constraint "usr_audit_usr_fkey";
set check_function_bodies = off;
create type "public"."audited_column" as ("column_" text, "type_" text);
CREATE OR REPLACE FUNCTION public.setup_audit_and_soft_delete(schema text, table_ text, tracked_columns audited_column[])
RETURNS void
LANGUAGE plpgsql
AS $function$
declare
col audited_column;
audit_table text := concat(schema, '.', table_, '_audit');
do_table_audit text := concat(schema, '.do_', table_, '_audit');
do_table_soft_delete text := concat(schema, '.do_', table_, '_soft_delete');
create_audit_table text := '';
create_do_table_audit text := '';
create_do_table_soft_delete text := '';
create_trigger_table_audit text := '';
create_trigger_table_soft_delete text := '';
begin
-- create table X_audit() >>>
create_audit_table := concat( create_audit_table
, 'create table ', audit_table, E'\n'
, ' ( ', table_, ' int not null references ', table_, '(id)', E'\n'
, ' , kind public.audit_kind not null', E'\n'
, ' , actor int not null references public.usr (id)', E'\n'
);
foreach col in array tracked_columns loop
create_audit_table := concat( create_audit_table
, E' , prev_', (col.column_), ' ', (col.type_), E' null\n'
);
end loop;
create_audit_table := concat( create_audit_table
, ' );'
);
-- <<< create table X_audit()
-- create function do_X_soft_delete() >>>
create_do_table_soft_delete := concat( create_do_table_soft_delete
, 'create function ', do_table_soft_delete, E'() returns trigger language plpgsql as \$\$\n'
, E'begin\n'
, E' insert into ', audit_table, E'\n'
, E' (', table_, E', kind, actor)\n'
, E' values\n'
, E' (OLD.id, \'delete\', 1);\n'
, E'\n'
, E' update ', schema, '.', table_, E'\n'
, E' set deleted = true\n'
, E' where id = OLD.id;\n'
, E'\n'
, E' return null;\n'
, E'end;\n'
, E'\$\$;'
);
-- <<< create function do_X_soft_delete()
-- create function do_X_audit() >>>
create_do_table_audit := concat( create_do_table_audit
, 'create function ', do_table_audit, E'() returns trigger language plpgsql as \$\$\n'
, E'declare\n'
, E' audit_kind public.audit_kind;\n'
, E' id int;\n'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, 'prev_', (col.column_), ' ', (col.type_), E';\n'
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E'begin\n'
, E' if (TG_OP = \'INSERT\') then\n'
, E' id := NEW.id;\n'
, E' audit_kind := \'create\';\n'
, E' elsif (TG_OP = \'UPDATE\') then\n'
, E' id := OLD.id;\n'
, E' audit_kind := \'modify\';\n'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ' prev_', (col.column_), ' := OLD.', (col.column_), E';\n'
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E' end if;\n'
, E' insert into public.usr_audit\n'
, E' (kind, usr, actor'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ', prev_', (col.column_)
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E' )\n'
, E' values\n'
, E' (audit_kind, id, 1'
);
foreach col in array tracked_columns loop
create_do_table_audit := concat( create_do_table_audit
, ', prev_', (col.column_)
);
end loop;
create_do_table_audit := concat( create_do_table_audit
, E');\n'
, E' return NEW;\n'
, E'end;\n'
, E'\$\$;'
);
-- <<< create function do_X_audit()
create_trigger_table_audit := concat( 'create trigger trigger_', table_, E'_audit\n'
, 'after insert or update on ', schema, '.', table_, E'\n'
, 'for each row execute function ', do_table_audit, '();'
);
create_trigger_table_soft_delete := concat( 'create trigger trigger_', table_, E'_soft_delete\n'
, 'before delete on ', schema, '.', table_, E'\n'
, 'for each row execute function ', do_table_soft_delete, '();'
);
execute create_audit_table;
execute create_do_table_audit;
execute create_do_table_soft_delete;
execute create_trigger_table_audit;
execute create_trigger_table_soft_delete;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_usr_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
id int;
prev_username public.usr_username;
prev_password public.hashed_text;
prev_email public.email;
begin
if (TG_OP = 'INSERT') then
id := NEW.id;
audit_kind := 'create';
elsif (TG_OP = 'UPDATE') then
id := OLD.id;
audit_kind := 'modify';
prev_username := OLD.username;
prev_password := OLD.password;
prev_email := OLD.email;
end if;
insert into public.usr_audit
(kind, usr, actor, prev_username, prev_password, prev_email )
values
(audit_kind, id, 1, prev_username, prev_password, prev_email);
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_usr_soft_delete()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
insert into public.usr_audit
(usr, kind, actor)
values
(OLD.id, 'delete', 1);
update public.usr
set deleted = true
where id = OLD.id;
return null;
end;
$function$
;

1
4a81f5f_to_eba0043.sql Normal file
View File

@ -0,0 +1 @@

1
4b9a59a_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
508c1c2_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
5105b7f_to_c0d37ca.sql Normal file
View File

@ -0,0 +1 @@

1
5169bcb_to_6a5d100.sql Normal file
View File

@ -0,0 +1 @@

1
533493e_to_6941558.sql Normal file
View File

@ -0,0 +1 @@

1
548f60b_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
5521049_to_7825f89.sql Normal file
View File

@ -0,0 +1 @@

1
55fca68_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
56957d3_to_e6a9378.sql Normal file
View File

@ -0,0 +1 @@

1
56980bf_to_f84b8ed.sql Normal file
View File

@ -0,0 +1 @@

1
5da43dd_to_0d639e8.sql Normal file
View File

@ -0,0 +1 @@

1
5e39afb_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
5f31077_to_be07aca.sql Normal file
View File

@ -0,0 +1 @@

1
5f9c3c7_to_e4984dd.sql Normal file
View File

@ -0,0 +1 @@

1
60a672d_to_f9fa376.sql Normal file
View File

@ -0,0 +1 @@

1
629bc32_to_533493e.sql Normal file
View File

@ -0,0 +1 @@

1
658ad01_to_3712154.sql Normal file
View File

@ -0,0 +1 @@

1
65b6f0f_to_0728ba9.sql Normal file
View File

@ -0,0 +1 @@

1
65f7926_to_885e22c.sql Normal file
View File

@ -0,0 +1 @@

50
6700413_to_68cd9c5.sql Normal file
View File

@ -0,0 +1,50 @@
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.create_newtype_text(qualified_name text)
RETURNS void
LANGUAGE plpgsql
AS $function$
begin
execute concat('create type ', qualified_name, ' as (str text);');
execute concat( 'create function '
, qualified_name || '_to_string(val ' || qualified_name || ')'
, E' returns text language sql as \'select (val.str);\';'
);
execute concat( 'create function '
, qualified_name || '_of_string(val text)'
, ' returns ' || qualified_name || E' language sql as \'select row(val);\';'
);
execute concat( 'create cast '
, ' (' || qualified_name || ' as text)'
, ' with function ' || qualified_name || E'_to_string(' || qualified_name || ')'
, ' as assignment;'
);
execute concat( 'create cast '
, ' (text as ' || qualified_name || ')'
, ' with function ' || qualified_name || E'_of_string(text)'
, ' as assignment;'
);
end;
$function$
;
create cast (public.usr_tag as text) with function public.usr_tag_to_string(public.usr_tag) as assignment;
create cast (text as public.usr_tag) with function public.usr_tag_of_string(text) as assignment;
create cast (public.usr_session_key as text) with function public.usr_session_key_to_string(public.usr_session_key) as assignment;
create cast (text as public.usr_session_key) with function public.usr_session_key_of_string(text) as assignment;
create cast (public.grp_tag as text) with function public.grp_tag_to_string(public.grp_tag) as assignment;
create cast (text as public.grp_tag) with function public.grp_tag_of_string(text) as assignment;
create cast (public.community_tag as text) with function public.community_tag_to_string(public.community_tag) as assignment;
create cast (text as public.community_tag) with function public.community_tag_of_string(text) as assignment;
create cast (public.email as text) with function public.email_to_string(public.email) as assignment;
create cast (text as public.email) with function public.email_of_string(text) as assignment;
create cast (public.hashed_text as text) with function public.hashed_text_to_string(public.hashed_text) as assignment;
create cast (text as public.hashed_text) with function public.hashed_text_of_string(text) as assignment;
create cast (human_uuid.huid as text) with function human_uuid.huid_to_string(human_uuid.huid) as assignment;
create cast (text as human_uuid.huid) with function human_uuid.huid_of_string(text) as assignment;

1
68cd9c5_to_ee6390d.sql Normal file
View File

@ -0,0 +1 @@

1
6941558_to_cda6320.sql Normal file
View File

@ -0,0 +1 @@

1
6a5d100_to_b402f08.sql Normal file
View File

@ -0,0 +1 @@

1
6b1557a_to_c310921.sql Normal file
View File

@ -0,0 +1 @@

1
6d10cb3_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
6f08ab4_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
6f52cad_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
70a8bca_to_fd395c2.sql Normal file
View File

@ -0,0 +1 @@

1
72e1e0e_to_5521049.sql Normal file
View File

@ -0,0 +1 @@

62
7371374_to_94501b7.sql Normal file
View File

@ -0,0 +1,62 @@
alter table "public"."audit" drop constraint "audit_pkey";
drop index if exists "public"."audit_pkey";
drop table "public"."audit";
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.do_usr_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_kind public.audit_kind;
usr_id int;
prev_username public.usr_username := null;
prev_password public.hashed_text := null;
prev_email public.email := null;
begin
if (TG_OP = 'UPDATE') then
usr_id := OLD.id;
audit_kind := 'modify';
prev_username := OLD.username;
prev_password := OLD.password;
prev_email := OLD.email;
elsif (TG_OP = 'INSERT') then
usr_id := NEW.id;
audit_kind := 'create';
end if;
insert into public.usr_audit
(kind, usr, actor, prev_username, prev_email, prev_password )
values
-- TODO actor
(audit_kind, usr_id, usr_id, prev_username, prev_email, prev_password );
return NEW;
end;
$function$
;
CREATE OR REPLACE FUNCTION public.do_usr_soft_delete()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
audit_id int;
begin
insert into public.usr_audit
(kind, usr, actor)
values
-- TODO actor
('delete' :: public.audit_kind, OLD.id, OLD.id);
update public.usr
set deleted = true
where id = OLD.id;
return null;
end;
$function$
;

1
7613dc8_to_3a14f00.sql Normal file
View File

@ -0,0 +1 @@

1
7825f89_to_994fa9d.sql Normal file
View File

@ -0,0 +1 @@

53
7a4d728_to_c19a46b.sql Normal file
View File

@ -0,0 +1,53 @@
drop function if exists "public"."hashed_text_string"(hashed hashed_text);
alter type "public"."email" rename attribute "email" to "str";
alter type "public"."hashed_text" rename attribute "hashed" to "str";
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.email_of_string(val text)
RETURNS email
LANGUAGE sql
AS $function$select row(val);$function$
;
CREATE OR REPLACE FUNCTION public.email_to_string(val email)
RETURNS text
LANGUAGE sql
AS $function$select (val.str);$function$
;
CREATE OR REPLACE FUNCTION public.hashed_text_of_string(val text)
RETURNS hashed_text
LANGUAGE sql
AS $function$select row(val);$function$
;
CREATE OR REPLACE FUNCTION public.hashed_text_to_string(val hashed_text)
RETURNS text
LANGUAGE sql
AS $function$select (val.str);$function$
;
CREATE OR REPLACE FUNCTION public.hash_text(plain text)
RETURNS hashed_text
LANGUAGE plpgsql
IMMUTABLE
AS $function$
begin
return hashed_text_of_string(crypt(plain, gen_salt('bf')));
end;
$function$
;
CREATE OR REPLACE FUNCTION public.hashed_text_matches(plain text, hashed hashed_text)
RETURNS boolean
LANGUAGE plpgsql
IMMUTABLE
AS $function$
begin
return hashed_text_to_string(hashed) = crypt(plain, hashed_text_to_string(hashed));
end;
$function$
;

1
7cda196_to_1bf3d29.sql Normal file
View File

@ -0,0 +1 @@

1
80d94f9_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
835faa5_to_3e57563.sql Normal file
View File

@ -0,0 +1 @@

1
86bb834_to_1dae8d2.sql Normal file
View File

@ -0,0 +1 @@

30
87f7a4d_to_b3de72d.sql Normal file
View File

@ -0,0 +1,30 @@
alter table "public"."usr_session" alter column "key" drop default;
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.usr_session_login(tag_or_email usr_tag_or_email, password text, remember boolean DEFAULT false, location text DEFAULT NULL::text, device usr_session_device DEFAULT NULL::usr_session_device, ip inet DEFAULT NULL::inet)
RETURNS usr_session_key
LANGUAGE plpgsql
AS $function$
declare
usr public.usr;
key public.usr_session_key := usr_session_key_gen();
expires_at timestamp;
begin
usr := public.usr_session_login_validate(tag_or_email, password);
if remember then
expires_at := now() + interval '1 week';
else
expires_at := now() + interval '1 hour';
end if;
insert into public.usr_session
(key, expires_at, usr, location, device, ip)
values
(key, expires_at, usr.id, location, device, ip);
return key;
end;
$function$
;

1
885e22c_to_27a69a5.sql Normal file
View File

@ -0,0 +1 @@

1
89df399_to_bba7d98.sql Normal file
View File

@ -0,0 +1 @@

4812
8b08d9f_to_4a81f5f.sql Normal file

File diff suppressed because it is too large Load Diff

1
8b648a8_to_89df399.sql Normal file
View File

@ -0,0 +1 @@

1
9226df1_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
93e81ad_to_16a20c2.sql Normal file
View File

@ -0,0 +1 @@

32
94501b7_to_047a51b.sql Normal file
View File

@ -0,0 +1,32 @@
create extension if not exists "pgcrypto" with schema "public" version '1.3';
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.hash_text(plain text)
RETURNS hashed_text
LANGUAGE plpgsql
IMMUTABLE
AS $function$
begin
return row(crypt(plain, gen_salt('bf')));
end;
$function$
;
CREATE OR REPLACE FUNCTION public.hashed_text_matches(plain text, hashed hashed_text)
RETURNS boolean
LANGUAGE plpgsql
IMMUTABLE
AS $function$
begin
return hashed_text_string(hashed) = crypt(plain, hashed_text_string(hashed));
end;
$function$
;
CREATE OR REPLACE FUNCTION public.hashed_text_string(hashed hashed_text)
RETURNS text
LANGUAGE sql
IMMUTABLE
AS $function$select (hashed.hashed);$function$
;

1
94ab122_to_9abd45a.sql Normal file
View File

@ -0,0 +1 @@

1
9569e36_to_da31970.sql Normal file
View File

@ -0,0 +1 @@

1
96937df_skipped.sql Normal file
View File

@ -0,0 +1 @@

1
9929ee9_skipped.sql Normal file
View File

@ -0,0 +1 @@

Some files were not shown because too many files have changed in this diff Show More