Release 2025-05-19
This commit is contained in:
@@ -1,265 +0,0 @@
|
||||
-- This script manages user karma based on comment interactions. It handles karma points
|
||||
-- for comment approvals, verifications, spam status changes, and votes (upvotes/downvotes).
|
||||
-- Karma transactions are recorded, and user karma totals are updated accordingly.
|
||||
|
||||
-- Drop existing triggers first
|
||||
DROP TRIGGER IF EXISTS comment_status_change_trigger ON "Comment";
|
||||
DROP TRIGGER IF EXISTS comment_suspicious_change_trigger ON "Comment";
|
||||
DROP TRIGGER IF EXISTS comment_upvote_change_trigger ON "Comment";
|
||||
DROP TRIGGER IF EXISTS comment_vote_change_trigger ON "CommentVote";
|
||||
DROP TRIGGER IF EXISTS suggestion_status_change_trigger ON "ServiceSuggestion";
|
||||
|
||||
-- Drop existing functions
|
||||
DROP FUNCTION IF EXISTS handle_comment_upvote_change();
|
||||
DROP FUNCTION IF EXISTS handle_comment_status_change();
|
||||
DROP FUNCTION IF EXISTS handle_comment_approval();
|
||||
DROP FUNCTION IF EXISTS handle_comment_verification();
|
||||
DROP FUNCTION IF EXISTS handle_comment_spam_status();
|
||||
DROP FUNCTION IF EXISTS handle_comment_vote_change();
|
||||
DROP FUNCTION IF EXISTS insert_karma_transaction();
|
||||
DROP FUNCTION IF EXISTS update_user_karma();
|
||||
DROP FUNCTION IF EXISTS handle_suggestion_status_change();
|
||||
|
||||
-- Helper function to insert karma transaction
|
||||
CREATE OR REPLACE FUNCTION insert_karma_transaction(
|
||||
p_user_id INT,
|
||||
p_points INT,
|
||||
p_action TEXT,
|
||||
p_comment_id INT,
|
||||
p_description TEXT,
|
||||
p_suggestion_id INT DEFAULT NULL
|
||||
) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
INSERT INTO "KarmaTransaction" (
|
||||
"userId", "points", "action", "commentId",
|
||||
"suggestionId",
|
||||
"description", "processed", "createdAt"
|
||||
)
|
||||
VALUES (
|
||||
p_user_id, p_points, p_action, p_comment_id,
|
||||
p_suggestion_id,
|
||||
p_description, true, NOW()
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Helper function to update user karma
|
||||
CREATE OR REPLACE FUNCTION update_user_karma(
|
||||
p_user_id INT,
|
||||
p_karma_change INT
|
||||
) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
UPDATE "User"
|
||||
SET "totalKarma" = "totalKarma" + p_karma_change
|
||||
WHERE id = p_user_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Handle comment approval
|
||||
CREATE OR REPLACE FUNCTION handle_comment_approval(
|
||||
NEW RECORD,
|
||||
OLD RECORD
|
||||
) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
IF OLD.status = 'PENDING' AND NEW.status = 'APPROVED' THEN
|
||||
PERFORM insert_karma_transaction(
|
||||
NEW."authorId",
|
||||
1,
|
||||
'comment_approved',
|
||||
NEW.id,
|
||||
format('Your comment #comment-%s in %s has been approved!',
|
||||
NEW.id,
|
||||
(SELECT name FROM "Service" WHERE id = NEW."serviceId"))
|
||||
);
|
||||
PERFORM update_user_karma(NEW."authorId", 1);
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Handle comment verification
|
||||
CREATE OR REPLACE FUNCTION handle_comment_verification(
|
||||
NEW RECORD,
|
||||
OLD RECORD
|
||||
) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
IF NEW.status = 'VERIFIED' AND OLD.status != 'VERIFIED' THEN
|
||||
PERFORM insert_karma_transaction(
|
||||
NEW."authorId",
|
||||
5,
|
||||
'comment_verified',
|
||||
NEW.id,
|
||||
format('Your comment #comment-%s in %s has been verified!',
|
||||
NEW.id,
|
||||
(SELECT name FROM "Service" WHERE id = NEW."serviceId"))
|
||||
);
|
||||
PERFORM update_user_karma(NEW."authorId", 5);
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Handle spam status changes
|
||||
CREATE OR REPLACE FUNCTION handle_comment_spam_status(
|
||||
NEW RECORD,
|
||||
OLD RECORD
|
||||
) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
-- Handle marking as spam
|
||||
IF NEW.suspicious = true AND OLD.suspicious = false THEN
|
||||
PERFORM insert_karma_transaction(
|
||||
NEW."authorId",
|
||||
-10,
|
||||
'comment_spam',
|
||||
NEW.id,
|
||||
format('Your comment #comment-%s in %s has been marked as spam.',
|
||||
NEW.id,
|
||||
(SELECT name FROM "Service" WHERE id = NEW."serviceId"))
|
||||
);
|
||||
PERFORM update_user_karma(NEW."authorId", -10);
|
||||
-- Handle unmarking as spam
|
||||
ELSIF NEW.suspicious = false AND OLD.suspicious = true THEN
|
||||
PERFORM insert_karma_transaction(
|
||||
NEW."authorId",
|
||||
10,
|
||||
'comment_spam_reverted',
|
||||
NEW.id,
|
||||
format('Your comment #comment-%s in %s is no longer marked as spam.',
|
||||
NEW.id,
|
||||
(SELECT name FROM "Service" WHERE id = NEW."serviceId"))
|
||||
);
|
||||
PERFORM update_user_karma(NEW."authorId", 10);
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Function for handling vote changes
|
||||
CREATE OR REPLACE FUNCTION handle_comment_vote_change()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
karma_points INT;
|
||||
vote_action TEXT;
|
||||
vote_description TEXT;
|
||||
comment_author_id INT;
|
||||
service_name TEXT;
|
||||
upvote_change INT := 0; -- Variable to track change in upvotes
|
||||
BEGIN
|
||||
-- Get comment author and service info
|
||||
SELECT c."authorId", s.name INTO comment_author_id, service_name
|
||||
FROM "Comment" c
|
||||
JOIN "Service" s ON c.id = COALESCE(NEW."commentId", OLD."commentId") AND c."serviceId" = s.id;
|
||||
|
||||
-- Calculate karma impact based on vote type
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
-- New vote
|
||||
karma_points := CASE WHEN NEW.downvote THEN -1 ELSE 1 END;
|
||||
vote_action := CASE WHEN NEW.downvote THEN 'comment_downvote' ELSE 'comment_upvote' END;
|
||||
vote_description := format('Your comment #comment-%s in %s received %s',
|
||||
NEW."commentId",
|
||||
service_name,
|
||||
CASE WHEN NEW.downvote THEN 'a downvote' ELSE 'an upvote' END);
|
||||
upvote_change := CASE WHEN NEW.downvote THEN -1 ELSE 1 END; -- -1 for downvote, +1 for upvote
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
-- Removed vote
|
||||
karma_points := CASE WHEN OLD.downvote THEN 1 ELSE -1 END;
|
||||
vote_action := 'comment_vote_removed';
|
||||
vote_description := format('A vote was removed from your comment #comment-%s in %s',
|
||||
OLD."commentId",
|
||||
service_name);
|
||||
upvote_change := CASE WHEN OLD.downvote THEN 1 ELSE -1 END; -- +1 if downvote removed, -1 if upvote removed
|
||||
ELSIF TG_OP = 'UPDATE' THEN
|
||||
-- Changed vote (from upvote to downvote or vice versa)
|
||||
karma_points := CASE WHEN NEW.downvote THEN -2 ELSE 2 END;
|
||||
vote_action := CASE WHEN NEW.downvote THEN 'comment_downvote' ELSE 'comment_upvote' END;
|
||||
vote_description := format('Your comment #comment-%s in %s vote changed to %s',
|
||||
NEW."commentId",
|
||||
service_name,
|
||||
CASE WHEN NEW.downvote THEN 'downvote' ELSE 'upvote' END);
|
||||
upvote_change := CASE WHEN NEW.downvote THEN -2 ELSE 2 END; -- -2 if upvote->downvote, +2 if downvote->upvote
|
||||
END IF;
|
||||
|
||||
-- Record karma transaction and update user karma
|
||||
PERFORM insert_karma_transaction(
|
||||
comment_author_id,
|
||||
karma_points,
|
||||
vote_action,
|
||||
COALESCE(NEW."commentId", OLD."commentId"),
|
||||
vote_description
|
||||
);
|
||||
|
||||
PERFORM update_user_karma(comment_author_id, karma_points);
|
||||
|
||||
-- Update comment's upvotes count incrementally
|
||||
UPDATE "Comment"
|
||||
SET upvotes = upvotes + upvote_change
|
||||
WHERE id = COALESCE(NEW."commentId", OLD."commentId");
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Main function for handling status changes
|
||||
CREATE OR REPLACE FUNCTION handle_comment_status_change()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM handle_comment_approval(NEW, OLD);
|
||||
PERFORM handle_comment_verification(NEW, OLD);
|
||||
PERFORM handle_comment_spam_status(NEW, OLD);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create triggers
|
||||
CREATE TRIGGER comment_status_change_trigger
|
||||
AFTER UPDATE OF status
|
||||
ON "Comment"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION handle_comment_status_change();
|
||||
|
||||
CREATE TRIGGER comment_suspicious_change_trigger
|
||||
AFTER UPDATE OF suspicious
|
||||
ON "Comment"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION handle_comment_status_change();
|
||||
|
||||
CREATE TRIGGER comment_vote_change_trigger
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON "CommentVote"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION handle_comment_vote_change();
|
||||
|
||||
-- Function to handle suggestion status changes and award karma
|
||||
CREATE OR REPLACE FUNCTION handle_suggestion_status_change()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
service_name TEXT;
|
||||
BEGIN
|
||||
-- Award karma for first approval
|
||||
-- Check that OLD.status is not NULL to handle the initial creation case if needed,
|
||||
-- and ensure it wasn't already APPROVED.
|
||||
IF OLD.status IS DISTINCT FROM 'APPROVED' AND NEW.status = 'APPROVED' THEN
|
||||
-- Fetch service name for the description
|
||||
SELECT name INTO service_name FROM "Service" WHERE id = NEW."serviceId";
|
||||
|
||||
-- Insert karma transaction, linking it to the suggestion
|
||||
PERFORM insert_karma_transaction(
|
||||
NEW."userId",
|
||||
10,
|
||||
'suggestion_approved',
|
||||
NULL, -- p_comment_id (not applicable)
|
||||
format('Your suggestion for service ''%s'' has been approved!', service_name),
|
||||
NEW.id -- p_suggestion_id
|
||||
);
|
||||
|
||||
-- Update user's total karma
|
||||
PERFORM update_user_karma(NEW."userId", 10);
|
||||
END IF;
|
||||
|
||||
RETURN NEW; -- Result is ignored since this is an AFTER trigger
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create triggers
|
||||
CREATE TRIGGER suggestion_status_change_trigger
|
||||
AFTER UPDATE OF status
|
||||
ON "ServiceSuggestion"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION handle_suggestion_status_change();
|
||||
@@ -1,264 +0,0 @@
|
||||
-- This script defines PostgreSQL functions and triggers for managing service scores:
|
||||
-- 1. Automatically calculates and updates privacy, trust, and overall scores
|
||||
-- for services when services or their attributes change.
|
||||
-- 2. Updates the isRecentlyListed flag for services listed within the last 15 days.
|
||||
-- 3. Queues asynchronous score recalculation in "ServiceScoreRecalculationJob"
|
||||
-- when an "Attribute" definition (e.g., points) is updated, ensuring
|
||||
-- efficient handling of widespread score updates.
|
||||
|
||||
-- Drop existing triggers first
|
||||
DROP TRIGGER IF EXISTS service_score_update_trigger ON "Service";
|
||||
DROP TRIGGER IF EXISTS service_attribute_change_trigger ON "ServiceAttribute";
|
||||
DROP TRIGGER IF EXISTS attribute_change_trigger ON "Attribute";
|
||||
|
||||
-- Drop existing functions
|
||||
DROP FUNCTION IF EXISTS calculate_service_scores();
|
||||
DROP FUNCTION IF EXISTS calculate_privacy_score();
|
||||
DROP FUNCTION IF EXISTS calculate_trust_score();
|
||||
DROP FUNCTION IF EXISTS calculate_overall_score();
|
||||
DROP FUNCTION IF EXISTS recalculate_scores_for_attribute();
|
||||
|
||||
-- Calculate privacy score based on service attributes and properties
|
||||
CREATE OR REPLACE FUNCTION calculate_privacy_score(service_id INT)
|
||||
RETURNS INT AS $$
|
||||
DECLARE
|
||||
privacy_score INT := 50; -- Start from middle value (50)
|
||||
kyc_factor INT;
|
||||
onion_factor INT := 0;
|
||||
i2p_factor INT := 0;
|
||||
monero_factor INT := 0;
|
||||
open_source_factor INT := 0;
|
||||
p2p_factor INT := 0;
|
||||
decentralized_factor INT := 0;
|
||||
attributes_score INT := 0;
|
||||
BEGIN
|
||||
-- Get service data
|
||||
SELECT
|
||||
CASE
|
||||
WHEN "kycLevel" = 0 THEN 25 -- No KYC is best for privacy
|
||||
WHEN "kycLevel" = 1 THEN 10 -- Minimal KYC
|
||||
WHEN "kycLevel" = 2 THEN -5 -- Moderate KYC
|
||||
WHEN "kycLevel" = 3 THEN -15 -- More KYC
|
||||
WHEN "kycLevel" = 4 THEN -25 -- Full mandatory KYC
|
||||
ELSE 0 -- Default to no change
|
||||
END
|
||||
INTO kyc_factor
|
||||
FROM "Service"
|
||||
WHERE "id" = service_id;
|
||||
|
||||
-- Check for onion URLs
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM "Service"
|
||||
WHERE "id" = service_id AND array_length("onionUrls", 1) > 0
|
||||
) THEN
|
||||
onion_factor := 5;
|
||||
END IF;
|
||||
|
||||
-- Check for i2p URLs
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM "Service"
|
||||
WHERE "id" = service_id AND array_length("i2pUrls", 1) > 0
|
||||
) THEN
|
||||
i2p_factor := 5;
|
||||
END IF;
|
||||
|
||||
-- Check for Monero acceptance
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM "Service"
|
||||
WHERE "id" = service_id AND 'MONERO' = ANY("acceptedCurrencies")
|
||||
) THEN
|
||||
monero_factor := 5;
|
||||
END IF;
|
||||
|
||||
-- Calculate score from privacy attributes - directly use the points
|
||||
SELECT COALESCE(SUM(a."privacyPoints"), 0)
|
||||
INTO attributes_score
|
||||
FROM "ServiceAttribute" sa
|
||||
JOIN "Attribute" a ON sa."attributeId" = a."id"
|
||||
WHERE sa."serviceId" = service_id AND a."category" = 'PRIVACY';
|
||||
|
||||
-- Calculate final privacy score (base 100)
|
||||
privacy_score := privacy_score + kyc_factor + onion_factor + i2p_factor + monero_factor + open_source_factor + p2p_factor + decentralized_factor + attributes_score;
|
||||
|
||||
-- Ensure the score is in reasonable bounds (0-100)
|
||||
privacy_score := GREATEST(0, LEAST(100, privacy_score));
|
||||
|
||||
RETURN privacy_score;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Calculate trust score based on service attributes and verification status
|
||||
CREATE OR REPLACE FUNCTION calculate_trust_score(service_id INT)
|
||||
RETURNS INT AS $$
|
||||
DECLARE
|
||||
trust_score INT := 50; -- Start from middle value (50)
|
||||
verification_factor INT;
|
||||
attributes_score INT := 0;
|
||||
BEGIN
|
||||
-- Get verification status factor
|
||||
SELECT
|
||||
CASE
|
||||
WHEN "verificationStatus" = 'VERIFICATION_SUCCESS' THEN 10
|
||||
WHEN "verificationStatus" = 'APPROVED' THEN 5
|
||||
WHEN "verificationStatus" = 'COMMUNITY_CONTRIBUTED' THEN 0
|
||||
WHEN "verificationStatus" = 'VERIFICATION_FAILED' THEN -50
|
||||
ELSE 0
|
||||
END
|
||||
INTO verification_factor
|
||||
FROM "Service"
|
||||
WHERE id = service_id;
|
||||
|
||||
-- Calculate score from trust attributes - directly use the points
|
||||
SELECT COALESCE(SUM(a."trustPoints"), 0)
|
||||
INTO attributes_score
|
||||
FROM "ServiceAttribute" sa
|
||||
JOIN "Attribute" a ON sa."attributeId" = a.id
|
||||
WHERE sa."serviceId" = service_id AND a.category = 'TRUST';
|
||||
|
||||
-- Apply penalty if service was listed within the last 15 days
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM "Service"
|
||||
WHERE id = service_id
|
||||
AND "listedAt" IS NOT NULL
|
||||
AND "verificationStatus" = 'APPROVED'
|
||||
AND (NOW() - "listedAt") <= INTERVAL '15 days'
|
||||
) THEN
|
||||
trust_score := trust_score - 10;
|
||||
-- Update the isRecentlyListed flag to true
|
||||
UPDATE "Service"
|
||||
SET "isRecentlyListed" = TRUE
|
||||
WHERE id = service_id;
|
||||
ELSE
|
||||
-- Update the isRecentlyListed flag to false
|
||||
UPDATE "Service"
|
||||
SET "isRecentlyListed" = FALSE
|
||||
WHERE id = service_id;
|
||||
END IF;
|
||||
|
||||
-- Calculate final trust score (base 100)
|
||||
trust_score := trust_score + verification_factor + attributes_score;
|
||||
|
||||
-- Ensure the score is in reasonable bounds (0-100)
|
||||
trust_score := GREATEST(0, LEAST(100, trust_score));
|
||||
|
||||
RETURN trust_score;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Calculate overall score based on weighted average of privacy and trust scores
|
||||
CREATE OR REPLACE FUNCTION calculate_overall_score(service_id INT, privacy_score INT, trust_score INT)
|
||||
RETURNS INT AS $$
|
||||
DECLARE
|
||||
overall_score INT;
|
||||
BEGIN
|
||||
overall_score := CAST(ROUND(((privacy_score * 0.6) + (trust_score * 0.4)) / 10.0) AS INT);
|
||||
RETURN GREATEST(0, LEAST(10, overall_score));
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Main function to calculate all scores for a service
|
||||
CREATE OR REPLACE FUNCTION calculate_service_scores()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
privacy_score INT;
|
||||
trust_score INT;
|
||||
overall_score INT;
|
||||
service_id INT;
|
||||
BEGIN
|
||||
-- Determine which service ID to use based on the trigger context and table
|
||||
IF TG_TABLE_NAME = 'Service' THEN
|
||||
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
|
||||
service_id := NEW."id";
|
||||
END IF;
|
||||
ELSIF TG_TABLE_NAME = 'ServiceAttribute' THEN
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
service_id := OLD."serviceId";
|
||||
ELSE -- INSERT or UPDATE
|
||||
service_id := NEW."serviceId";
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Calculate each score
|
||||
privacy_score := calculate_privacy_score(service_id);
|
||||
trust_score := calculate_trust_score(service_id);
|
||||
overall_score := calculate_overall_score(service_id, privacy_score, trust_score);
|
||||
|
||||
-- Cap score if service is flagged as scam (verificationStatus = 'VERIFICATION_FAILED')
|
||||
IF (SELECT "verificationStatus" FROM "Service" WHERE "id" = service_id) = 'VERIFICATION_FAILED' THEN
|
||||
IF overall_score > 3 THEN
|
||||
overall_score := 3;
|
||||
ELSIF overall_score < 0 THEN
|
||||
overall_score := 0;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Update the service with the new scores
|
||||
UPDATE "Service"
|
||||
SET
|
||||
"privacyScore" = privacy_score,
|
||||
"trustScore" = trust_score,
|
||||
"overallScore" = overall_score
|
||||
WHERE "id" = service_id;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create trigger to recalculate scores when service is created or updated
|
||||
CREATE TRIGGER service_score_update_trigger
|
||||
AFTER INSERT OR UPDATE
|
||||
ON "Service"
|
||||
FOR EACH ROW
|
||||
WHEN (pg_trigger_depth() < 2) -- Prevent recursive triggering
|
||||
EXECUTE FUNCTION calculate_service_scores();
|
||||
|
||||
-- Create trigger to recalculate scores when service attributes change
|
||||
CREATE TRIGGER service_attribute_change_trigger
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON "ServiceAttribute"
|
||||
FOR EACH ROW
|
||||
WHEN (pg_trigger_depth() < 2) -- Prevent recursive triggering
|
||||
EXECUTE FUNCTION calculate_service_scores();
|
||||
|
||||
-- Function to queue score recalculation for all services with a specific attribute
|
||||
CREATE OR REPLACE FUNCTION queue_service_score_recalculation_for_attribute()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
service_rec RECORD;
|
||||
BEGIN
|
||||
-- Only trigger recalculation if relevant fields changed
|
||||
IF (TG_OP = 'UPDATE' AND (
|
||||
OLD."privacyPoints" != NEW."privacyPoints" OR
|
||||
OLD."trustPoints" != NEW."trustPoints" OR
|
||||
OLD."type" != NEW."type" OR
|
||||
OLD."category" != NEW."category"
|
||||
)) THEN
|
||||
-- Find all services that have this attribute and queue a recalculation job
|
||||
FOR service_rec IN
|
||||
SELECT DISTINCT sa."serviceId"
|
||||
FROM "ServiceAttribute" sa
|
||||
WHERE sa."attributeId" = NEW.id
|
||||
LOOP
|
||||
-- Insert a job into the queue table
|
||||
-- ON CONFLICT clause ensures we don't queue the same service multiple times per transaction
|
||||
INSERT INTO "ServiceScoreRecalculationJob" ("serviceId", "createdAt", "processedAt")
|
||||
VALUES (service_rec."serviceId", NOW(), NULL)
|
||||
ON CONFLICT ("serviceId") DO UPDATE SET "processedAt" = NULL, "createdAt" = NOW();
|
||||
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create constraint trigger to queue score recalculation when attributes are updated
|
||||
DROP TRIGGER IF EXISTS attribute_change_trigger ON "Attribute";
|
||||
CREATE CONSTRAINT TRIGGER attribute_change_trigger
|
||||
AFTER UPDATE
|
||||
ON "Attribute"
|
||||
DEFERRABLE INITIALLY DEFERRED
|
||||
FOR EACH ROW
|
||||
WHEN (pg_trigger_depth() < 2)
|
||||
EXECUTE FUNCTION queue_service_score_recalculation_for_attribute();
|
||||
@@ -1,57 +0,0 @@
|
||||
-- This script defines a PostgreSQL function and trigger to automatically calculate
|
||||
-- and update the average user rating for services based on associated comments.
|
||||
-- The average rating is recalculated whenever comments are added, updated, or deleted.
|
||||
|
||||
-- Drop existing triggers first
|
||||
DROP TRIGGER IF EXISTS comment_average_rating_trigger ON "Comment";
|
||||
|
||||
-- Drop existing functions
|
||||
DROP FUNCTION IF EXISTS calculate_average_rating();
|
||||
|
||||
-- Calculate average rating based on active comments with approved or verified status
|
||||
CREATE OR REPLACE FUNCTION calculate_average_rating()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
affected_service_id INT;
|
||||
average_user_rating DECIMAL;
|
||||
BEGIN
|
||||
-- Determine which service ID to use based on the trigger context
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
affected_service_id := OLD."serviceId";
|
||||
ELSE -- INSERT or UPDATE
|
||||
affected_service_id := NEW."serviceId";
|
||||
END IF;
|
||||
|
||||
-- Calculate average rating from active comments with approved or verified status
|
||||
-- Excluding suspicious comments and replies (comments with parentId not null)
|
||||
|
||||
SELECT AVG(rating) INTO average_user_rating
|
||||
FROM "Comment"
|
||||
WHERE "serviceId" = affected_service_id
|
||||
AND "parentId" IS NULL
|
||||
AND rating IS NOT NULL
|
||||
AND (status = 'APPROVED' OR status = 'VERIFIED')
|
||||
AND "ratingActive" = true
|
||||
AND suspicious = false;
|
||||
|
||||
-- Update the service with the new average rating
|
||||
UPDATE "Service"
|
||||
SET "averageUserRating" = average_user_rating
|
||||
WHERE "id" = affected_service_id;
|
||||
|
||||
-- Return the appropriate record based on operation
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
ELSE
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create trigger to recalculate average rating when comments are created, updated, or deleted
|
||||
CREATE TRIGGER comment_average_rating_trigger
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON "Comment"
|
||||
FOR EACH ROW
|
||||
WHEN (pg_trigger_depth() < 2) -- Prevent recursive triggering
|
||||
EXECUTE FUNCTION calculate_average_rating();
|
||||
@@ -1,48 +0,0 @@
|
||||
-- This script manages the `listedAt`, `verifiedAt`, and `isRecentlyListed` timestamps
|
||||
-- for services based on changes to their `verificationStatus`. It ensures these timestamps
|
||||
-- are set or cleared appropriately when a service's verification status is updated.
|
||||
|
||||
CREATE OR REPLACE FUNCTION manage_service_timestamps()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Manage listedAt timestamp
|
||||
IF NEW."verificationStatus" IN ('APPROVED', 'VERIFICATION_SUCCESS') THEN
|
||||
-- Set listedAt only on the first time status becomes APPROVED or VERIFICATION_SUCCESS
|
||||
IF OLD."listedAt" IS NULL THEN
|
||||
NEW."listedAt" := NOW();
|
||||
NEW."isRecentlyListed" := TRUE;
|
||||
END IF;
|
||||
ELSIF OLD."verificationStatus" IN ('APPROVED', 'VERIFICATION_SUCCESS') THEN
|
||||
-- Clear listedAt if the status changes FROM APPROVED or VERIFICATION_SUCCESS to something else
|
||||
-- The trigger's WHEN clause ensures NEW."verificationStatus" is different.
|
||||
NEW."listedAt" := NULL;
|
||||
NEW."isRecentlyListed" := FALSE;
|
||||
END IF;
|
||||
|
||||
-- Manage verifiedAt timestamp
|
||||
IF NEW."verificationStatus" = 'VERIFICATION_SUCCESS' THEN
|
||||
-- Set verifiedAt when status changes TO VERIFICATION_SUCCESS
|
||||
NEW."verifiedAt" := NOW();
|
||||
NEW."isRecentlyListed" := FALSE;
|
||||
ELSIF OLD."verificationStatus" = 'VERIFICATION_SUCCESS' THEN
|
||||
-- Clear verifiedAt when status changes FROM VERIFICATION_SUCCESS
|
||||
-- The trigger's WHEN clause ensures NEW."verificationStatus" is different.
|
||||
NEW."verifiedAt" := NULL;
|
||||
NEW."isRecentlyListed" := FALSE;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Drop the old trigger first if it exists under the old name
|
||||
DROP TRIGGER IF EXISTS trigger_set_service_listed_at ON "Service";
|
||||
-- Drop the trigger if it exists under the new name
|
||||
DROP TRIGGER IF EXISTS trigger_manage_service_timestamps ON "Service";
|
||||
|
||||
CREATE TRIGGER trigger_manage_service_timestamps
|
||||
BEFORE UPDATE OF "verificationStatus" ON "Service"
|
||||
FOR EACH ROW
|
||||
-- Only execute the function if the verificationStatus value has actually changed
|
||||
WHEN (OLD."verificationStatus" IS DISTINCT FROM NEW."verificationStatus")
|
||||
EXECUTE FUNCTION manage_service_timestamps();
|
||||
@@ -1,399 +0,0 @@
|
||||
-- Service Events Trigger
|
||||
-- This trigger automatically creates events when services are updated
|
||||
-- to track important changes over time
|
||||
|
||||
CREATE OR REPLACE FUNCTION trigger_service_events()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
change_descriptions TEXT[] := '{}';
|
||||
event_title TEXT;
|
||||
event_content TEXT;
|
||||
change_type TEXT := NULL;
|
||||
event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp();
|
||||
currency_desc TEXT;
|
||||
BEGIN
|
||||
-- Only proceed if this is an UPDATE operation
|
||||
IF TG_OP <> 'UPDATE' THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
-- Check for domain/URL changes
|
||||
IF OLD."serviceUrls" IS DISTINCT FROM NEW."serviceUrls" THEN
|
||||
change_descriptions := array_append(change_descriptions,
|
||||
'Service URLs updated from ' || array_to_string(OLD."serviceUrls", ', ') ||
|
||||
' to ' || array_to_string(NEW."serviceUrls", ', ')
|
||||
);
|
||||
change_type := COALESCE(change_type, 'Domain change');
|
||||
END IF;
|
||||
|
||||
-- Check for KYC level changes
|
||||
IF OLD."kycLevel" IS DISTINCT FROM NEW."kycLevel" THEN
|
||||
change_descriptions := array_append(change_descriptions,
|
||||
'KYC level changed from ' || OLD."kycLevel"::TEXT || ' to ' || NEW."kycLevel"::TEXT
|
||||
);
|
||||
change_type := COALESCE(change_type, 'KYC update');
|
||||
END IF;
|
||||
|
||||
-- Check for verification status changes
|
||||
IF OLD."verificationStatus" IS DISTINCT FROM NEW."verificationStatus" THEN
|
||||
change_descriptions := array_append(change_descriptions,
|
||||
'Verification status changed from ' || OLD."verificationStatus"::TEXT || ' to ' || NEW."verificationStatus"::TEXT
|
||||
);
|
||||
change_type := COALESCE(change_type, 'Verification update');
|
||||
END IF;
|
||||
|
||||
-- Check for description changes
|
||||
IF OLD.description IS DISTINCT FROM NEW.description THEN
|
||||
change_descriptions := array_append(change_descriptions, 'Description was updated');
|
||||
change_type := COALESCE(change_type, 'Description update');
|
||||
END IF;
|
||||
|
||||
-- Check for currency changes
|
||||
IF OLD."acceptedCurrencies" IS DISTINCT FROM NEW."acceptedCurrencies" THEN
|
||||
-- Find currencies added
|
||||
WITH
|
||||
old_currencies AS (SELECT unnest(OLD."acceptedCurrencies") AS currency),
|
||||
new_currencies AS (SELECT unnest(NEW."acceptedCurrencies") AS currency),
|
||||
added_currencies AS (
|
||||
SELECT currency FROM new_currencies
|
||||
EXCEPT
|
||||
SELECT currency FROM old_currencies
|
||||
),
|
||||
removed_currencies AS (
|
||||
SELECT currency FROM old_currencies
|
||||
EXCEPT
|
||||
SELECT currency FROM new_currencies
|
||||
)
|
||||
|
||||
-- Temp variable for currency description
|
||||
SELECT
|
||||
CASE
|
||||
WHEN (SELECT COUNT(*) FROM added_currencies) > 0 AND (SELECT COUNT(*) FROM removed_currencies) > 0 THEN
|
||||
'Currencies updated: added ' || array_to_string(ARRAY(SELECT currency FROM added_currencies), ', ') ||
|
||||
', removed ' || array_to_string(ARRAY(SELECT currency FROM removed_currencies), ', ')
|
||||
WHEN (SELECT COUNT(*) FROM added_currencies) > 0 THEN
|
||||
'Added currencies: ' || array_to_string(ARRAY(SELECT currency FROM added_currencies), ', ')
|
||||
WHEN (SELECT COUNT(*) FROM removed_currencies) > 0 THEN
|
||||
'Removed currencies: ' || array_to_string(ARRAY(SELECT currency FROM removed_currencies), ', ')
|
||||
ELSE
|
||||
'Currencies changed'
|
||||
END
|
||||
INTO currency_desc;
|
||||
|
||||
IF currency_desc IS NOT NULL AND currency_desc <> '' THEN
|
||||
change_descriptions := array_append(change_descriptions, currency_desc);
|
||||
change_type := COALESCE(change_type, 'Currency update');
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- If there are changes, create an event
|
||||
IF array_length(change_descriptions, 1) > 0 THEN
|
||||
-- Create a title based on number of changes
|
||||
IF array_length(change_descriptions, 1) = 1 THEN
|
||||
event_title := COALESCE(change_type, 'Service updated'); -- Ensure title is not null
|
||||
ELSE
|
||||
event_title := 'Service updated';
|
||||
END IF;
|
||||
|
||||
-- Create content with all changes
|
||||
event_content := array_to_string(change_descriptions, '. ');
|
||||
|
||||
-- Ensure content is not null or empty
|
||||
IF event_content IS NULL OR event_content = '' THEN
|
||||
event_content := 'Service details changed (content unavailable)';
|
||||
END IF;
|
||||
|
||||
-- Insert the event
|
||||
INSERT INTO "Event" (
|
||||
"title",
|
||||
"content",
|
||||
"type",
|
||||
"visible",
|
||||
"startedAt",
|
||||
"endedAt",
|
||||
"serviceId"
|
||||
) VALUES (
|
||||
event_title,
|
||||
event_content,
|
||||
'UPDATE',
|
||||
TRUE,
|
||||
event_time,
|
||||
event_time,
|
||||
NEW.id
|
||||
);
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create a trigger for service updates
|
||||
DROP TRIGGER IF EXISTS service_events_trigger ON "Service";
|
||||
CREATE TRIGGER service_events_trigger
|
||||
AFTER UPDATE OF "serviceUrls", "kycLevel", "verificationStatus", "description", "acceptedCurrencies" ON "Service"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_service_events();
|
||||
|
||||
-- Additional trigger to monitor changes to ServiceAttribute
|
||||
CREATE OR REPLACE FUNCTION trigger_service_attribute_events()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
attribute_name TEXT;
|
||||
service_name TEXT;
|
||||
event_title TEXT := 'Attribute change'; -- Default title
|
||||
event_content TEXT;
|
||||
event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp();
|
||||
target_service_id INT;
|
||||
service_exists BOOLEAN;
|
||||
service_created_at TIMESTAMP WITH TIME ZONE;
|
||||
is_new_service BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- Determine target service ID and operation type
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
target_service_id := NEW."serviceId";
|
||||
|
||||
-- Check if this is a new service (created within the last minute)
|
||||
-- This helps prevent events when attributes are initially added to a new service
|
||||
SELECT "createdAt" INTO service_created_at FROM "Service" WHERE id = target_service_id;
|
||||
IF service_created_at IS NOT NULL AND (event_time - service_created_at) < INTERVAL '1 minute' THEN
|
||||
is_new_service := TRUE;
|
||||
RETURN NEW; -- Skip event creation for new services
|
||||
END IF;
|
||||
|
||||
SELECT title INTO attribute_name FROM "Attribute" WHERE id = NEW."attributeId";
|
||||
SELECT name INTO service_name FROM "Service" WHERE id = target_service_id;
|
||||
IF attribute_name IS NOT NULL AND service_name IS NOT NULL THEN
|
||||
event_title := 'Attribute added';
|
||||
event_content := 'Attribute "' || attribute_name || '" was added to ' || service_name;
|
||||
ELSE
|
||||
event_content := 'An attribute was added (details unavailable)';
|
||||
END IF;
|
||||
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
target_service_id := OLD."serviceId";
|
||||
-- Check if the service still exists before trying to fetch its name or create an event
|
||||
SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists;
|
||||
IF service_exists THEN
|
||||
SELECT title INTO attribute_name FROM "Attribute" WHERE id = OLD."attributeId";
|
||||
SELECT name INTO service_name FROM "Service" WHERE id = target_service_id;
|
||||
IF attribute_name IS NOT NULL AND service_name IS NOT NULL THEN
|
||||
event_title := 'Attribute removed';
|
||||
event_content := 'Attribute "' || attribute_name || '" was removed from ' || service_name;
|
||||
ELSE
|
||||
-- This case might happen if attribute was deleted concurrently
|
||||
event_content := 'An attribute was removed (details unavailable)';
|
||||
END IF;
|
||||
ELSE
|
||||
-- Service was deleted, don't create an event
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Ensure content is not null/empty and insert
|
||||
IF event_content IS NOT NULL AND event_content <> '' AND target_service_id IS NOT NULL AND NOT is_new_service THEN
|
||||
-- Re-check service existence right before insert just in case of concurrency on INSERT
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists;
|
||||
END IF;
|
||||
|
||||
IF service_exists THEN
|
||||
INSERT INTO "Event" (
|
||||
"title",
|
||||
"content",
|
||||
"type",
|
||||
"visible",
|
||||
"startedAt",
|
||||
"endedAt",
|
||||
"serviceId"
|
||||
) VALUES (
|
||||
event_title,
|
||||
event_content,
|
||||
'UPDATE',
|
||||
TRUE,
|
||||
event_time,
|
||||
event_time,
|
||||
target_service_id
|
||||
);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Return appropriate record
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
RETURN NEW;
|
||||
ELSE
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create a trigger for service attribute changes
|
||||
DROP TRIGGER IF EXISTS service_attribute_events_trigger ON "ServiceAttribute";
|
||||
CREATE TRIGGER service_attribute_events_trigger
|
||||
AFTER INSERT OR DELETE ON "ServiceAttribute"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_service_attribute_events();
|
||||
|
||||
-- Additional trigger to monitor changes to service categories
|
||||
CREATE OR REPLACE FUNCTION trigger_service_category_events()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
category_name TEXT;
|
||||
service_name TEXT;
|
||||
event_title TEXT := 'Category change'; -- Default title
|
||||
event_content TEXT;
|
||||
event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp();
|
||||
target_service_id INT;
|
||||
service_exists BOOLEAN;
|
||||
service_created_at TIMESTAMP WITH TIME ZONE;
|
||||
is_new_service BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- Determine target service ID and operation type
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
target_service_id := NEW."A";
|
||||
|
||||
-- Check if this is a new service (created within the last minute)
|
||||
-- This helps prevent events when categories are initially added to a new service
|
||||
SELECT "createdAt" INTO service_created_at FROM "Service" WHERE id = target_service_id;
|
||||
IF service_created_at IS NOT NULL AND (event_time - service_created_at) < INTERVAL '1 minute' THEN
|
||||
is_new_service := TRUE;
|
||||
RETURN NEW; -- Skip event creation for new services
|
||||
END IF;
|
||||
|
||||
SELECT name INTO category_name FROM "Category" WHERE id = NEW."B";
|
||||
SELECT name INTO service_name FROM "Service" WHERE id = target_service_id;
|
||||
IF category_name IS NOT NULL AND service_name IS NOT NULL THEN
|
||||
event_title := 'Category added';
|
||||
event_content := 'Category "' || category_name || '" was added to ' || service_name;
|
||||
ELSE
|
||||
event_content := 'A category was added (details unavailable)';
|
||||
END IF;
|
||||
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
target_service_id := OLD."A";
|
||||
-- Check if the service still exists before trying to fetch its name or create an event
|
||||
SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists;
|
||||
IF service_exists THEN
|
||||
SELECT name INTO category_name FROM "Category" WHERE id = OLD."B";
|
||||
SELECT name INTO service_name FROM "Service" WHERE id = target_service_id;
|
||||
IF category_name IS NOT NULL AND service_name IS NOT NULL THEN
|
||||
event_title := 'Category removed';
|
||||
event_content := 'Category "' || category_name || '" was removed from ' || service_name;
|
||||
ELSE
|
||||
-- This case might happen if category was deleted concurrently
|
||||
event_content := 'A category was removed (details unavailable)';
|
||||
END IF;
|
||||
ELSE
|
||||
-- Service was deleted, don't create an event
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Ensure content is not null/empty and insert
|
||||
IF event_content IS NOT NULL AND event_content <> '' AND target_service_id IS NOT NULL AND NOT is_new_service THEN
|
||||
-- Re-check service existence right before insert just in case of concurrency on INSERT
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = target_service_id) INTO service_exists;
|
||||
END IF;
|
||||
|
||||
IF service_exists THEN
|
||||
INSERT INTO "Event" (
|
||||
"title",
|
||||
"content",
|
||||
"type",
|
||||
"visible",
|
||||
"startedAt",
|
||||
"endedAt",
|
||||
"serviceId"
|
||||
) VALUES (
|
||||
event_title,
|
||||
event_content,
|
||||
'UPDATE',
|
||||
TRUE,
|
||||
event_time,
|
||||
event_time,
|
||||
target_service_id
|
||||
);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Return appropriate record
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
RETURN NEW;
|
||||
ELSE
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create a trigger for service category changes (on the junction table)
|
||||
DROP TRIGGER IF EXISTS service_category_events_trigger ON "_ServiceToCategory";
|
||||
CREATE TRIGGER service_category_events_trigger
|
||||
AFTER INSERT OR DELETE ON "_ServiceToCategory"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_service_category_events();
|
||||
|
||||
-- Verification Steps Trigger
|
||||
-- This trigger creates events when verification steps are added or status changes
|
||||
CREATE OR REPLACE FUNCTION trigger_verification_step_events()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
service_name TEXT;
|
||||
event_title TEXT;
|
||||
event_content TEXT;
|
||||
event_time TIMESTAMP WITH TIME ZONE := transaction_timestamp();
|
||||
service_exists BOOLEAN;
|
||||
BEGIN
|
||||
-- Check if the service exists
|
||||
SELECT EXISTS (SELECT 1 FROM "Service" WHERE id = NEW."serviceId") INTO service_exists;
|
||||
|
||||
IF NOT service_exists THEN
|
||||
-- Service was deleted or doesn't exist, don't create an event
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
-- Get service name
|
||||
SELECT name INTO service_name FROM "Service" WHERE id = NEW."serviceId";
|
||||
|
||||
-- Handle different operations
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
event_title := 'Verification step added';
|
||||
event_content := '"' || NEW.title || '" was added';
|
||||
|
||||
ELSIF TG_OP = 'UPDATE' AND OLD.status IS DISTINCT FROM NEW.status THEN
|
||||
event_title := 'Verification step ' || replace(lower(NEW.status::TEXT), '_', ' ');
|
||||
event_content := '"' || NEW.title || '" status changed from ' ||
|
||||
replace(lower(OLD.status::TEXT), '_', ' ') || ' to ' || replace(lower(NEW.status::TEXT), '_', ' ');
|
||||
ELSE
|
||||
-- No relevant changes, exit
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
-- Insert the event
|
||||
INSERT INTO "Event" (
|
||||
"title",
|
||||
"content",
|
||||
"type",
|
||||
"visible",
|
||||
"startedAt",
|
||||
"endedAt",
|
||||
"serviceId"
|
||||
) VALUES (
|
||||
event_title,
|
||||
event_content,
|
||||
'UPDATE',
|
||||
TRUE,
|
||||
event_time,
|
||||
event_time,
|
||||
NEW."serviceId"
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create trigger for verification step changes
|
||||
DROP TRIGGER IF EXISTS verification_step_events_trigger ON "VerificationStep";
|
||||
CREATE TRIGGER verification_step_events_trigger
|
||||
AFTER INSERT OR UPDATE OF status ON "VerificationStep"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_verification_step_events();
|
||||
@@ -1,227 +0,0 @@
|
||||
-- Function & Trigger for Root Comment Insertions (Approved/Verified)
|
||||
CREATE OR REPLACE FUNCTION notify_root_comment_inserted()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
watcher_count INT;
|
||||
BEGIN
|
||||
RAISE NOTICE '[notify_root_comment_inserted] Trigger fired for comment ID: %', NEW.id;
|
||||
WITH watchers AS (
|
||||
SELECT np."userId", np."enableNotifyPendingRepliesOnWatch"
|
||||
FROM "_onRootCommentCreatedForServices" rc
|
||||
JOIN "NotificationPreferences" np ON rc."A" = np."id"
|
||||
WHERE rc."B" = NEW."serviceId"
|
||||
AND np."userId" <> NEW."authorId"
|
||||
)
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutCommentId")
|
||||
SELECT w."userId",
|
||||
'ROOT_COMMENT_CREATED',
|
||||
NEW."id"
|
||||
FROM watchers w
|
||||
WHERE (
|
||||
NEW.status IN ('APPROVED', 'VERIFIED')
|
||||
OR (NEW.status = 'PENDING' AND w."enableNotifyPendingRepliesOnWatch")
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
RAISE NOTICE '[notify_root_comment_inserted] Inserted % notifications for comment ID: %', FOUND, NEW.id;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_notify_root_comment_inserted ON "Comment";
|
||||
CREATE TRIGGER trg_notify_root_comment_inserted
|
||||
AFTER INSERT ON "Comment"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW."parentId" IS NULL)
|
||||
EXECUTE FUNCTION notify_root_comment_inserted();
|
||||
|
||||
-- Function & Trigger for Reply Comment Insertions
|
||||
CREATE OR REPLACE FUNCTION notify_reply_comment_inserted()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
WITH watchers AS (
|
||||
SELECT np."userId", np."enableNotifyPendingRepliesOnWatch"
|
||||
FROM "_watchedComments" w
|
||||
JOIN "NotificationPreferences" np ON w."B" = np."id"
|
||||
WHERE w."A" = NEW."parentId"
|
||||
AND np."userId" <> NEW."authorId"
|
||||
)
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutCommentId")
|
||||
SELECT w."userId",
|
||||
'REPLY_COMMENT_CREATED',
|
||||
NEW."id"
|
||||
FROM watchers w
|
||||
WHERE (
|
||||
NEW.status IN ('APPROVED', 'VERIFIED')
|
||||
OR (NEW.status = 'PENDING' AND w."enableNotifyPendingRepliesOnWatch")
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_notify_reply_comment_inserted ON "Comment";
|
||||
CREATE TRIGGER trg_notify_reply_comment_inserted
|
||||
AFTER INSERT ON "Comment"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW."parentId" IS NOT NULL)
|
||||
EXECUTE FUNCTION notify_reply_comment_inserted();
|
||||
|
||||
-- Function & Trigger for Reply Approval/Verification
|
||||
CREATE OR REPLACE FUNCTION notify_reply_approved()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
WITH watchers AS (
|
||||
SELECT np."userId"
|
||||
FROM "_watchedComments" w
|
||||
JOIN "NotificationPreferences" np ON w."B" = np."id"
|
||||
WHERE w."A" = NEW."parentId"
|
||||
AND np."userId" <> NEW."authorId"
|
||||
)
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutCommentId")
|
||||
SELECT w."userId",
|
||||
'REPLY_COMMENT_CREATED',
|
||||
NEW."id"
|
||||
FROM watchers w
|
||||
ON CONFLICT DO NOTHING;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_notify_reply_approved ON "Comment";
|
||||
CREATE TRIGGER trg_notify_reply_approved
|
||||
AFTER UPDATE OF status ON "Comment"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW."parentId" IS NOT NULL AND NEW.status IN ('APPROVED', 'VERIFIED') AND OLD.status NOT IN ('APPROVED', 'VERIFIED'))
|
||||
EXECUTE FUNCTION notify_reply_approved();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_notify_root_approved ON "Comment";
|
||||
|
||||
CREATE OR REPLACE FUNCTION notify_root_approved()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
WITH watchers AS (
|
||||
SELECT np."userId"
|
||||
FROM "_onRootCommentCreatedForServices" rc
|
||||
JOIN "NotificationPreferences" np ON rc."A" = np."id"
|
||||
WHERE rc."B" = NEW."serviceId"
|
||||
AND np."userId" <> NEW."authorId"
|
||||
AND NOT np."enableNotifyPendingRepliesOnWatch"
|
||||
)
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutCommentId")
|
||||
SELECT w."userId",
|
||||
'ROOT_COMMENT_CREATED',
|
||||
NEW."id"
|
||||
FROM watchers w
|
||||
ON CONFLICT DO NOTHING;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_notify_root_approved
|
||||
AFTER UPDATE OF status ON "Comment"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW."parentId" IS NULL AND NEW.status IN ('APPROVED', 'VERIFIED') AND OLD.status NOT IN ('APPROVED', 'VERIFIED'))
|
||||
EXECUTE FUNCTION notify_root_approved();
|
||||
|
||||
-- Function & Trigger for Comment Status Changes (Status, Suspicious, AdminReview)
|
||||
CREATE OR REPLACE FUNCTION notify_comment_status_changed()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
v_status_change "CommentStatusChange" := NULL;
|
||||
BEGIN
|
||||
-- Determine the status change type
|
||||
IF NEW.status <> OLD.status THEN
|
||||
IF NEW.status = 'APPROVED' THEN v_status_change := 'STATUS_CHANGED_TO_APPROVED';
|
||||
ELSIF NEW.status = 'VERIFIED' THEN v_status_change := 'STATUS_CHANGED_TO_VERIFIED';
|
||||
ELSIF NEW.status = 'REJECTED' THEN v_status_change := 'STATUS_CHANGED_TO_REJECTED';
|
||||
ELSIF (NEW.status = 'PENDING' OR NEW.status = 'HUMAN_PENDING') AND (OLD.status <> 'PENDING' AND OLD.status <> 'HUMAN_PENDING') THEN v_status_change := 'STATUS_CHANGED_TO_PENDING';
|
||||
END IF;
|
||||
ELSIF NEW.suspicious <> OLD.suspicious THEN
|
||||
IF NEW.suspicious = true THEN v_status_change := 'MARKED_AS_SPAM';
|
||||
ELSE v_status_change := 'UNMARKED_AS_SPAM';
|
||||
END IF;
|
||||
ELSIF NEW."requiresAdminReview" <> OLD."requiresAdminReview" THEN
|
||||
IF NEW."requiresAdminReview" = true THEN v_status_change := 'MARKED_FOR_ADMIN_REVIEW';
|
||||
ELSE v_status_change := 'UNMARKED_FOR_ADMIN_REVIEW';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- If a relevant status change occurred, notify watchers of THIS comment
|
||||
IF v_status_change IS NOT NULL THEN
|
||||
WITH watchers AS (
|
||||
-- Get all watchers excluding author
|
||||
SELECT np."userId"
|
||||
FROM "_watchedComments" w
|
||||
JOIN "NotificationPreferences" np ON w."B" = np."id"
|
||||
WHERE w."A" = NEW."id"
|
||||
AND np."userId" <> NEW."authorId"
|
||||
AND np."enableOnMyCommentStatusChange"
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Add author if they have enabled notifications for their own comments
|
||||
SELECT np."userId"
|
||||
FROM "NotificationPreferences" np
|
||||
WHERE np."userId" = NEW."authorId"
|
||||
AND np."enableOnMyCommentStatusChange"
|
||||
)
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutCommentId", "aboutCommentStatusChange")
|
||||
SELECT w."userId",
|
||||
'COMMENT_STATUS_CHANGE',
|
||||
NEW."id",
|
||||
v_status_change
|
||||
FROM watchers w
|
||||
ON CONFLICT DO NOTHING;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_notify_comment_status_changed ON "Comment";
|
||||
CREATE TRIGGER trg_notify_comment_status_changed
|
||||
AFTER UPDATE OF status, suspicious, "requiresAdminReview" ON "Comment"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.status <> OLD.status OR NEW.suspicious <> OLD.suspicious OR NEW."requiresAdminReview" <> OLD."requiresAdminReview")
|
||||
EXECUTE FUNCTION notify_comment_status_changed();
|
||||
|
||||
-- Function & Trigger for Community Note Added
|
||||
CREATE OR REPLACE FUNCTION notify_community_note_added()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Notify watchers of this specific comment (excluding author)
|
||||
WITH watchers AS (
|
||||
SELECT np."userId"
|
||||
FROM "_watchedComments" w
|
||||
JOIN "NotificationPreferences" np ON w."B" = np."id"
|
||||
WHERE w."A" = NEW."id"
|
||||
AND np."userId" <> NEW."authorId"
|
||||
)
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutCommentId")
|
||||
SELECT w."userId",
|
||||
'COMMUNITY_NOTE_ADDED',
|
||||
NEW."id"
|
||||
FROM watchers w
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Always notify the author
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutCommentId")
|
||||
VALUES (NEW."authorId", 'COMMUNITY_NOTE_ADDED', NEW."id")
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_notify_community_note_added ON "Comment";
|
||||
CREATE TRIGGER trg_notify_community_note_added
|
||||
AFTER UPDATE OF "communityNote" ON "Comment"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW."communityNote" IS NOT NULL AND NEW."communityNote" <> '' AND (OLD."communityNote" IS NULL OR OLD."communityNote" = ''))
|
||||
EXECUTE FUNCTION notify_community_note_added();
|
||||
|
||||
-- Remove the old monolithic trigger and function definition if they still exist
|
||||
DROP TRIGGER IF EXISTS comment_notifications_trigger ON "Comment";
|
||||
DROP FUNCTION IF EXISTS trigger_comment_notifications();
|
||||
@@ -1,72 +0,0 @@
|
||||
CREATE OR REPLACE FUNCTION trigger_service_suggestion_notifications()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
suggestion_status_change "ServiceSuggestionStatusChange";
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN -- Corresponds to ServiceSuggestionMessage insert
|
||||
-- Notify suggestion author (if not the sender)
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutServiceSuggestionId", "aboutServiceSuggestionMessageId")
|
||||
SELECT s."userId", 'SUGGESTION_MESSAGE', NEW."suggestionId", NEW."id"
|
||||
FROM "ServiceSuggestion" s
|
||||
WHERE s."id" = NEW."suggestionId"
|
||||
AND s."userId" <> NEW."userId"
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Notification" n
|
||||
WHERE n."userId" = s."userId"
|
||||
AND n."type" = 'SUGGESTION_MESSAGE'
|
||||
AND n."aboutServiceSuggestionMessageId" = NEW."id"
|
||||
);
|
||||
|
||||
-- Notify all admins (except the sender), but only if sender is not admin
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutServiceSuggestionId", "aboutServiceSuggestionMessageId")
|
||||
SELECT u."id", 'SUGGESTION_MESSAGE', NEW."suggestionId", NEW."id"
|
||||
FROM "User" u
|
||||
WHERE u."admin" = true
|
||||
AND u."id" <> NEW."userId"
|
||||
-- Only notify admins if the message sender is not an admin
|
||||
AND NOT EXISTS (SELECT 1 FROM "User" WHERE "id" = NEW."userId" AND "admin" = true)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Notification" n
|
||||
WHERE n."userId" = u."id"
|
||||
AND n."type" = 'SUGGESTION_MESSAGE'
|
||||
AND n."aboutServiceSuggestionMessageId" = NEW."id"
|
||||
);
|
||||
|
||||
ELSIF TG_OP = 'UPDATE' THEN -- Corresponds to ServiceSuggestion status update
|
||||
-- Notify suggestion author about status change
|
||||
IF NEW.status <> OLD.status THEN
|
||||
IF NEW.status = 'PENDING' THEN
|
||||
suggestion_status_change := 'STATUS_CHANGED_TO_PENDING';
|
||||
ELSIF NEW.status = 'APPROVED' THEN
|
||||
suggestion_status_change := 'STATUS_CHANGED_TO_APPROVED';
|
||||
ELSIF NEW.status = 'REJECTED' THEN
|
||||
suggestion_status_change := 'STATUS_CHANGED_TO_REJECTED';
|
||||
ELSIF NEW.status = 'WITHDRAWN' THEN
|
||||
suggestion_status_change := 'STATUS_CHANGED_TO_WITHDRAWN';
|
||||
END IF;
|
||||
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutServiceSuggestionId", "aboutSuggestionStatusChange")
|
||||
VALUES (NEW."userId", 'SUGGESTION_STATUS_CHANGE', NEW."id", suggestion_status_change);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Use RETURN NULL for AFTER triggers as the return value is ignored.
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger for new messages
|
||||
DROP TRIGGER IF EXISTS service_suggestion_message_notifications_trigger ON "ServiceSuggestionMessage";
|
||||
CREATE TRIGGER service_suggestion_message_notifications_trigger
|
||||
AFTER INSERT ON "ServiceSuggestionMessage"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_service_suggestion_notifications();
|
||||
|
||||
-- Trigger for status updates
|
||||
DROP TRIGGER IF EXISTS service_suggestion_status_notifications_trigger ON "ServiceSuggestion";
|
||||
CREATE TRIGGER service_suggestion_status_notifications_trigger
|
||||
AFTER UPDATE OF status ON "ServiceSuggestion"
|
||||
FOR EACH ROW
|
||||
-- Only run the function if the status actually changed
|
||||
WHEN (OLD.status IS DISTINCT FROM NEW.status)
|
||||
EXECUTE FUNCTION trigger_service_suggestion_notifications();
|
||||
@@ -1,28 +0,0 @@
|
||||
CREATE OR REPLACE FUNCTION trigger_service_events_notifications()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Handle new Event insertions
|
||||
IF TG_TABLE_NAME = 'Event' AND TG_OP = 'INSERT' THEN
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutServiceId", "aboutEventId")
|
||||
SELECT np."userId", 'EVENT_CREATED', NEW."serviceId", NEW.id
|
||||
FROM "_onEventCreatedForServices" oes
|
||||
JOIN "NotificationPreferences" np ON oes."A" = np.id
|
||||
WHERE oes."B" = NEW."serviceId"
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Notification" n
|
||||
WHERE n."userId" = np."userId"
|
||||
AND n."type" = 'EVENT_CREATED'
|
||||
AND n."aboutEventId" = NEW.id
|
||||
);
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger for new Events
|
||||
DROP TRIGGER IF EXISTS eVENT_CREATED_notifications_trigger ON "Event";
|
||||
CREATE TRIGGER eVENT_CREATED_notifications_trigger
|
||||
AFTER INSERT ON "Event"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_service_events_notifications();
|
||||
@@ -1,37 +0,0 @@
|
||||
CREATE OR REPLACE FUNCTION trigger_service_verification_status_change_notifications()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
v_status_change "ServiceVerificationStatusChange";
|
||||
BEGIN
|
||||
-- Check if verificationStatus actually changed
|
||||
IF OLD."verificationStatus" IS DISTINCT FROM NEW."verificationStatus" THEN
|
||||
-- Determine the correct ServiceVerificationStatusChange enum value
|
||||
SELECT CASE NEW."verificationStatus"
|
||||
WHEN 'COMMUNITY_CONTRIBUTED' THEN 'STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED'
|
||||
WHEN 'APPROVED' THEN 'STATUS_CHANGED_TO_APPROVED'
|
||||
WHEN 'VERIFICATION_SUCCESS' THEN 'STATUS_CHANGED_TO_VERIFICATION_SUCCESS'
|
||||
WHEN 'VERIFICATION_FAILED' THEN 'STATUS_CHANGED_TO_VERIFICATION_FAILED'
|
||||
ELSE NULL
|
||||
END
|
||||
INTO v_status_change;
|
||||
|
||||
-- Only insert if we determined a valid status change enum
|
||||
IF v_status_change IS NOT NULL THEN
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutServiceId", "aboutServiceVerificationStatusChange")
|
||||
SELECT np."userId", 'SERVICE_VERIFICATION_STATUS_CHANGE', NEW.id, v_status_change
|
||||
FROM "_onVerificationChangeForServices" oes
|
||||
JOIN "NotificationPreferences" np ON oes."A" = np.id -- A -> NotificationPreferences.id
|
||||
WHERE oes."B" = NEW.id; -- B -> Service.id
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN NULL; -- Return NULL for AFTER trigger
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger for Service verificationStatus updates
|
||||
DROP TRIGGER IF EXISTS service_verification_status_change_notifications_trigger ON "Service";
|
||||
CREATE TRIGGER service_verification_status_change_notifications_trigger
|
||||
AFTER UPDATE ON "Service"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_service_verification_status_change_notifications();
|
||||
@@ -1,62 +0,0 @@
|
||||
CREATE OR REPLACE FUNCTION trigger_user_status_change_notifications()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
status_change "AccountStatusChange";
|
||||
BEGIN
|
||||
-- Check for admin status change
|
||||
IF OLD.admin IS DISTINCT FROM NEW.admin THEN
|
||||
IF NEW.admin = true THEN
|
||||
status_change := 'ADMIN_TRUE';
|
||||
ELSE
|
||||
status_change := 'ADMIN_FALSE';
|
||||
END IF;
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange")
|
||||
VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change);
|
||||
END IF;
|
||||
|
||||
-- Check for verified status change
|
||||
IF OLD.verified IS DISTINCT FROM NEW.verified THEN
|
||||
IF NEW.verified = true THEN
|
||||
status_change := 'VERIFIED_TRUE';
|
||||
ELSE
|
||||
status_change := 'VERIFIED_FALSE';
|
||||
END IF;
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange")
|
||||
VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change);
|
||||
END IF;
|
||||
|
||||
-- Check for verifier status change
|
||||
IF OLD.verifier IS DISTINCT FROM NEW.verifier THEN
|
||||
IF NEW.verifier = true THEN
|
||||
status_change := 'VERIFIER_TRUE';
|
||||
ELSE
|
||||
status_change := 'VERIFIER_FALSE';
|
||||
END IF;
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange")
|
||||
VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change);
|
||||
END IF;
|
||||
|
||||
-- Check for spammer status change
|
||||
IF OLD.spammer IS DISTINCT FROM NEW.spammer THEN
|
||||
IF NEW.spammer = true THEN
|
||||
status_change := 'SPAMMER_TRUE';
|
||||
ELSE
|
||||
status_change := 'SPAMMER_FALSE';
|
||||
END IF;
|
||||
INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange")
|
||||
VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change);
|
||||
END IF;
|
||||
|
||||
-- Return NULL for AFTER triggers as the return value is ignored.
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Drop the trigger if it exists to ensure a clean setup
|
||||
DROP TRIGGER IF EXISTS user_status_change_notifications_trigger ON "User";
|
||||
|
||||
-- Create the trigger to fire after updates on specific status columns
|
||||
CREATE TRIGGER user_status_change_notifications_trigger
|
||||
AFTER UPDATE OF admin, verified, verifier, spammer ON "User"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_user_status_change_notifications();
|
||||
Reference in New Issue
Block a user