Skip to content

Commit fd0fe16

Browse files
committed
Updating Audit Schema generation. Updating tests. Updating README.
1 parent 20dd3ac commit fd0fe16

File tree

3 files changed

+54
-51
lines changed

3 files changed

+54
-51
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ SERVER_PORT=""
6464

6565
## Setup
6666

67-
1. Create a local `app` database. `$ createdb app_local;` (audit is currently mapped to it **TODO**)
68-
2. Create a `.env.local` file on the root of the project based on `.env.example`
67+
1. Create a `.env.local` file on the root of the project based on `.env.example`
68+
2. Create a local `app` database. `$ createdb <DB_NAME>;`
6969
3. Execute `$ npm run setup:local`
7070
4. Start the development server running `$ npm run dev`
7171

7272
## Running Tests
7373

74-
1. Create a test `app` database. `$ createdb app_test;` (audit is currently mapped to it **TODO**)
75-
2. Create a `.env.test` file on the root of the project based on `.env.example`
74+
1. Create a `.env.test` file on the root of the project based on `.env.example`
75+
2. Create a test `app` database. `$ createdb <DB_NAME>;`
7676
3. Execute `$ npm run setup:test`
7777
4. Run `$ npm test`
7878

src/__tests__/packages/api/resources/users/controller.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { server } from '~/__tests__/config/helpers'
33
describe('Users Controller', () => {
44
it('should list Users', async () => {
55
const res = await server.get('/users')
6+
const { status, body } = res
67

7-
expect(res.status).toBe(200)
8-
expect(res.body.length).not.toBe(0)
8+
expect(status).toBe(200)
9+
expect(body.length >= 0).toBeTruthy()
910
})
1011
})

src/packages/database/helpers/installDatabaseAudit.ts

+47-45
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// tslint:disable:max-line-length
22

3+
const auditSchema = process.env.DB_AUDIT_SCHEMA
4+
35
export const auditSQL = `
46
-- An audit history is important on most tables. Provide an audit trigger that logs to
57
-- a dedicated audit table for the major relations.
@@ -17,10 +19,10 @@ export const auditSQL = `
1719
1820
CREATE EXTENSION IF NOT EXISTS hstore;
1921
20-
CREATE SCHEMA app_audit;
21-
REVOKE ALL ON SCHEMA app_audit FROM public;
22+
CREATE SCHEMA ${auditSchema};
23+
REVOKE ALL ON SCHEMA ${auditSchema} FROM public;
2224
23-
COMMENT ON SCHEMA app_audit IS 'Out-of-table audit/history logging tables and trigger functions';
25+
COMMENT ON SCHEMA ${auditSchema} IS 'Out-of-table audit/history logging tables and trigger functions';
2426
2527
--
2628
-- Audited data. Lots of information is available, it's just a matter of how much
@@ -39,7 +41,7 @@ COMMENT ON SCHEMA app_audit IS 'Out-of-table audit/history logging tables and tr
3941
-- you're interested in, into a temporary table where you CREATE any useful
4042
-- indexes and do your analysis.
4143
--
42-
CREATE TABLE app_audit.logged_actions (
44+
CREATE TABLE ${auditSchema}.logged_actions (
4345
event_id bigserial primary key,
4446
schema_name text not null,
4547
table_name text not null,
@@ -59,46 +61,46 @@ CREATE TABLE app_audit.logged_actions (
5961
statement_only boolean not null
6062
);
6163
62-
REVOKE ALL ON app_audit.logged_actions FROM public;
63-
64-
COMMENT ON TABLE app_audit.logged_actions IS 'History of auditable actions on audited tables, from app_audit.if_modified_func()';
65-
COMMENT ON COLUMN app_audit.logged_actions.event_id IS 'Unique identifier for each auditable event';
66-
COMMENT ON COLUMN app_audit.logged_actions.schema_name IS 'Database schema audited table for this event is in';
67-
COMMENT ON COLUMN app_audit.logged_actions.table_name IS 'Non-schema-qualified table name of table event occurred in';
68-
COMMENT ON COLUMN app_audit.logged_actions.relid IS 'Table OID. Changes with drop/create. Get with ''tablename''::regclass';
69-
COMMENT ON COLUMN app_audit.logged_actions.session_user_name IS 'Login / session user whose statement caused the audited event';
70-
COMMENT ON COLUMN app_audit.logged_actions.action_tstamp_tx IS 'Transaction start timestamp for tx in which audited event occurred';
71-
COMMENT ON COLUMN app_audit.logged_actions.action_tstamp_stm IS 'Statement start timestamp for tx in which audited event occurred';
72-
COMMENT ON COLUMN app_audit.logged_actions.action_tstamp_clk IS 'Wall clock time at which audited event''s trigger call occurred';
73-
COMMENT ON COLUMN app_audit.logged_actions.transaction_id IS 'Identifier of transaction that made the change. May wrap, but unique paired with action_tstamp_tx.';
74-
COMMENT ON COLUMN app_audit.logged_actions.client_addr IS 'IP address of client that issued query. Null for unix domain socket.';
75-
COMMENT ON COLUMN app_audit.logged_actions.client_port IS 'Remote peer IP port address of client that issued query. Undefined for unix socket.';
76-
COMMENT ON COLUMN app_audit.logged_actions.client_query IS 'Top-level query that caused this auditable event. May be more than one statement.';
77-
COMMENT ON COLUMN app_audit.logged_actions.application_name IS 'Application name set when this audit event occurred. Can be changed in-session by client.';
78-
COMMENT ON COLUMN app_audit.logged_actions.action IS 'Action type; I = insert, D = delete, U = update, T = truncate';
79-
COMMENT ON COLUMN app_audit.logged_actions.row_data IS 'Record value. Null for statement-level trigger. For INSERT this is the new tuple. For DELETE and UPDATE it is the old tuple.';
80-
COMMENT ON COLUMN app_audit.logged_actions.changed_fields IS 'New values of fields changed by UPDATE. Null except for row-level UPDATE events.';
81-
COMMENT ON COLUMN app_audit.logged_actions.statement_only IS '''t'' if audit event is from an FOR EACH STATEMENT trigger, ''f'' for FOR EACH ROW';
82-
83-
CREATE INDEX logged_actions_relid_idx ON app_audit.logged_actions(relid);
84-
CREATE INDEX logged_actions_action_tstamp_tx_stm_idx ON app_audit.logged_actions(action_tstamp_stm);
85-
CREATE INDEX logged_actions_action_idx ON app_audit.logged_actions(action);
86-
87-
CREATE OR REPLACE FUNCTION app_audit.if_modified_func() RETURNS TRIGGER AS $body$
64+
REVOKE ALL ON ${auditSchema}.logged_actions FROM public;
65+
66+
COMMENT ON TABLE ${auditSchema}.logged_actions IS 'History of auditable actions on audited tables, from ${auditSchema}.if_modified_func()';
67+
COMMENT ON COLUMN ${auditSchema}.logged_actions.event_id IS 'Unique identifier for each auditable event';
68+
COMMENT ON COLUMN ${auditSchema}.logged_actions.schema_name IS 'Database schema audited table for this event is in';
69+
COMMENT ON COLUMN ${auditSchema}.logged_actions.table_name IS 'Non-schema-qualified table name of table event occurred in';
70+
COMMENT ON COLUMN ${auditSchema}.logged_actions.relid IS 'Table OID. Changes with drop/create. Get with ''tablename''::regclass';
71+
COMMENT ON COLUMN ${auditSchema}.logged_actions.session_user_name IS 'Login / session user whose statement caused the audited event';
72+
COMMENT ON COLUMN ${auditSchema}.logged_actions.action_tstamp_tx IS 'Transaction start timestamp for tx in which audited event occurred';
73+
COMMENT ON COLUMN ${auditSchema}.logged_actions.action_tstamp_stm IS 'Statement start timestamp for tx in which audited event occurred';
74+
COMMENT ON COLUMN ${auditSchema}.logged_actions.action_tstamp_clk IS 'Wall clock time at which audited event''s trigger call occurred';
75+
COMMENT ON COLUMN ${auditSchema}.logged_actions.transaction_id IS 'Identifier of transaction that made the change. May wrap, but unique paired with action_tstamp_tx.';
76+
COMMENT ON COLUMN ${auditSchema}.logged_actions.client_addr IS 'IP address of client that issued query. Null for unix domain socket.';
77+
COMMENT ON COLUMN ${auditSchema}.logged_actions.client_port IS 'Remote peer IP port address of client that issued query. Undefined for unix socket.';
78+
COMMENT ON COLUMN ${auditSchema}.logged_actions.client_query IS 'Top-level query that caused this auditable event. May be more than one statement.';
79+
COMMENT ON COLUMN ${auditSchema}.logged_actions.application_name IS 'Application name set when this audit event occurred. Can be changed in-session by client.';
80+
COMMENT ON COLUMN ${auditSchema}.logged_actions.action IS 'Action type; I = insert, D = delete, U = update, T = truncate';
81+
COMMENT ON COLUMN ${auditSchema}.logged_actions.row_data IS 'Record value. Null for statement-level trigger. For INSERT this is the new tuple. For DELETE and UPDATE it is the old tuple.';
82+
COMMENT ON COLUMN ${auditSchema}.logged_actions.changed_fields IS 'New values of fields changed by UPDATE. Null except for row-level UPDATE events.';
83+
COMMENT ON COLUMN ${auditSchema}.logged_actions.statement_only IS '''t'' if audit event is from an FOR EACH STATEMENT trigger, ''f'' for FOR EACH ROW';
84+
85+
CREATE INDEX logged_actions_relid_idx ON ${auditSchema}.logged_actions(relid);
86+
CREATE INDEX logged_actions_action_tstamp_tx_stm_idx ON ${auditSchema}.logged_actions(action_tstamp_stm);
87+
CREATE INDEX logged_actions_action_idx ON ${auditSchema}.logged_actions(action);
88+
89+
CREATE OR REPLACE FUNCTION ${auditSchema}.if_modified_func() RETURNS TRIGGER AS $body$
8890
DECLARE
89-
audit_row app_audit.logged_actions;
91+
audit_row ${auditSchema}.logged_actions;
9092
include_values boolean;
9193
log_diffs boolean;
9294
h_old hstore;
9395
h_new hstore;
9496
excluded_cols text[] = ARRAY[]::text[];
9597
BEGIN
9698
IF TG_WHEN <> 'AFTER' THEN
97-
RAISE EXCEPTION 'app_audit.if_modified_func() may only run as an AFTER trigger';
99+
RAISE EXCEPTION '${auditSchema}.if_modified_func() may only run as an AFTER trigger';
98100
END IF;
99101
100102
audit_row = ROW(
101-
nextval('app_audit.logged_actions_event_id_seq'), -- event_id
103+
nextval('${auditSchema}.logged_actions_event_id_seq'), -- event_id
102104
TG_TABLE_SCHEMA::text, -- schema_name
103105
TG_TABLE_NAME::text, -- table_name
104106
TG_RELID, -- relation OID for much quicker searches
@@ -138,18 +140,18 @@ BEGIN
138140
ELSIF (TG_LEVEL = 'STATEMENT' AND TG_OP IN ('INSERT','UPDATE','DELETE','TRUNCATE')) THEN
139141
audit_row.statement_only = 't';
140142
ELSE
141-
RAISE EXCEPTION '[app_audit.if_modified_func] - Trigger func added as trigger for unhandled case: %, %',TG_OP, TG_LEVEL;
143+
RAISE EXCEPTION '[${auditSchema}.if_modified_func] - Trigger func added as trigger for unhandled case: %, %',TG_OP, TG_LEVEL;
142144
RETURN NULL;
143145
END IF;
144-
INSERT INTO app_audit.logged_actions VALUES (audit_row.*);
146+
INSERT INTO ${auditSchema}.logged_actions VALUES (audit_row.*);
145147
RETURN NULL;
146148
END;
147149
$body$
148150
LANGUAGE plpgsql
149151
SECURITY DEFINER
150152
SET search_path = pg_catalog, public, extensions;
151153
152-
COMMENT ON FUNCTION app_audit.if_modified_func() IS $body$
154+
COMMENT ON FUNCTION ${auditSchema}.if_modified_func() IS $body$
153155
Track changes to a table at the statement and/or row level.
154156
155157
Optional parameters to trigger in CREATE TRIGGER call:
@@ -180,7 +182,7 @@ cannot obtain the active role because it is reset by the SECURITY DEFINER invoca
180182
of the audit trigger its self.
181183
$body$;
182184
183-
CREATE OR REPLACE FUNCTION app_audit.audit_table(target_table regclass, audit_rows boolean, audit_query_text boolean, ignored_cols text[]) RETURNS void AS $body$
185+
CREATE OR REPLACE FUNCTION ${auditSchema}.audit_table(target_table regclass, audit_rows boolean, audit_query_text boolean, ignored_cols text[]) RETURNS void AS $body$
184186
DECLARE
185187
stm_targets text = 'INSERT OR UPDATE OR DELETE OR TRUNCATE';
186188
_q_txt text;
@@ -195,7 +197,7 @@ BEGIN
195197
END IF;
196198
_q_txt = 'CREATE TRIGGER audit_trigger_row AFTER INSERT OR UPDATE OR DELETE ON ' ||
197199
quote_ident(target_table::TEXT) ||
198-
' FOR EACH ROW EXECUTE PROCEDURE app_audit.if_modified_func(' ||
200+
' FOR EACH ROW EXECUTE PROCEDURE ${auditSchema}.if_modified_func(' ||
199201
quote_literal(audit_query_text) || _ignored_cols_snip || ');';
200202
RAISE NOTICE '%',_q_txt;
201203
EXECUTE _q_txt;
@@ -205,7 +207,7 @@ BEGIN
205207
206208
_q_txt = 'CREATE TRIGGER audit_trigger_stm AFTER ' || stm_targets || ' ON ' ||
207209
target_table ||
208-
' FOR EACH STATEMENT EXECUTE PROCEDURE app_audit.if_modified_func('||
210+
' FOR EACH STATEMENT EXECUTE PROCEDURE ${auditSchema}.if_modified_func('||
209211
quote_literal(audit_query_text) || ');';
210212
RAISE NOTICE '%',_q_txt;
211213
EXECUTE _q_txt;
@@ -214,7 +216,7 @@ END;
214216
$body$
215217
language 'plpgsql';
216218
217-
COMMENT ON FUNCTION app_audit.audit_table(regclass, boolean, boolean, text[]) IS $body$
219+
COMMENT ON FUNCTION ${auditSchema}.audit_table(regclass, boolean, boolean, text[]) IS $body$
218220
Add auditing support to a table.
219221
220222
Arguments:
@@ -225,18 +227,18 @@ Arguments:
225227
$body$;
226228
227229
-- Pg doesn't allow variadic calls with 0 params, so provide a wrapper
228-
CREATE OR REPLACE FUNCTION app_audit.audit_table(target_table regclass, audit_rows boolean, audit_query_text boolean) RETURNS void AS $body$
229-
SELECT app_audit.audit_table($1, $2, $3, ARRAY[]::text[]);
230+
CREATE OR REPLACE FUNCTION ${auditSchema}.audit_table(target_table regclass, audit_rows boolean, audit_query_text boolean) RETURNS void AS $body$
231+
SELECT ${auditSchema}.audit_table($1, $2, $3, ARRAY[]::text[]);
230232
$body$ LANGUAGE SQL;
231233
232234
-- And provide a convenience call wrapper for the simplest case
233235
-- of row-level logging with no excluded cols and query logging enabled.
234236
--
235-
CREATE OR REPLACE FUNCTION app_audit.audit_table(target_table regclass) RETURNS void AS $body$
236-
SELECT app_audit.audit_table($1, BOOLEAN 't', BOOLEAN 't');
237+
CREATE OR REPLACE FUNCTION ${auditSchema}.audit_table(target_table regclass) RETURNS void AS $body$
238+
SELECT ${auditSchema}.audit_table($1, BOOLEAN 't', BOOLEAN 't');
237239
$body$ LANGUAGE 'sql';
238240
239-
COMMENT ON FUNCTION app_audit.audit_table(regclass) IS $body$
241+
COMMENT ON FUNCTION ${auditSchema}.audit_table(regclass) IS $body$
240242
Add auditing support to the given table. Row-level changes will be logged with full client query text. No cols are ignored.
241243
$body$;
242244
`

0 commit comments

Comments
 (0)