- Postgrest
- PostgreSql
- Elixir
connect to rabbitmq using amqp
{:ok, connection} = AMQP.Connection.open(host: "localhost", port: 5672, virtual_host: "/", username: "admin", password: "adminpass")
{:ok, %AMQP.Connection{pid: #PID<0.251.0>}}
-
RabbitMQ
-
Pgbridge Nota: investigar https://github.com/eulerto/wal2json
-
PG Migrator: https://github.com/aphel-bilisim-hizmetleri/pg-migrator
#### Strange settings
Is this needed ?
SET default_transaction_read_only = off;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;
-- Roles
CREATE ROLE anonymous;
CREATE ROLE api;
CREATE ROLE webuser;
CREATE ROLE authenticator;
-- Role memberships
GRANT anonymous TO authenticator;
GRANT api TO current_user;
GRANT webuser TO authenticator;
CREATE SCHEMA api;
CREATE SCHEMA auth;
CREATE SCHEMA data;
CREATE SCHEMA pgjwt;
CREATE SCHEMA rabbitmq;
CREATE SCHEMA request;
CREATE SCHEMA settings;
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
CREATE TYPE api.session AS (
me json,
token text
);
CREATE TYPE api."user" AS (
id integer,
name text,
email text,
role text
);
CREATE TYPE data.user_role AS ENUM (
'webuser'
);
CREATE TYPE public._time_trial_type AS (
a_time numeric
);
-- Name: login(text, text); Type: FUNCTION; Schema: api; Owner: superuser
CREATE FUNCTION api.login(email text, password text) RETURNS api.session
LANGUAGE plpgsql STABLE SECURITY DEFINER
AS $_$
declare
usr record;
usr_api record;
result record;
begin
EXECUTE format(
' select row_to_json(u.*) as j'
' from %I."user" as u'
' where u.email = $1 and u.password = public.crypt($2, u.password)'
, quote_ident(settings.get('auth.data-schema')))
INTO usr
USING $1, $2;
if usr is NULL then
raise exception 'invalid email/password';
else
EXECUTE format(
' select json_populate_record(null::%I."user", $1) as r'
, quote_ident(settings.get('auth.api-schema')))
INTO usr_api
USING usr.j;
result = (
row_to_json(usr_api.r),
auth.sign_jwt(auth.get_jwt_payload(usr.j))
);
return result;
end if;
end
$_$;
-- Name: me(); Type: FUNCTION; Schema: api; Owner: superuser
CREATE FUNCTION api.me() RETURNS api."user"
LANGUAGE plpgsql STABLE SECURITY DEFINER
AS $_$
declare
usr record;
begin
EXECUTE format(
' select row_to_json(u.*) as j'
' from %I."user" as u'
' where id = $1'
, quote_ident(settings.get('auth.data-schema')))
INTO usr
USING request.user_id();
EXECUTE format(
'select json_populate_record(null::%I."user", $1) as r'
, quote_ident(settings.get('auth.api-schema')))
INTO usr
USING usr.j;
return usr.r;
end
$_$;
-- Name: refresh_token(); Type: FUNCTION; Schema: api; Owner: superuser
CREATE FUNCTION api.refresh_token() RETURNS text
LANGUAGE plpgsql STABLE SECURITY DEFINER
AS $_$
declare
usr record;
token text;
begin
EXECUTE format(
' select row_to_json(u.*) as j'
' from %I."user" as u'
' where u.id = $1'
, quote_ident(settings.get('auth.data-schema')))
INTO usr
USING request.user_id();
if usr is NULL then
raise exception 'user not found';
else
select auth.sign_jwt(auth.get_jwt_payload(usr.j))
into token;
return token;
end if;
end
$_$;
-- Name: signup(text, text, text); Type: FUNCTION; Schema: api; Owner: superuser
CREATE FUNCTION api.signup(name text, email text, password text) RETURNS api.session
LANGUAGE plpgsql SECURITY DEFINER
AS $_$
declare
usr record;
result record;
usr_api record;
begin
EXECUTE format(
' insert into %I."user" as u'
' (name, email, password) values'
' ($1, $2, $3)'
' returning row_to_json(u.*) as j'
, quote_ident(settings.get('auth.data-schema')))
INTO usr
USING $1, $2, $3;
EXECUTE format(
' select json_populate_record(null::%I."user", $1) as r'
, quote_ident(settings.get('auth.api-schema')))
INTO usr_api
USING usr.j;
result := (
row_to_json(usr_api.r),
auth.sign_jwt(auth.get_jwt_payload(usr.j))
);
return result;
end
$_$;
-- Name: encrypt_pass(); Type: FUNCTION; Schema: auth; Owner: superuser
CREATE FUNCTION auth.encrypt_pass() RETURNS trigger
LANGUAGE plpgsql
AS $$
begin
if new.password is not null then
new.password = public.crypt(new.password, public.gen_salt('bf'));
end if;
return new;
end
$$;
-- Name: get_jwt_payload(json); Type: FUNCTION; Schema: auth; Owner: superuser
CREATE FUNCTION auth.get_jwt_payload(json) RETURNS json
LANGUAGE sql STABLE
AS $_$
select json_build_object(
'role', $1->'role',
'user_id', $1->'id',
'exp', extract(epoch from now())::integer + settings.get('jwt_lifetime')::int -- token expires in 1 hour
)
$_$;
-- Name: set_auth_endpoints_privileges(text, text, text[]); Type: FUNCTION; Schema: auth; Owner: superuser
CREATE FUNCTION auth.set_auth_endpoints_privileges(schema text, anonymous text, roles text[]) RETURNS void
LANGUAGE plpgsql
AS $$
declare r record;
begin
execute 'grant execute on function ' || quote_ident(schema) || '.login(text,text) to ' || quote_ident(anonymous);
execute 'grant execute on function ' || quote_ident(schema) || '.signup(text,text,text) to ' || quote_ident(anonymous);
for r in
select unnest(roles) as role
loop
execute 'grant execute on function ' || quote_ident(schema) || '.me() to ' || quote_ident(r.role);
execute 'grant execute on function ' || quote_ident(schema) || '.login(text,text) to ' || quote_ident(r.role);
execute 'grant execute on function ' || quote_ident(schema) || '.refresh_token() to ' || quote_ident(r.role);
end loop;
end;
$$;
-- Name: sign_jwt(json); Type: FUNCTION; Schema: auth; Owner: superuser
CREATE FUNCTION auth.sign_jwt(json) RETURNS text
LANGUAGE sql STABLE
AS $_$
select pgjwt.sign($1, settings.get('jwt_secret'))
$_$;
-- Name: algorithm_sign(text, text, text); Type: FUNCTION; Schema: pgjwt; Owner: superuser
CREATE FUNCTION pgjwt.algorithm_sign(signables text, secret text, algorithm text) RETURNS text
LANGUAGE sql
AS $$
WITH
alg AS (
SELECT CASE
WHEN algorithm = 'HS256' THEN 'sha256'
WHEN algorithm = 'HS384' THEN 'sha384'
WHEN algorithm = 'HS512' THEN 'sha512'
ELSE '' END) -- hmac throws error
SELECT pgjwt.url_encode(public.hmac(signables, secret, (select * FROM alg)));
$$;
-- Name: sign(json, text, text); Type: FUNCTION; Schema: pgjwt; Owner: superuser
CREATE FUNCTION pgjwt.sign(payload json, secret text, algorithm text DEFAULT 'HS256'::text) RETURNS text
LANGUAGE sql
AS $$
WITH
header AS (
SELECT pgjwt.url_encode(convert_to('{"alg":"' || algorithm || '","typ":"JWT"}', 'utf8'))
),
payload AS (
SELECT pgjwt.url_encode(convert_to(payload::text, 'utf8'))
),
signables AS (
SELECT (SELECT * FROM header) || '.' || (SELECT * FROM payload)
)
SELECT
(SELECT * FROM signables)
|| '.' ||
pgjwt.algorithm_sign((SELECT * FROM signables), secret, algorithm);
$$;
-- Name: url_decode(text); Type: FUNCTION; Schema: pgjwt; Owner: superuser
CREATE FUNCTION pgjwt.url_decode(data text) RETURNS bytea
LANGUAGE sql
AS $$
WITH t AS (SELECT translate(data, '-_', '+/')),
rem AS (SELECT length((SELECT * FROM t)) % 4) -- compute padding size
SELECT decode(
(SELECT * FROM t) ||
CASE WHEN (SELECT * FROM rem) > 0
THEN repeat('=', (4 - (SELECT * FROM rem)))
ELSE '' END,
'base64');
$$;
-- Name: url_encode(bytea); Type: FUNCTION; Schema: pgjwt; Owner: superuser
CREATE FUNCTION pgjwt.url_encode(data bytea) RETURNS text
LANGUAGE sql
AS $$
SELECT translate(encode(data, 'base64'), E'+/=\n', '-_');
$$;
-- Name: verify(text, text, text); Type: FUNCTION; Schema: pgjwt; Owner: superuser
CREATE FUNCTION pgjwt.verify(token text, secret text, algorithm text DEFAULT 'HS256'::text) RETURNS TABLE(header json, payload json, valid boolean)
LANGUAGE sql
AS $$
SELECT
convert_from(pgjwt.url_decode(r[1]), 'utf8')::json AS header,
convert_from(pgjwt.url_decode(r[2]), 'utf8')::json AS payload,
r[3] = pgjwt.algorithm_sign(r[1] || '.' || r[2], secret, algorithm) AS valid
FROM regexp_split_to_array(token, '\.') r;
$$;
-- Name: on_row_change(); Type: FUNCTION; Schema: rabbitmq; Owner: superuser
CREATE FUNCTION rabbitmq.on_row_change() RETURNS trigger
LANGUAGE plpgsql STABLE
AS $$
declare
routing_key text;
row jsonb;
config jsonb;
excluded_columns text[];
col text;
begin
routing_key := 'row_change'
'.table-'::text || TG_TABLE_NAME::text ||
'.event-'::text || TG_OP::text;
if (TG_OP = 'DELETE') then
row := row_to_json(old)::jsonb;
elsif (TG_OP = 'UPDATE') then
row := row_to_json(new)::jsonb;
elsif (TG_OP = 'INSERT') then
row := row_to_json(new)::jsonb;
end if;
-- decide what row columns to send based on the config parameter
-- there is a 8000 byte hard limit on the payload size so sending many big columns is not possible
if ( TG_NARGS = 1 ) then
config := TG_ARGV[0];
if (config ? 'include') then
--excluded_columns := ARRAY(SELECT unnest(jsonb_object_keys(row)::text[]) EXCEPT SELECT unnest( array(select jsonb_array_elements_text(config->'include')) ));
-- this is a diff between two arrays
excluded_columns := array(
-- array of all row columns
select unnest(
array(select jsonb_object_keys(row))
)
except
-- array of included columns
select unnest(
array(select jsonb_array_elements_text(config->'include'))
)
);
end if;
if (config ? 'exclude') then
excluded_columns := array(select jsonb_array_elements_text(config->'exclude'));
end if;
if (current_setting('server_version_num')::int >= 100000) then
row := row - excluded_columns;
else
FOREACH col IN ARRAY excluded_columns
LOOP
row := row - col;
END LOOP;
end if;
end if;
perform rabbitmq.send_message('events', routing_key, row::text);
return null;
end;
$$;
-- Name: send_message(text, text, text); Type: FUNCTION; Schema: rabbitmq; Owner: superuser
CREATE FUNCTION rabbitmq.send_message(channel text, routing_key text, message text) RETURNS void
LANGUAGE sql STABLE
AS $$
select pg_notify(
channel,
routing_key || '|' || message
);
$$;
-- Name: cookie(text); Type: FUNCTION; Schema: request; Owner: superuser
CREATE FUNCTION request.cookie(c text) RETURNS text
LANGUAGE sql STABLE
AS $$
select request.env_var('request.cookie.' || c);
$$;
-- Name: env_var(text); Type: FUNCTION; Schema: request; Owner: superuser
CREATE FUNCTION request.env_var(v text) RETURNS text
LANGUAGE sql STABLE
AS $$
select current_setting(v, true);
$$;
-- Name: header(text); Type: FUNCTION; Schema: request; Owner: superuser
CREATE FUNCTION request.header(h text) RETURNS text
LANGUAGE sql STABLE
AS $$
select request.env_var('request.header.' || h);
$$;
-- Name: jwt_claim(text); Type: FUNCTION; Schema: request; Owner: superuser
CREATE FUNCTION request.jwt_claim(c text) RETURNS text
LANGUAGE sql STABLE
AS $$
select request.env_var('request.jwt.claim.' || c);
$$;
-- Name: user_id(); Type: FUNCTION; Schema: request; Owner: superuser
CREATE FUNCTION request.user_id() RETURNS integer
LANGUAGE sql STABLE
AS $$
select
case request.jwt_claim('user_id')
when '' then 0
else request.jwt_claim('user_id')::int
end
$$;
-- Name: user_role(); Type: FUNCTION; Schema: request; Owner: superuser
CREATE FUNCTION request.user_role() RETURNS text
LANGUAGE sql STABLE
AS $$
select request.jwt_claim('role')::text;
$$;
-- Name: get(text); Type: FUNCTION; Schema: settings; Owner: superuser
CREATE FUNCTION settings.get(text) RETURNS text
LANGUAGE sql STABLE SECURITY DEFINER
AS $$
select value from settings.secrets where key = $1
$$;
-- Name: set(text, text); Type: FUNCTION; Schema: settings; Owner: superuser
CREATE FUNCTION settings.set(text, text) RETURNS void
LANGUAGE sql SECURITY DEFINER
AS $$
insert into settings.secrets (key, value)
values ($1, $2)
on conflict (key) do update
set value = $2;
$$;
SET default_tablespace = '';
SET default_with_oids = false;
-- Name: todo; Type: TABLE; Schema: data; Owner: superuser
CREATE TABLE data.todo (
id integer NOT NULL,
todo text NOT NULL,
private boolean DEFAULT true,
owner_id integer DEFAULT request.user_id()
);
-- Name: todos; Type: VIEW; Schema: api; Owner: api
CREATE VIEW api.todos AS
SELECT todo.id,
todo.todo,
todo.private,
(todo.owner_id = request.user_id()) AS mine
FROM data.todo;
ALTER TABLE api.todos OWNER TO api;
-- Name: todo_id_seq; Type: SEQUENCE; Schema: data; Owner: superuser
CREATE SEQUENCE data.todo_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE data.todo_id_seq OWNED BY data.todo.id;
-- Name: user; Type: TABLE; Schema: data; Owner: superuser
CREATE TABLE data."user" (
id integer NOT NULL,
name text NOT NULL,
email text NOT NULL,
password text NOT NULL,
role data.user_role DEFAULT (settings.get('auth.default-role'::text))::data.user_role NOT NULL,
CONSTRAINT user_email_check CHECK ((email ~* '^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+[.][A-Za-z]+$'::text)),
CONSTRAINT user_name_check CHECK ((length(name) > 2))
);
-- Name: user_id_seq; Type: SEQUENCE; Schema: data; Owner: superuser
CREATE SEQUENCE data.user_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE data.user_id_seq OWNED BY data."user".id;
-- Name: secrets; Type: TABLE; Schema: settings; Owner: superuser
CREATE TABLE settings.secrets (
key text NOT NULL,
value text NOT NULL
);
ALTER TABLE ONLY data.todo ALTER COLUMN id SET DEFAULT nextval('data.todo_id_seq'::regclass);
ALTER TABLE ONLY data."user" ALTER COLUMN id SET DEFAULT nextval('data.user_id_seq'::regclass);
ALTER TABLE ONLY data.todo
ADD CONSTRAINT todo_pkey PRIMARY KEY (id);
ALTER TABLE ONLY data."user"
ADD CONSTRAINT user_email_key UNIQUE (email);
ALTER TABLE ONLY data."user"
ADD CONSTRAINT user_pkey PRIMARY KEY (id);
ALTER TABLE ONLY settings.secrets
ADD CONSTRAINT secrets_pkey PRIMARY KEY (key);
-- Name: todo send_change_event; Type: TRIGGER; Schema: data; Owner: superuser
CREATE TRIGGER send_change_event AFTER INSERT OR DELETE OR UPDATE ON data.todo FOR EACH ROW EXECUTE PROCEDURE rabbitmq.on_row_change('{"include":["id","todo"]}');
-- Name: user user_encrypt_pass_trigger; Type: TRIGGER; Schema: data; Owner: superuser
CREATE TRIGGER user_encrypt_pass_trigger BEFORE INSERT OR UPDATE ON data."user" FOR EACH ROW EXECUTE PROCEDURE auth.encrypt_pass();
-- Name: todo todo_owner_id_fkey; Type: FK CONSTRAINT; Schema: data; Owner: superuser
ALTER TABLE ONLY data.todo
ADD CONSTRAINT todo_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES data."user"(id);
-- Name: todo; Type: ROW SECURITY; Schema: data; Owner: superuser
ALTER TABLE data.todo ENABLE ROW LEVEL SECURITY;
-- Name: todo todo_access_policy; Type: POLICY; Schema: data; Owner: superuser
CREATE POLICY todo_access_policy ON data.todo TO api USING ((((request.user_role() = 'webuser'::text) AND (request.user_id() = owner_id)) OR (private = false))) WITH CHECK (((request.user_role() = 'webuser'::text) AND (request.user_id() = owner_id)));
-- Name: SCHEMA api; Type: ACL; Schema: -; Owner: superuser
GRANT USAGE ON SCHEMA api TO anonymous;
GRANT USAGE ON SCHEMA api TO webuser;
GRANT USAGE ON SCHEMA rabbitmq TO PUBLIC;
GRANT USAGE ON SCHEMA request TO PUBLIC;
REVOKE ALL ON FUNCTION api.login(email text, password text) FROM PUBLIC;
GRANT ALL ON FUNCTION api.login(email text, password text) TO anonymous;
GRANT ALL ON FUNCTION api.login(email text, password text) TO webuser;
REVOKE ALL ON FUNCTION api.me() FROM PUBLIC;
GRANT ALL ON FUNCTION api.me() TO webuser;
REVOKE ALL ON FUNCTION api.refresh_token() FROM PUBLIC;
GRANT ALL ON FUNCTION api.refresh_token() TO webuser;
REVOKE ALL ON FUNCTION api.signup(name text, email text, password text) FROM PUBLIC;
GRANT ALL ON FUNCTION api.signup(name text, email text, password text) TO anonymous;
GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE data.todo TO api;
GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE api.todos TO webuser;
GRANT SELECT(id) ON TABLE api.todos TO anonymous;
GRANT SELECT(todo) ON TABLE api.todos TO anonymous;
GRANT USAGE ON SEQUENCE data.todo_id_seq TO webuser;