package main import ( "context" "ersteller-lib" "github.com/joho/godotenv" "os" ) func main() { err := godotenv.Load() if err != nil { ersteller_lib.LogError("Error loading .env file: %v", err) panic(err) } dbUrl := os.Getenv("DATABASE_URL") connpool, err := ersteller_lib.CreatePostgresConnpool(dbUrl) if err != nil { ersteller_lib.LogError("Failed to create connection pool: %v", err) panic(err) } // https://medium.com/israeli-tech-radar/postgresql-trigger-based-audit-log-fd9d9d5e412c sql := ` CREATE TABLE IF NOT EXISTS audit_log ( id serial PRIMARY KEY, table_name TEXT, record_id TEXT, operation_type TEXT, changed_at TIMESTAMP DEFAULT now(), changed_by TEXT, original_values jsonb, new_values jsonb ); CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$ DECLARE new_data jsonb; old_data jsonb; key text; new_values jsonb; old_values jsonb; user_id text; BEGIN user_id := current_setting('audit.user_id', true); IF user_id IS NULL THEN user_id := current_user; END IF; new_values := '{}'; old_values := '{}'; IF TG_OP = 'INSERT' THEN new_data := to_jsonb(NEW); new_values := new_data; ELSIF TG_OP = 'UPDATE' THEN new_data := to_jsonb(NEW); old_data := to_jsonb(OLD); FOR key IN SELECT jsonb_object_keys(new_data) INTERSECT SELECT jsonb_object_keys(old_data) LOOP IF new_data ->> key != old_data ->> key THEN new_values := new_values || jsonb_build_object(key, new_data ->> key); old_values := old_values || jsonb_build_object(key, old_data ->> key); END IF; END LOOP; ELSIF TG_OP = 'DELETE' THEN old_data := to_jsonb(OLD); old_values := old_data; FOR key IN SELECT jsonb_object_keys(old_data) LOOP old_values := old_values || jsonb_build_object(key, old_data ->> key); END LOOP; END IF; IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN INSERT INTO audit_log (table_name, record_id, operation_type, changed_by, original_values, new_values) VALUES (TG_TABLE_NAME, NEW.id, TG_OP, user_id, old_values, new_values); RETURN NEW; ELSE INSERT INTO audit_log (table_name, record_id, operation_type, changed_by, original_values, new_values) VALUES (TG_TABLE_NAME, OLD.id, TG_OP, user_id, old_values, new_values); RETURN OLD; END IF; END; $$ LANGUAGE plpgsql; CREATE OR REPLACE TRIGGER audit_log_trigger BEFORE INSERT OR UPDATE OR DELETE ON public."marketDataLead" FOR EACH ROW EXECUTE FUNCTION audit_trigger(); CREATE OR REPLACE TRIGGER audit_log_trigger BEFORE INSERT OR UPDATE OR DELETE ON public.group FOR EACH ROW EXECUTE FUNCTION audit_trigger(); CREATE OR REPLACE TRIGGER audit_log_trigger BEFORE INSERT OR UPDATE OR DELETE ON public."groupMembership" FOR EACH ROW EXECUTE FUNCTION audit_trigger(); CREATE OR REPLACE TRIGGER audit_log_trigger BEFORE INSERT OR UPDATE OR DELETE ON public."marketDataLeadNote" FOR EACH ROW EXECUTE FUNCTION audit_trigger(); CREATE OR REPLACE TRIGGER audit_log_trigger BEFORE INSERT OR UPDATE OR DELETE ON public.settings FOR EACH ROW EXECUTE FUNCTION audit_trigger(); CREATE OR REPLACE TRIGGER audit_log_trigger BEFORE INSERT OR UPDATE OR DELETE ON public.user FOR EACH ROW EXECUTE FUNCTION audit_trigger(); ` _, err = connpool.Exec(context.Background(), sql) if err != nil { ersteller_lib.LogError("Failed to create audit log table: %v", err) panic(err) } println("Created audit log table") }