diff --git a/server/prisma/executeTriggers.ts b/server/prisma/executeTriggers.ts new file mode 100644 index 0000000..f8419f9 --- /dev/null +++ b/server/prisma/executeTriggers.ts @@ -0,0 +1,45 @@ +import pg from "pg"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import dotenv from "dotenv"; + +dotenv.config(); + +const { Client } = pg; + +// Fix __dirname for ES Modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Path to triggers.sql +const filePath = path.join(__dirname, "scripts", "triggers.sql"); + +const client = new Client({ + connectionString: process.env.DATABASE_URL, +}); + +async function executeTriggers() { + try { + await client.connect(); + console.log(" Connected to the database."); + + // Ensure triggers.sql exists + if (!fs.existsSync(filePath)) { + throw new Error(` triggers.sql file not found at: ${filePath}`); + } + + const sql = fs.readFileSync(filePath, "utf8"); + + console.log(" Executing triggers.sql..."); + await client.query(sql); + console.log(" Triggers executed successfully."); + } catch (error) { + console.error(" Error executing triggers.sql:", error); + } finally { + await client.end(); + console.log(" Database connection closed."); + } +} + +executeTriggers(); diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 0e8d5b8..d7ad312 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -1,7 +1,6 @@ generator client { - provider = "prisma-client-js" - binaryTargets = ["native", "linux-musl"] - + provider = "prisma-client-js" + binaryTargets = ["native", "linux-musl"] } datasource db { @@ -21,15 +20,16 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt activities Activity[] + mentorActivities MentorActivity[] @relation("UserMentorActivities") mentorFeedback MentorFeedback[] - mentorReports Report[] @relation(name: "mentorReports") - mentorRoles Mentorship[] @relation(name: "mentorRole") - studentRoles Mentorship[] @relation(name: "studentRole") - mentorActivities MentorActivity[] @relation(name: "UserMentorActivities") + mentorRoles Mentorship[] @relation("mentorRole") + studentRoles Mentorship[] @relation("studentRole") + mentorReports Report[] @relation("mentorReports") @@map("users") } + model Activity { id String @id @default(uuid()) studentId String @@ -59,12 +59,12 @@ model MentorFeedback { } model Report { - id String @id @default(uuid()) + id String @id @default(uuid()) mentorId String reportData Json status ProcessStatus @default(pending) - generatedAt DateTime @default(now()) - mentor User @relation(fields: [mentorId], references: [id], name: "mentorReports") + generatedAt DateTime @default(now()) + mentor User @relation("mentorReports", fields: [mentorId], references: [id]) @@map("reports") } @@ -73,8 +73,8 @@ model Mentorship { id String @id @default(uuid()) mentorId String studentId String - mentor User @relation(fields: [mentorId], references: [id], name: "mentorRole") - student User @relation(fields: [studentId], references: [id], name: "studentRole") + mentor User @relation("mentorRole", fields: [mentorId], references: [id]) + student User @relation("studentRole", fields: [studentId], references: [id]) @@map("mentorship") } @@ -87,11 +87,23 @@ model MentorActivity { activities String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - mentor User @relation(fields: [mentorId], references: [id], name: "UserMentorActivities") + mentor User @relation("UserMentorActivities", fields: [mentorId], references: [id]) @@map("mentoractivities") } +model AuditLog { + id String @id @default(uuid()) + action String + tableName String + recordId String + oldData Json? + timestamp DateTime @default(now()) + @@map("audit_logs") +} + + + enum Role { student mentor @@ -109,4 +121,4 @@ enum ProcessStatus { error completed wip -} \ No newline at end of file +} diff --git a/server/prisma/scripts/triggers.sql b/server/prisma/scripts/triggers.sql new file mode 100644 index 0000000..b33d058 --- /dev/null +++ b/server/prisma/scripts/triggers.sql @@ -0,0 +1,52 @@ +-- Drop existing function if it exists +DROP FUNCTION IF EXISTS audit_all_tables CASCADE; + +-- Create the audit function without current_user_id +CREATE OR REPLACE FUNCTION audit_all_tables() RETURNS TRIGGER AS $$ +BEGIN + -- Insert into audit_logs (without current_user_id) + INSERT INTO audit_logs ("id", "action", "tableName", "recordId", "oldData", "timestamp") + VALUES ( + gen_random_uuid(), + TG_OP, + TG_TABLE_NAME, + CASE WHEN TG_OP = 'DELETE' THEN OLD.id ELSE NEW.id END, + CASE WHEN TG_OP IN ('UPDATE', 'DELETE') THEN row_to_json(OLD) ELSE NULL END, + now() + ); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create triggers for all tracked tables + +-- Activities table trigger +DROP TRIGGER IF EXISTS audit_trigger_activities ON activities; +CREATE TRIGGER audit_trigger_activities +AFTER INSERT OR UPDATE OR DELETE ON activities +FOR EACH ROW EXECUTE FUNCTION audit_all_tables(); + +-- Mentor Feedback table trigger +DROP TRIGGER IF EXISTS audit_trigger_mentorfeedback ON mentorfeedback; +CREATE TRIGGER audit_trigger_mentorfeedback +AFTER INSERT OR UPDATE OR DELETE ON mentorfeedback +FOR EACH ROW EXECUTE FUNCTION audit_all_tables(); + +-- Mentorship table trigger +DROP TRIGGER IF EXISTS audit_trigger_mentorship ON mentorship; +CREATE TRIGGER audit_trigger_mentorship +AFTER INSERT OR UPDATE OR DELETE ON mentorship +FOR EACH ROW EXECUTE FUNCTION audit_all_tables(); + +-- Users table trigger +DROP TRIGGER IF EXISTS audit_trigger_users ON users; +CREATE TRIGGER audit_trigger_users +AFTER INSERT OR UPDATE OR DELETE ON users +FOR EACH ROW EXECUTE FUNCTION audit_all_tables(); + +-- Mentor Activity table trigger +DROP TRIGGER IF EXISTS audit_trigger_mentoractivities ON mentoractivities; +CREATE TRIGGER audit_trigger_mentoractivities +AFTER INSERT OR UPDATE OR DELETE ON mentoractivities +FOR EACH ROW EXECUTE FUNCTION audit_all_tables();