diff --git a/cli/create_audit_log.go b/cli/create_audit_log.go new file mode 100644 index 0000000..b946e41 --- /dev/null +++ b/cli/create_audit_log.go @@ -0,0 +1,140 @@ +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") +} diff --git a/go.mod b/go.mod index 3f24a40..f1e86eb 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/doug-martin/goqu/v9 v9.19.0 github.com/gorilla/sessions v1.4.0 github.com/jackc/pgx/v5 v5.7.5 + github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.13.4 github.com/markbates/goth v1.81.0 github.com/mattn/go-sqlite3 v1.14.29 diff --git a/go.sum b/go.sum index 2066f3c..ea2acf8 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= diff --git a/schema_template.prisma b/schema_template.prisma new file mode 100644 index 0000000..aa88432 --- /dev/null +++ b/schema_template.prisma @@ -0,0 +1,39 @@ +generator db { + provider = "go run github.com/steebchen/prisma-client-go" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model user { + id Int @id @default(autoincrement()) + email String @unique @default("") + state Json @default("{}") + admin Boolean @default(false) + password String @default("") + created_at DateTime @default(now()) @db.Timestamptz(3) + updated_at DateTime @default(now()) @updatedAt @db.Timestamptz(3) + googleAuth googleAuth[] +} + +model googleAuth { + id Int @id @default(autoincrement()) + credentials Json @default("{}") + user_id Int @default(0) + created_at DateTime @default(now()) @db.Timestamptz(3) + updated_at DateTime @default(now()) @updatedAt @db.Timestamptz(3) + user user @relation(fields: [user_id], references: [id], onDelete: Cascade) +} + +model audit_log { + id Int @id @default(autoincrement()) + table_name String? + record_id String? + operation_type String? + changed_at DateTime? @default(now()) @db.Timestamp(6) + changed_by String? + original_values Json? + new_values Json? +}