= Advanced Database Development == Data constraints requirements: Enrollment-Subscription consistency + single enrollment per course === Data requirements description Course enrollments must remain consistent with the subscription and participation rules of the platform: # A user must have an ACTIVE subscription in order to enroll in a course. # A user cannot enroll in the same course more than once. # enrollment status changes must follow a valid transition model. # Each enrollment must reference a valid user and a valid course. === Implementation {{{Triggers}}} {{{ -- status transition helper CREATE OR REPLACE FUNCTION olpms_is_valid_enrollment_transition(p_old text, p_new text) RETURNS boolean LANGUAGE sql AS $$ SELECT CASE WHEN p_old IS NULL AND p_new IN ('IN_PROGRESS', 'COMPLETED', 'CANCELLED') THEN true WHEN p_old = p_new THEN true WHEN p_old = 'IN_PROGRESS' AND p_new IN ('COMPLETED', 'CANCELLED') THEN true WHEN p_old = 'COMPLETED' AND p_new = 'COMPLETED' THEN true WHEN p_old = 'CANCELLED' AND p_new = 'CANCELLED' THEN true ELSE false END; $$; -- main enrollment consistency trigger function CREATE OR REPLACE FUNCTION olpms_trg_enrollment_enforce() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_active_subscription_count integer; BEGIN -- user must have an active subscription when inserting enrollment IF TG_OP = 'INSERT' THEN SELECT COUNT(*) INTO v_active_subscription_count FROM user_subscription us WHERE us.user_id = NEW.user_id AND us.status = 'ACTIVE' AND CURRENT_DATE BETWEEN us.start_date AND us.end_date; IF v_active_subscription_count = 0 THEN RAISE EXCEPTION 'User % does not have an active subscription', NEW.user_id; END IF; END IF; -- prevent duplicate enrollment for the same user and the same course IF EXISTS ( SELECT 1 FROM enrollment e WHERE e.user_id = NEW.user_id AND e.course_id = NEW.course_id AND (TG_OP <> 'UPDATE' OR e.enrollment_id <> NEW.enrollment_id) ) THEN RAISE EXCEPTION 'User % is already enrolled in course %', NEW.user_id, NEW.course_id; END IF; -- validate completion_status transition on UPDATE IF TG_OP = 'UPDATE' THEN IF NOT olpms_is_valid_enrollment_transition(OLD.completion_status, NEW.completion_status) THEN RAISE EXCEPTION 'Invalid enrollment status transition: % -> %', OLD.completion_status, NEW.completion_status; END IF; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_enrollment_enforce ON enrollment; CREATE TRIGGER trg_enrollment_enforce BEFORE INSERT OR UPDATE ON enrollment FOR EACH ROW EXECUTE FUNCTION olpms_trg_enrollment_enforce(); }}} {{{Stored procedures / functions}}} {{{ CREATE OR REPLACE FUNCTION olpms_is_valid_enrollment_transition(p_old text, p_new text) RETURNS boolean LANGUAGE sql AS $$ SELECT CASE WHEN p_old IS NULL AND p_new IN ('IN_PROGRESS', 'COMPLETED', 'CANCELLED') THEN true WHEN p_old = p_new THEN true WHEN p_old = 'IN_PROGRESS' AND p_new IN ('COMPLETED', 'CANCELLED') THEN true WHEN p_old = 'COMPLETED' AND p_new = 'COMPLETED' THEN true WHEN p_old = 'CANCELLED' AND p_new = 'CANCELLED' THEN true ELSE false END; $$; CREATE OR REPLACE FUNCTION olpms_trg_enrollment_enforce() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_active_subscription_count integer; BEGIN IF TG_OP = 'INSERT' THEN SELECT COUNT(*) INTO v_active_subscription_count FROM user_subscription us WHERE us.user_id = NEW.user_id AND us.status = 'ACTIVE' AND CURRENT_DATE BETWEEN us.start_date AND us.end_date; IF v_active_subscription_count = 0 THEN RAISE EXCEPTION 'User % does not have an active subscription', NEW.user_id; END IF; END IF; IF EXISTS ( SELECT 1 FROM enrollment e WHERE e.user_id = NEW.user_id AND e.course_id = NEW.course_id AND (TG_OP <> 'UPDATE' OR e.enrollment_id <> NEW.enrollment_id) ) THEN RAISE EXCEPTION 'User % is already enrolled in course %', NEW.user_id, NEW.course_id; END IF; IF TG_OP = 'UPDATE' THEN IF NOT olpms_is_valid_enrollment_transition(OLD.completion_status, NEW.completion_status) THEN RAISE EXCEPTION 'Invalid enrollment status transition: % -> %', OLD.completion_status, NEW.completion_status; END IF; END IF; RETURN NEW; END; $$; }}} {{{Views}}} {{{ CREATE OR REPLACE VIEW v_enrollments_enriched AS SELECT e.enrollment_id, e.user_id, ue.first_name, ue.last_name, ue.email, e.course_id, c.name AS course_name, e.enroll_date, e.completion_status, e.progress_percentage, us.subscription_id, us.status AS subscription_status, us.start_date AS subscription_start_date, us.end_date AS subscription_end_date FROM enrollment e JOIN users u ON u.id = e.user_id JOIN user_entity ue ON ue.id = u.id JOIN course c ON c.course_id = e.course_id LEFT JOIN user_subscription us ON us.user_id = e.user_id AND us.status = 'ACTIVE' AND CURRENT_DATE BETWEEN us.start_date AND us.end_date; }}} == Data constraints requirements: Certificate generation consistency + one certificate per completed enrollment === Data requirements description Certificates must obey completion and consistency rules: * A certificate can only be issued for an enrollment whose completion status is COMPLETED. * The same enrollment cannot have more than one certificate. * A certificate must always refer to a valid enrollment. * Issuing a certificate before course completion is blocked. === Implementation {{{Triggers}}} {{{ CREATE OR REPLACE FUNCTION olpms_trg_certificate_enforce() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_completion_status VARCHAR(30); BEGIN -- find the status of the referenced enrollment SELECT e.completion_status INTO v_completion_status FROM enrollment e WHERE e.enrollment_id = NEW.enrollment_id; IF v_completion_status IS NULL THEN RAISE EXCEPTION 'Enrollment % does not exist or has no completion status', NEW.enrollment_id; END IF; -- certificate can only be issued for completed enrollments IF v_completion_status <> 'COMPLETED' THEN RAISE EXCEPTION 'Certificate cannot be issued because enrollment % is not COMPLETED', NEW.enrollment_id; END IF; -- prevent more than one certificate for the same enrollment IF EXISTS ( SELECT 1 FROM certificate c WHERE c.enrollment_id = NEW.enrollment_id AND (TG_OP <> 'UPDATE' OR c.certificate_id <> NEW.certificate_id) ) THEN RAISE EXCEPTION 'Enrollment % already has a certificate', NEW.enrollment_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_certificate_enforce ON certificate; CREATE TRIGGER trg_certificate_enforce BEFORE INSERT OR UPDATE ON certificate FOR EACH ROW EXECUTE FUNCTION olpms_trg_certificate_enforce(); }}} {{{Views}}} {{{ CREATE OR REPLACE VIEW v_certificates_enriched AS SELECT c.certificate_id, c.enrollment_id, c.issue_date, c.certificate_code, c.status AS certificate_status, e.user_id, ue.first_name, ue.last_name, ue.email, e.course_id, cr.name AS course_name, e.enroll_date, e.completion_status, e.progress_percentage FROM certificate c JOIN enrollment e ON e.enrollment_id = c.enrollment_id JOIN users u ON u.id = e.user_id JOIN user_entity ue ON ue.id = u.id JOIN course cr ON cr.course_id = e.course_id; }}} == Data constraints requirements: Quiz attempt validity + score consistency === Data requirements description Quiz attempts must obey assessment consistency rules: * A quiz attempt must always refer to a valid user and a valid quiz. * The achieved score must be between 0 and the quiz total_points. * A user should only attempt quizzes that belong to lessons from courses in which the user is enrolled. * Invalid quiz attempts are blocked === Implementation {{{Triggers}}} {{{ CREATE OR REPLACE FUNCTION olpms_trg_quiz_attempt_enforce() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_total_points INT; BEGIN IF NEW.user_id IS NULL THEN RAISE EXCEPTION 'Quiz attempt must reference a valid user'; END IF; IF NEW.quiz_id IS NULL THEN RAISE EXCEPTION 'Quiz attempt must reference a valid quiz'; END IF; SELECT q.total_points INTO v_total_points FROM quiz q WHERE q.quiz_id = NEW.quiz_id; IF v_total_points IS NULL THEN RAISE EXCEPTION 'Quiz % does not exist', NEW.quiz_id; END IF; IF NEW.score IS NULL THEN RAISE EXCEPTION 'Quiz attempt score must not be NULL'; END IF; IF NEW.score < 0 OR NEW.score > v_total_points THEN RAISE EXCEPTION 'Invalid score % for quiz %, allowed range is 0 to %', NEW.score, NEW.quiz_id, v_total_points; END IF; IF NOT EXISTS ( SELECT 1 FROM enrollment e JOIN module m ON m.course_id = e.course_id JOIN lesson l ON l.module_id = m.module_id JOIN quiz q ON q.lesson_id = l.lesson_id WHERE e.user_id = NEW.user_id AND q.quiz_id = NEW.quiz_id ) THEN RAISE EXCEPTION 'User % is not enrolled in the course associated with quiz %', NEW.user_id, NEW.quiz_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_quiz_attempt_enforce ON quiz_attempt; CREATE TRIGGER trg_quiz_attempt_enforce BEFORE INSERT OR UPDATE ON quiz_attempt FOR EACH ROW EXECUTE FUNCTION olpms_trg_quiz_attempt_enforce(); }}} {{{Views}}} {{{ CREATE OR REPLACE VIEW v_quiz_attempts_enriched AS SELECT qa.attempt_id, qa.attempt_date, qa.score, qa.user_id, ue.first_name, ue.last_name, ue.email, qa.quiz_id, q.total_points, q.passing_score, l.lesson_id, l.title AS lesson_title, m.module_id, m.title AS module_title, c.course_id, c.name AS course_name, CASE WHEN qa.score >= q.passing_score THEN 'PASSED' ELSE 'FAILED' END AS result_status FROM quiz_attempt qa JOIN users u ON u.id = qa.user_id JOIN user_entity ue ON ue.id = u.id JOIN quiz q ON q.quiz_id = qa.quiz_id JOIN lesson l ON l.lesson_id = q.lesson_id JOIN module m ON m.module_id = l.module_id JOIN course c ON c.course_id = m.course_id; }}} == Data constraints requirements: Review Consistency === Data requirements description Reviews are split across multiple tables and require consistency checks: * A base reviews row can represent either a user_review or a clinic_review, never both. * A reviewer cannot repeatedly review the same target too frequently (30-day cooldown). * Prevent self-review. === Implementation {{{Triggers}}} {{{ CREATE OR REPLACE FUNCTION petify_trg_reviews_no_update() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'Reviews are immutable. Updates are not allowed (review_id=%).', OLD.review_id; END; $$; DROP TRIGGER IF EXISTS trg_reviews_no_update ON reviews; CREATE TRIGGER trg_reviews_no_update BEFORE UPDATE ON reviews FOR EACH ROW EXECUTE FUNCTION petify_trg_reviews_no_update(); DROP TRIGGER IF EXISTS trg_clinic_reviews_no_update ON clinic_reviews; CREATE TRIGGER trg_clinic_reviews_no_update BEFORE UPDATE ON clinic_reviews FOR EACH ROW EXECUTE FUNCTION petify_trg_no_update_generic(); CREATE OR REPLACE FUNCTION petify_trg_user_review_exclusive() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF EXISTS (SELECT 1 FROM clinic_reviews cr WHERE cr.review_id = NEW.review_id) THEN RAISE EXCEPTION 'review_id % already used as clinic review (cannot also be user review)', NEW.review_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_user_review_exclusive ON user_reviews; CREATE TRIGGER trg_user_review_exclusive BEFORE INSERT ON user_reviews FOR EACH ROW EXECUTE FUNCTION petify_trg_user_review_exclusive(); CREATE OR REPLACE FUNCTION petify_trg_clinic_review_exclusive() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF EXISTS (SELECT 1 FROM user_reviews ur WHERE ur.review_id = NEW.review_id) THEN RAISE EXCEPTION 'review_id % already used as user review (cannot also be clinic review)', NEW.review_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_clinic_review_exclusive ON clinic_reviews; CREATE TRIGGER trg_clinic_review_exclusive BEFORE INSERT ON clinic_reviews FOR EACH ROW EXECUTE FUNCTION petify_trg_clinic_review_exclusive(); CREATE OR REPLACE FUNCTION petify_trg_user_reviews_cooldown() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_reviewer bigint; v_created timestamp; BEGIN SELECT reviewer_id, created_at INTO v_reviewer, v_created FROM reviews WHERE review_id = NEW.review_id; IF v_reviewer IS NULL THEN RAISE EXCEPTION 'Base review % not found', NEW.review_id; END IF; IF v_reviewer = NEW.target_user_id THEN RAISE EXCEPTION 'User cannot review themselves (user_id=%)', v_reviewer; END IF; IF EXISTS ( SELECT 1 FROM user_reviews ur JOIN reviews r ON r.review_id = ur.review_id WHERE r.reviewer_id = v_reviewer AND ur.target_user_id = NEW.target_user_id AND r.is_deleted = false AND r.created_at >= v_created - interval '30 days' ) THEN RAISE EXCEPTION 'Cooldown: reviewer % already reviewed user % within last 30 days', v_reviewer, NEW.target_user_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_user_reviews_cooldown ON user_reviews; CREATE TRIGGER trg_user_reviews_cooldown BEFORE INSERT ON user_reviews FOR EACH ROW EXECUTE FUNCTION petify_trg_user_reviews_cooldown(); CREATE OR REPLACE FUNCTION petify_trg_clinic_reviews_cooldown() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_reviewer bigint; v_created timestamp; BEGIN SELECT reviewer_id, created_at INTO v_reviewer, v_created FROM reviews WHERE review_id = NEW.review_id; IF v_reviewer IS NULL THEN RAISE EXCEPTION 'Base review % not found', NEW.review_id; END IF; IF EXISTS ( SELECT 1 FROM clinic_reviews cr JOIN reviews r ON r.review_id = cr.review_id WHERE r.reviewer_id = v_reviewer AND cr.target_clinic_id = NEW.target_clinic_id AND r.is_deleted = false AND r.created_at >= v_created - interval '30 days' ) THEN RAISE EXCEPTION 'Cooldown: reviewer % already reviewed clinic % within last 30 days', v_reviewer, NEW.target_clinic_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_clinic_reviews_cooldown ON clinic_reviews; CREATE TRIGGER trg_clinic_reviews_cooldown BEFORE INSERT ON clinic_reviews FOR EACH ROW EXECUTE FUNCTION petify_trg_clinic_reviews_cooldown(); }}} {{{Views}}} {{{ CREATE OR REPLACE VIEW v_user_ratings AS SELECT ur.target_user_id, COUNT(*) FILTER (WHERE r.is_deleted = false) AS review_count, ROUND(AVG(r.rating)::numeric, 2) FILTER (WHERE r.is_deleted = false) AS avg_rating FROM user_reviews ur JOIN reviews r ON r.review_id = ur.review_id GROUP BY ur.target_user_id; CREATE OR REPLACE VIEW v_clinic_ratings AS SELECT cr.target_clinic_id, COUNT(*) FILTER (WHERE r.is_deleted = false) AS review_count, ROUND(AVG(r.rating)::numeric, 2) FILTER (WHERE r.is_deleted = false) AS avg_rating FROM clinic_reviews cr JOIN reviews r ON r.review_id = cr.review_id GROUP BY cr.target_clinic_id; }}} {{{Custom domains}}} {{{ CREATE DOMAIN rating_1_5 AS int CHECK (VALUE BETWEEN 1 AND 5); }}} == Data constraints requirements: Background Jobs === Data requirements description These are time-based business rules that must be enforced asynchronously: * If an appointment is still CONFIRMED long after its scheduled time, mark it as NO_SHOW. * If an Archive listing is older than 30days its status is draft. === Implementation {{{Stored procedures}}} {{{ CREATE EXTENSION IF NOT EXISTS pg_cron; CREATE OR REPLACE PROCEDURE job_mark_no_show() LANGUAGE plpgsql AS $$ BEGIN UPDATE appointments SET status = 'NO_SHOW' WHERE status = 'CONFIRMED' AND date_time < now() - interval '45 minutes'; END; $$; CREATE OR REPLACE PROCEDURE job_archive_stale_drafts() LANGUAGE plpgsql AS $$ BEGIN UPDATE listings SET status = 'ARCHIVED' WHERE status = 'DRAFT' AND created_at < now() - interval '30 days'; END; $$; }}} {{{Views}}} {{{ CREATE OR REPLACE VIEW v_overdue_confirmed_appointments AS SELECT * FROM appointments WHERE status='CONFIRMED' AND date_time < now() - interval '45 minutes'; CREATE OR REPLACE VIEW v_stale_draft_listings AS SELECT * FROM listings WHERE status='DRAFT' AND created_at < now() - interval '30 days'; }}} {{{Scheduling}}} {{{ SELECT cron.schedule('petify_mark_no_show', '*/10 * * * *', $$CALL job_mark_no_show();$$); SELECT cron.schedule('petify_archive_stale_drafts_daily', '10 2 * * *', $$CALL job_archive_stale_drafts();$$); }}}