BOD-5 #comment Data level changes to support audit trails. Only enabled for jobs. Potentially big impact changes from this commit forward. Details logged in reference file.

This commit is contained in:
Patrick Fic
2020-03-09 16:23:07 -07:00
parent dd0562cae3
commit d1cfd9bacf
24 changed files with 399 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
CREATE SCHEMA audit;
CREATE TABLE audit_trail (
id serial PRIMARY KEY,
tstamp timestamp DEFAULT now(),
schemaname text,
tabname text,
operation text,
recordid uuid,
-- who text DEFAULT current_user,
new_val json,
old_val json,
useremail text,
bodyshopid uuid
);
-- More as an example than anything else, I wanted a function that would take two JSONB objects in PostgreSQL, and return how the left-hand side differs from the right-hand side. This means any key that is in the left but not in the right would be returned, along with any key whose value on the left is different from the right.
-- Heres a quick example of how to do this in a single SELECT. In real life, you probably want more error checking, but it shows how nice the built-in primitives are:
CREATE OR REPLACE FUNCTION json_diff(l JSONB, r JSONB) RETURNS JSONB AS
$json_diff$
SELECT jsonb_object_agg(a.key, a.value) FROM
( SELECT key, value FROM jsonb_each(l) ) a LEFT OUTER JOIN
( SELECT key, value FROM jsonb_each(r) ) b ON a.key = b.key
WHERE a.value != b.value OR b.key IS NULL;
$json_diff$
LANGUAGE sql;
CREATE OR REPLACE FUNCTION audit_trigger() RETURNS trigger AS $$
DECLARE
shopid text ;
email text;
BEGIN
select b.id, u.email INTO shopid, email from users u join associations a on u.email = a.useremail join bodyshops b on b.id = a.shopid where u.authid = current_setting('hasura.user', 't')::jsonb->>'x-hasura-user-id' and a.active = true;
IF TG_OP = 'INSERT'
THEN
INSERT INTO public.audit_trail (tabname, schemaname, operation, new_val, recordid, bodyshopid, useremail)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(NEW), NEW.id, shopid, email);
RETURN NEW;
ELSIF TG_OP = 'UPDATE'
THEN
INSERT INTO public.audit_trail (tabname, schemaname, operation, old_val, new_val, recordid, bodyshopid, useremail)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP,
json_diff(to_jsonb(OLD), to_jsonb(NEW)) , json_diff(to_jsonb(NEW), to_jsonb(OLD)), OLD.id, shopid, email);
RETURN NEW;
ELSIF TG_OP = 'DELETE'
THEN
INSERT INTO public.audit_trail (tabname, schemaname, operation, old_val, recordid, bodyshopid, useremail)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(OLD), OLD.ID, shopid, email);
RETURN OLD;
END IF;
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;
CREATE TRIGGER audit_trigger_users AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE PROCEDURE audit_trigger();

3
client/debug.log Normal file
View File

@@ -0,0 +1,3 @@
[0309/123120.472:ERROR:process_reader_win.cc(108)] process 40916 not found
[0309/123120.472:ERROR:exception_snapshot_win.cc(98)] thread ID 50448 not found in process
[0309/123120.472:ERROR:scoped_process_suspend.cc(40)] NtResumeProcess: An attempt was made to access an exiting process. (0xc000010a)

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,15 @@
- args:
cascade: false
read_only: false
sql: "CREATE TABLE audit_trail (\r\n \r\n id serial PRIMARY
KEY, \r\n \r\n created timestamp DEFAULT now(),\r\n
\r\n schemaname text,\r\n \r\n tabname text,\r\n
\r\n operation text,\r\n recordid uuid,\r\n \r\n
\ -- who text DEFAULT current_user,\r\n \r\n new_val
\ json,\r\n \r\n old_val json,\r\n
\ useremail text,\r\n bodyshopid uuid\r\n \r\n);"
type: run_sql
- args:
name: audit_trail
schema: public
type: add_existing_table_or_view

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."audit_trail" drop constraint "audit_trail_useremail_fkey";
type: run_sql

View File

@@ -0,0 +1,10 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."audit_trail"
add constraint "audit_trail_useremail_fkey"
foreign key ("useremail")
references "public"."users"
("email") on update restrict on delete restrict;
type: run_sql

View File

@@ -0,0 +1,12 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."audit_trail" drop constraint "audit_trail_useremail_fkey",
add constraint "audit_trail_useremail_fkey"
foreign key ("useremail")
references "public"."users"
("email")
on update restrict
on delete restrict;
type: run_sql

View File

@@ -0,0 +1,10 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."audit_trail" drop constraint "audit_trail_useremail_fkey",
add constraint "audit_trail_useremail_fkey"
foreign key ("useremail")
references "public"."users"
("email") on update restrict on delete restrict;
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."audit_trail" drop constraint "audit_trail_bodyshopid_fkey";
type: run_sql

View File

@@ -0,0 +1,10 @@
- args:
cascade: false
read_only: false
sql: |-
alter table "public"."audit_trail"
add constraint "audit_trail_bodyshopid_fkey"
foreign key ("bodyshopid")
references "public"."bodyshops"
("id") on update restrict on delete restrict;
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,9 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION json_diff(l JSONB, r JSONB) RETURNS JSONB AS\r\n$json_diff$\r\n
\ SELECT jsonb_object_agg(a.key, a.value) FROM\r\n ( SELECT key, value
FROM jsonb_each(l) ) a LEFT OUTER JOIN\r\n ( SELECT key, value FROM jsonb_each(r)
) b ON a.key = b.key\r\n WHERE a.value != b.value OR b.key IS NULL;\r\n$json_diff$\r\n
\ LANGUAGE sql;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,25 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION audit_trigger() RETURNS trigger AS $$\r\n \r\n
DECLARE\r\n shopid uuid ;\r\n email text;\r\n\r\n BEGIN\r\n \r\n select
b.id, u.email INTO shopid, email from users u join associations a on u.email
= a.useremail join bodyshops b on b.id = a.shopid where u.authid = current_setting('hasura.user',
't')::jsonb->>'x-hasura-user-id' and a.active = true;\r\n\r\n IF
\ TG_OP = 'INSERT'\r\n \r\n THEN\r\n \r\n INSERT
INTO public.audit_trail (tabname, schemaname, operation, new_val, recordid,
bodyshopid, useremail)\r\n \r\n VALUES (TG_RELNAME,
TG_TABLE_SCHEMA, TG_OP, row_to_json(NEW), NEW.id, shopid, email);\r\n \r\n RETURN
NEW;\r\n \r\n ELSIF TG_OP = 'UPDATE'\r\n \r\n THEN\r\n
\r\n INSERT INTO public.audit_trail (tabname, schemaname,
operation, old_val, new_val, recordid, bodyshopid, useremail)\r\n \r\n VALUES
(TG_RELNAME, TG_TABLE_SCHEMA, TG_OP,\r\n \r\n json_diff(to_jsonb(OLD),
to_jsonb(NEW)) , json_diff(to_jsonb(NEW), to_jsonb(OLD)), OLD.id, shopid,
email);\r\n \r\n RETURN NEW;\r\n \r\n ELSIF
\ TG_OP = 'DELETE'\r\n \r\n THEN\r\n \r\n INSERT
INTO public.audit_trail (tabname, schemaname, operation, old_val, recordid,
bodyshopid, useremail)\r\n \r\n VALUES (TG_RELNAME,
TG_TABLE_SCHEMA, TG_OP, row_to_json(OLD), OLD.ID, shopid, email);\r\n \r\n
\ RETURN OLD;\r\n \r\n END IF;\r\n \r\n
\ END;\r\n \r\n$$ LANGUAGE 'plpgsql' SECURITY DEFINER;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,6 @@
- args:
cascade: true
read_only: false
sql: "CREATE TRIGGER audit_trigger_jobs AFTER INSERT OR UPDATE OR DELETE ON jobs\r\n
\ FOR EACH ROW EXECUTE PROCEDURE audit_trigger();"
type: run_sql

View File

@@ -0,0 +1,30 @@
- args:
relationship: user
table:
name: audit_trail
schema: public
type: drop_relationship
- args:
relationship: bodyshop
table:
name: audit_trail
schema: public
type: drop_relationship
- args:
relationship: audit_trails
table:
name: bodyshops
schema: public
type: drop_relationship
- args:
relationship: invoices
table:
name: jobs
schema: public
type: drop_relationship
- args:
relationship: audit_trails
table:
name: users
schema: public
type: drop_relationship

View File

@@ -0,0 +1,52 @@
- args:
name: user
table:
name: audit_trail
schema: public
using:
foreign_key_constraint_on: useremail
type: create_object_relationship
- args:
name: bodyshop
table:
name: audit_trail
schema: public
using:
foreign_key_constraint_on: bodyshopid
type: create_object_relationship
- args:
name: audit_trails
table:
name: bodyshops
schema: public
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: audit_trail
schema: public
type: create_array_relationship
- args:
name: invoices
table:
name: jobs
schema: public
using:
foreign_key_constraint_on:
column: jobid
table:
name: invoices
schema: public
type: create_array_relationship
- args:
name: audit_trails
table:
name: users
schema: public
using:
foreign_key_constraint_on:
column: useremail
table:
name: audit_trail
schema: public
type: create_array_relationship

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: audit_trail
schema: public
type: drop_insert_permission

View File

@@ -0,0 +1,32 @@
- args:
permission:
allow_upsert: true
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created
- schemaname
- tabname
- operation
- recordid
- new_val
- old_val
- useremail
- bodyshopid
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: audit_trail
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: audit_trail
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,30 @@
- args:
permission:
allow_aggregations: false
columns:
- id
- new_val
- old_val
- operation
- schemaname
- tabname
- useremail
- created
- bodyshopid
- recordid
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
limit: null
role: user
table:
name: audit_trail
schema: public
type: create_select_permission

View File

@@ -0,0 +1,31 @@
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created
- schemaname
- tabname
- operation
- recordid
- new_val
- old_val
- useremail
- bodyshopid
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: audit_trail
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: audit_trail
schema: public
type: drop_insert_permission