Release 2025-05-19
This commit is contained in:
798
web/prisma/migrations/20250518085822_initial/migration.sql
Normal file
798
web/prisma/migrations/20250518085822_initial/migration.sql
Normal file
@@ -0,0 +1,798 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CommentStatus" AS ENUM ('PENDING', 'HUMAN_PENDING', 'APPROVED', 'VERIFIED', 'REJECTED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "OrderIdStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "VerificationStatus" AS ENUM ('COMMUNITY_CONTRIBUTED', 'APPROVED', 'VERIFICATION_SUCCESS', 'VERIFICATION_FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceInfoBanner" AS ENUM ('NONE', 'NO_LONGER_OPERATIONAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceVisibility" AS ENUM ('PUBLIC', 'UNLISTED', 'HIDDEN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Currency" AS ENUM ('MONERO', 'BITCOIN', 'LIGHTNING', 'FIAT', 'CASH');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EventType" AS ENUM ('WARNING', 'WARNING_SOLVED', 'ALERT', 'ALERT_SOLVED', 'INFO', 'NORMAL', 'UPDATE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceUserRole" AS ENUM ('OWNER', 'ADMIN', 'MODERATOR', 'SUPPORT', 'TEAM_MEMBER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AccountStatusChange" AS ENUM ('ADMIN_TRUE', 'ADMIN_FALSE', 'VERIFIED_TRUE', 'VERIFIED_FALSE', 'VERIFIER_TRUE', 'VERIFIER_FALSE', 'SPAMMER_TRUE', 'SPAMMER_FALSE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NotificationType" AS ENUM ('COMMENT_STATUS_CHANGE', 'REPLY_COMMENT_CREATED', 'COMMUNITY_NOTE_ADDED', 'ROOT_COMMENT_CREATED', 'SUGGESTION_MESSAGE', 'SUGGESTION_STATUS_CHANGE', 'ACCOUNT_STATUS_CHANGE', 'EVENT_CREATED', 'SERVICE_VERIFICATION_STATUS_CHANGE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CommentStatusChange" AS ENUM ('MARKED_AS_SPAM', 'UNMARKED_AS_SPAM', 'MARKED_FOR_ADMIN_REVIEW', 'UNMARKED_FOR_ADMIN_REVIEW', 'STATUS_CHANGED_TO_APPROVED', 'STATUS_CHANGED_TO_VERIFIED', 'STATUS_CHANGED_TO_REJECTED', 'STATUS_CHANGED_TO_PENDING');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceVerificationStatusChange" AS ENUM ('STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED', 'STATUS_CHANGED_TO_APPROVED', 'STATUS_CHANGED_TO_VERIFICATION_SUCCESS', 'STATUS_CHANGED_TO_VERIFICATION_FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceSuggestionStatusChange" AS ENUM ('STATUS_CHANGED_TO_PENDING', 'STATUS_CHANGED_TO_APPROVED', 'STATUS_CHANGED_TO_REJECTED', 'STATUS_CHANGED_TO_WITHDRAWN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceSuggestionStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED', 'WITHDRAWN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceSuggestionType" AS ENUM ('CREATE_SERVICE', 'EDIT_SERVICE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AttributeCategory" AS ENUM ('PRIVACY', 'TRUST');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AttributeType" AS ENUM ('GOOD', 'BAD', 'WARNING', 'INFO');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "VerificationStepStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'PASSED', 'FAILED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Comment" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"upvotes" INTEGER NOT NULL DEFAULT 0,
|
||||
"status" "CommentStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"suspicious" BOOLEAN NOT NULL DEFAULT false,
|
||||
"requiresAdminReview" BOOLEAN NOT NULL DEFAULT false,
|
||||
"communityNote" TEXT,
|
||||
"verificationNote" TEXT,
|
||||
"internalNote" TEXT,
|
||||
"privateContext" TEXT,
|
||||
"orderId" VARCHAR(100),
|
||||
"orderIdStatus" "OrderIdStatus" DEFAULT 'PENDING',
|
||||
"kycRequested" BOOLEAN NOT NULL DEFAULT false,
|
||||
"fundsBlocked" BOOLEAN NOT NULL DEFAULT false,
|
||||
"content" TEXT NOT NULL,
|
||||
"rating" SMALLINT,
|
||||
"ratingActive" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"authorId" INTEGER NOT NULL,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
"parentId" INTEGER,
|
||||
|
||||
CONSTRAINT "Comment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Notification" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"type" "NotificationType" NOT NULL,
|
||||
"read" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"aboutCommentId" INTEGER,
|
||||
"aboutEventId" INTEGER,
|
||||
"aboutServiceId" INTEGER,
|
||||
"aboutServiceSuggestionId" INTEGER,
|
||||
"aboutServiceSuggestionMessageId" INTEGER,
|
||||
"aboutAccountStatusChange" "AccountStatusChange",
|
||||
"aboutCommentStatusChange" "CommentStatusChange",
|
||||
"aboutServiceVerificationStatusChange" "ServiceVerificationStatusChange",
|
||||
"aboutSuggestionStatusChange" "ServiceSuggestionStatusChange",
|
||||
|
||||
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NotificationPreferences" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"enableOnMyCommentStatusChange" BOOLEAN NOT NULL DEFAULT true,
|
||||
"enableAutowatchMyComments" BOOLEAN NOT NULL DEFAULT true,
|
||||
"enableNotifyPendingRepliesOnWatch" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "NotificationPreferences_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NotificationPreferenceOnServiceVerificationChangeFilterFilter" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"verificationStatus" "VerificationStepStatus" NOT NULL,
|
||||
"notificationPreferencesId" INTEGER NOT NULL,
|
||||
"currencies" "Currency"[],
|
||||
"scores" INTEGER[],
|
||||
|
||||
CONSTRAINT "NotificationPreferenceOnServiceVerificationChangeFilterFil_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Event" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"source" TEXT,
|
||||
"type" "EventType" NOT NULL,
|
||||
"visible" BOOLEAN NOT NULL DEFAULT true,
|
||||
"startedAt" TIMESTAMP(3) NOT NULL,
|
||||
"endedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Event_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceSuggestion" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"type" "ServiceSuggestionType" NOT NULL,
|
||||
"status" "ServiceSuggestionStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "ServiceSuggestion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceSuggestionMessage" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"suggestionId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "ServiceSuggestionMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Service" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"kycLevel" INTEGER NOT NULL DEFAULT 4,
|
||||
"overallScore" INTEGER NOT NULL DEFAULT 0,
|
||||
"privacyScore" INTEGER NOT NULL DEFAULT 0,
|
||||
"trustScore" INTEGER NOT NULL DEFAULT 0,
|
||||
"isRecentlyListed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"averageUserRating" DOUBLE PRECISION,
|
||||
"serviceVisibility" "ServiceVisibility" NOT NULL DEFAULT 'PUBLIC',
|
||||
"serviceInfoBanner" "ServiceInfoBanner" NOT NULL DEFAULT 'NONE',
|
||||
"serviceInfoBannerNotes" TEXT,
|
||||
"verificationStatus" "VerificationStatus" NOT NULL DEFAULT 'COMMUNITY_CONTRIBUTED',
|
||||
"verificationSummary" TEXT,
|
||||
"verificationProofMd" TEXT,
|
||||
"verifiedAt" TIMESTAMP(3),
|
||||
"userSentiment" JSONB,
|
||||
"userSentimentAt" TIMESTAMP(3),
|
||||
"referral" TEXT,
|
||||
"acceptedCurrencies" "Currency"[] DEFAULT ARRAY[]::"Currency"[],
|
||||
"serviceUrls" TEXT[],
|
||||
"tosUrls" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"onionUrls" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"i2pUrls" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"imageUrl" TEXT,
|
||||
"tosReview" JSONB,
|
||||
"tosReviewAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"listedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Service_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceContactMethod" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"iconId" TEXT NOT NULL,
|
||||
"info" TEXT NOT NULL,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "ServiceContactMethod_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Attribute" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"privacyPoints" INTEGER NOT NULL DEFAULT 0,
|
||||
"trustPoints" INTEGER NOT NULL DEFAULT 0,
|
||||
"category" "AttributeCategory" NOT NULL,
|
||||
"type" "AttributeType" NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Attribute_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InternalUserNote" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"addedByUserId" INTEGER,
|
||||
|
||||
CONSTRAINT "InternalUserNote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"displayName" TEXT,
|
||||
"link" TEXT,
|
||||
"picture" TEXT,
|
||||
"spammer" BOOLEAN NOT NULL DEFAULT false,
|
||||
"verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"admin" BOOLEAN NOT NULL DEFAULT false,
|
||||
"verifier" BOOLEAN NOT NULL DEFAULT false,
|
||||
"verifiedLink" TEXT,
|
||||
"secretTokenHash" TEXT NOT NULL,
|
||||
"totalKarma" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CommentVote" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"downvote" BOOLEAN NOT NULL DEFAULT false,
|
||||
"commentId" INTEGER NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "CommentVote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceAttribute" (
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
"attributeId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ServiceAttribute_pkey" PRIMARY KEY ("serviceId","attributeId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "KarmaTransaction" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"points" INTEGER NOT NULL DEFAULT 0,
|
||||
"commentId" INTEGER,
|
||||
"suggestionId" INTEGER,
|
||||
"description" TEXT NOT NULL,
|
||||
"processed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "KarmaTransaction_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VerificationStep" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"status" "VerificationStepStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"evidenceMd" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "VerificationStep_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Category" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"icon" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceVerificationRequest" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ServiceVerificationRequest_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceScoreRecalculationJob" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"processedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "ServiceScoreRecalculationJob_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServiceUser" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"serviceId" INTEGER NOT NULL,
|
||||
"role" "ServiceUserRole" NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ServiceUser_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_watchedComments" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "_watchedComments_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_onEventCreatedForServices" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "_onEventCreatedForServices_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_onRootCommentCreatedForServices" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "_onRootCommentCreatedForServices_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_onVerificationChangeForServices" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "_onVerificationChangeForServices_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "_AttributeToNotificationPreferenceOnServiceVerification_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ServiceToCategory" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "_ServiceToCategory_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "_CategoryToNotificationPreferenceOnServiceVerificationC_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Comment_status_idx" ON "Comment"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Comment_createdAt_idx" ON "Comment"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Comment_serviceId_idx" ON "Comment"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Comment_authorId_idx" ON "Comment"("authorId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Comment_upvotes_idx" ON "Comment"("upvotes");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Comment_rating_idx" ON "Comment"("rating");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Comment_ratingActive_idx" ON "Comment"("ratingActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Comment_serviceId_orderId_key" ON "Comment"("serviceId", "orderId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_userId_idx" ON "Notification"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_read_idx" ON "Notification"("read");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_createdAt_idx" ON "Notification"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_userId_read_createdAt_idx" ON "Notification"("userId", "read", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_userId_type_aboutCommentId_idx" ON "Notification"("userId", "type", "aboutCommentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "idx_notification_suggestion_message" ON "Notification"("userId", "type", "aboutServiceSuggestionMessageId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "idx_notification_suggestion_status" ON "Notification"("userId", "type", "aboutServiceSuggestionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "idx_notification_account_status" ON "Notification"("userId", "type", "aboutAccountStatusChange");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NotificationPreferences_userId_key" ON "NotificationPreferences"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NotificationPreferenceOnServiceVerificationChangeFilterFilt_key" ON "NotificationPreferenceOnServiceVerificationChangeFilterFilter"("verificationStatus", "notificationPreferencesId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Event_visible_idx" ON "Event"("visible");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Event_startedAt_idx" ON "Event"("startedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Event_createdAt_idx" ON "Event"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Event_endedAt_idx" ON "Event"("endedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Event_type_idx" ON "Event"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Event_serviceId_idx" ON "Event"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceSuggestion_userId_idx" ON "ServiceSuggestion"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceSuggestion_serviceId_idx" ON "ServiceSuggestion"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceSuggestionMessage_userId_idx" ON "ServiceSuggestionMessage"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceSuggestionMessage_suggestionId_idx" ON "ServiceSuggestionMessage"("suggestionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceSuggestionMessage_createdAt_idx" ON "ServiceSuggestionMessage"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Service_slug_key" ON "Service"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_listedAt_idx" ON "Service"("listedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_overallScore_idx" ON "Service"("overallScore");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_privacyScore_idx" ON "Service"("privacyScore");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_trustScore_idx" ON "Service"("trustScore");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_averageUserRating_idx" ON "Service"("averageUserRating");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_name_idx" ON "Service"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_verificationStatus_idx" ON "Service"("verificationStatus");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_kycLevel_idx" ON "Service"("kycLevel");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_createdAt_idx" ON "Service"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_updatedAt_idx" ON "Service"("updatedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_slug_idx" ON "Service"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Attribute_slug_key" ON "Attribute"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InternalUserNote_userId_idx" ON "InternalUserNote"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InternalUserNote_addedByUserId_idx" ON "InternalUserNote"("addedByUserId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "InternalUserNote_createdAt_idx" ON "InternalUserNote"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_name_key" ON "User"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_secretTokenHash_key" ON "User"("secretTokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_createdAt_idx" ON "User"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_totalKarma_idx" ON "User"("totalKarma");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CommentVote_commentId_idx" ON "CommentVote"("commentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CommentVote_userId_idx" ON "CommentVote"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CommentVote_commentId_userId_key" ON "CommentVote"("commentId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "KarmaTransaction_createdAt_idx" ON "KarmaTransaction"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "KarmaTransaction_userId_idx" ON "KarmaTransaction"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "KarmaTransaction_processed_idx" ON "KarmaTransaction"("processed");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "KarmaTransaction_suggestionId_idx" ON "KarmaTransaction"("suggestionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "KarmaTransaction_commentId_idx" ON "KarmaTransaction"("commentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VerificationStep_serviceId_idx" ON "VerificationStep"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VerificationStep_status_idx" ON "VerificationStep"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VerificationStep_createdAt_idx" ON "VerificationStep"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Category_slug_key" ON "Category"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Category_name_idx" ON "Category"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Category_slug_idx" ON "Category"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceVerificationRequest_serviceId_idx" ON "ServiceVerificationRequest"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceVerificationRequest_userId_idx" ON "ServiceVerificationRequest"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceVerificationRequest_createdAt_idx" ON "ServiceVerificationRequest"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ServiceVerificationRequest_serviceId_userId_key" ON "ServiceVerificationRequest"("serviceId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ServiceScoreRecalculationJob_serviceId_key" ON "ServiceScoreRecalculationJob"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceScoreRecalculationJob_processedAt_idx" ON "ServiceScoreRecalculationJob"("processedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceScoreRecalculationJob_createdAt_idx" ON "ServiceScoreRecalculationJob"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceUser_userId_idx" ON "ServiceUser"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceUser_serviceId_idx" ON "ServiceUser"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ServiceUser_role_idx" ON "ServiceUser"("role");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ServiceUser_userId_serviceId_key" ON "ServiceUser"("userId", "serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_watchedComments_B_index" ON "_watchedComments"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_onEventCreatedForServices_B_index" ON "_onEventCreatedForServices"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_onRootCommentCreatedForServices_B_index" ON "_onRootCommentCreatedForServices"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_onVerificationChangeForServices_B_index" ON "_onVerificationChangeForServices"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_AttributeToNotificationPreferenceOnServiceVerification_B_index" ON "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ServiceToCategory_B_index" ON "_ServiceToCategory"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_CategoryToNotificationPreferenceOnServiceVerificationC_B_index" ON "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutCommentId_fkey" FOREIGN KEY ("aboutCommentId") REFERENCES "Comment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutEventId_fkey" FOREIGN KEY ("aboutEventId") REFERENCES "Event"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutServiceId_fkey" FOREIGN KEY ("aboutServiceId") REFERENCES "Service"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutServiceSuggestionId_fkey" FOREIGN KEY ("aboutServiceSuggestionId") REFERENCES "ServiceSuggestion"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_aboutServiceSuggestionMessageId_fkey" FOREIGN KEY ("aboutServiceSuggestionMessageId") REFERENCES "ServiceSuggestionMessage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NotificationPreferences" ADD CONSTRAINT "NotificationPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NotificationPreferenceOnServiceVerificationChangeFilterFilter" ADD CONSTRAINT "NotificationPreferenceOnServiceVerificationChangeFilterFil_fkey" FOREIGN KEY ("notificationPreferencesId") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Event" ADD CONSTRAINT "Event_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceSuggestion" ADD CONSTRAINT "ServiceSuggestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceSuggestion" ADD CONSTRAINT "ServiceSuggestion_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceSuggestionMessage" ADD CONSTRAINT "ServiceSuggestionMessage_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceSuggestionMessage" ADD CONSTRAINT "ServiceSuggestionMessage_suggestionId_fkey" FOREIGN KEY ("suggestionId") REFERENCES "ServiceSuggestion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceContactMethod" ADD CONSTRAINT "ServiceContactMethod_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InternalUserNote" ADD CONSTRAINT "InternalUserNote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InternalUserNote" ADD CONSTRAINT "InternalUserNote_addedByUserId_fkey" FOREIGN KEY ("addedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CommentVote" ADD CONSTRAINT "CommentVote_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CommentVote" ADD CONSTRAINT "CommentVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceAttribute" ADD CONSTRAINT "ServiceAttribute_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceAttribute" ADD CONSTRAINT "ServiceAttribute_attributeId_fkey" FOREIGN KEY ("attributeId") REFERENCES "Attribute"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "KarmaTransaction" ADD CONSTRAINT "KarmaTransaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "KarmaTransaction" ADD CONSTRAINT "KarmaTransaction_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "KarmaTransaction" ADD CONSTRAINT "KarmaTransaction_suggestionId_fkey" FOREIGN KEY ("suggestionId") REFERENCES "ServiceSuggestion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VerificationStep" ADD CONSTRAINT "VerificationStep_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceVerificationRequest" ADD CONSTRAINT "ServiceVerificationRequest_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceVerificationRequest" ADD CONSTRAINT "ServiceVerificationRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceUser" ADD CONSTRAINT "ServiceUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ServiceUser" ADD CONSTRAINT "ServiceUser_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_watchedComments" ADD CONSTRAINT "_watchedComments_A_fkey" FOREIGN KEY ("A") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_watchedComments" ADD CONSTRAINT "_watchedComments_B_fkey" FOREIGN KEY ("B") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_onEventCreatedForServices" ADD CONSTRAINT "_onEventCreatedForServices_A_fkey" FOREIGN KEY ("A") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_onEventCreatedForServices" ADD CONSTRAINT "_onEventCreatedForServices_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_onRootCommentCreatedForServices" ADD CONSTRAINT "_onRootCommentCreatedForServices_A_fkey" FOREIGN KEY ("A") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_onRootCommentCreatedForServices" ADD CONSTRAINT "_onRootCommentCreatedForServices_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_onVerificationChangeForServices" ADD CONSTRAINT "_onVerificationChangeForServices_A_fkey" FOREIGN KEY ("A") REFERENCES "NotificationPreferences"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_onVerificationChangeForServices" ADD CONSTRAINT "_onVerificationChangeForServices_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi" ADD CONSTRAINT "_AttributeToNotificationPreferenceOnServiceVerificationC_A_fkey" FOREIGN KEY ("A") REFERENCES "Attribute"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_AttributeToNotificationPreferenceOnServiceVerificationChangeFi" ADD CONSTRAINT "_AttributeToNotificationPreferenceOnServiceVerificationC_B_fkey" FOREIGN KEY ("B") REFERENCES "NotificationPreferenceOnServiceVerificationChangeFilterFilter"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ServiceToCategory" ADD CONSTRAINT "_ServiceToCategory_A_fkey" FOREIGN KEY ("A") REFERENCES "Category"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ServiceToCategory" ADD CONSTRAINT "_ServiceToCategory_B_fkey" FOREIGN KEY ("B") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil" ADD CONSTRAINT "_CategoryToNotificationPreferenceOnServiceVerificationCh_A_fkey" FOREIGN KEY ("A") REFERENCES "Category"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_CategoryToNotificationPreferenceOnServiceVerificationChangeFil" ADD CONSTRAINT "_CategoryToNotificationPreferenceOnServiceVerificationCh_B_fkey" FOREIGN KEY ("B") REFERENCES "NotificationPreferenceOnServiceVerificationChangeFilterFilter"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "lastLoginAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
3
web/prisma/migrations/migration_lock.toml
Normal file
3
web/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
590
web/prisma/schema.prisma
Normal file
590
web/prisma/schema.prisma
Normal file
@@ -0,0 +1,590 @@
|
||||
// This is your Prisma schema file
|
||||
|
||||
datasource db {
|
||||
provider = "postgres"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
generator json {
|
||||
provider = "prisma-json-types-generator"
|
||||
}
|
||||
|
||||
enum CommentStatus {
|
||||
PENDING
|
||||
HUMAN_PENDING
|
||||
APPROVED
|
||||
VERIFIED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
enum OrderIdStatus {
|
||||
PENDING
|
||||
APPROVED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id Int @id @default(autoincrement())
|
||||
/// Computed via trigger. Do not update through prisma.
|
||||
upvotes Int @default(0)
|
||||
status CommentStatus @default(PENDING)
|
||||
suspicious Boolean @default(false)
|
||||
requiresAdminReview Boolean @default(false)
|
||||
communityNote String?
|
||||
verificationNote String?
|
||||
internalNote String?
|
||||
privateContext String?
|
||||
orderId String? @db.VarChar(100)
|
||||
orderIdStatus OrderIdStatus? @default(PENDING)
|
||||
kycRequested Boolean @default(false)
|
||||
fundsBlocked Boolean @default(false)
|
||||
content String
|
||||
rating Int? @db.SmallInt
|
||||
ratingActive Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||
authorId Int
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
serviceId Int
|
||||
parentId Int?
|
||||
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade)
|
||||
replies Comment[] @relation("CommentReplies")
|
||||
karmaTransactions KarmaTransaction[]
|
||||
votes CommentVote[]
|
||||
|
||||
notificationPreferenceswatchedComments NotificationPreferences[] @relation("watchedComments")
|
||||
Notification Notification[]
|
||||
|
||||
@@unique([serviceId, orderId], name: "unique_orderId_per_service")
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
@@index([serviceId])
|
||||
@@index([authorId])
|
||||
@@index([upvotes])
|
||||
@@index([rating])
|
||||
@@index([ratingActive])
|
||||
}
|
||||
|
||||
enum VerificationStatus {
|
||||
COMMUNITY_CONTRIBUTED
|
||||
// COMMUNITY_VERIFIED
|
||||
APPROVED
|
||||
VERIFICATION_SUCCESS
|
||||
VERIFICATION_FAILED
|
||||
}
|
||||
|
||||
enum ServiceInfoBanner {
|
||||
NONE
|
||||
NO_LONGER_OPERATIONAL
|
||||
}
|
||||
|
||||
enum ServiceVisibility {
|
||||
PUBLIC
|
||||
UNLISTED
|
||||
HIDDEN
|
||||
}
|
||||
|
||||
enum Currency {
|
||||
MONERO
|
||||
BITCOIN
|
||||
LIGHTNING
|
||||
FIAT
|
||||
CASH
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
WARNING
|
||||
WARNING_SOLVED
|
||||
ALERT
|
||||
ALERT_SOLVED
|
||||
INFO
|
||||
NORMAL
|
||||
UPDATE
|
||||
}
|
||||
|
||||
enum ServiceUserRole {
|
||||
OWNER
|
||||
ADMIN
|
||||
MODERATOR
|
||||
SUPPORT
|
||||
TEAM_MEMBER
|
||||
}
|
||||
|
||||
enum AccountStatusChange {
|
||||
ADMIN_TRUE
|
||||
ADMIN_FALSE
|
||||
VERIFIED_TRUE
|
||||
VERIFIED_FALSE
|
||||
VERIFIER_TRUE
|
||||
VERIFIER_FALSE
|
||||
SPAMMER_TRUE
|
||||
SPAMMER_FALSE
|
||||
}
|
||||
|
||||
enum NotificationType {
|
||||
COMMENT_STATUS_CHANGE
|
||||
REPLY_COMMENT_CREATED
|
||||
COMMUNITY_NOTE_ADDED
|
||||
/// Comment that is not a reply. May include a rating.
|
||||
ROOT_COMMENT_CREATED
|
||||
SUGGESTION_MESSAGE
|
||||
SUGGESTION_STATUS_CHANGE
|
||||
// KARMA_UNLOCK // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code.
|
||||
/// Marked as spammer, promoted to admin, etc.
|
||||
ACCOUNT_STATUS_CHANGE
|
||||
EVENT_CREATED
|
||||
SERVICE_VERIFICATION_STATUS_CHANGE
|
||||
}
|
||||
|
||||
enum CommentStatusChange {
|
||||
MARKED_AS_SPAM
|
||||
UNMARKED_AS_SPAM
|
||||
MARKED_FOR_ADMIN_REVIEW
|
||||
UNMARKED_FOR_ADMIN_REVIEW
|
||||
STATUS_CHANGED_TO_APPROVED
|
||||
STATUS_CHANGED_TO_VERIFIED
|
||||
STATUS_CHANGED_TO_REJECTED
|
||||
STATUS_CHANGED_TO_PENDING
|
||||
}
|
||||
|
||||
enum ServiceVerificationStatusChange {
|
||||
STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED
|
||||
STATUS_CHANGED_TO_APPROVED
|
||||
STATUS_CHANGED_TO_VERIFICATION_SUCCESS
|
||||
STATUS_CHANGED_TO_VERIFICATION_FAILED
|
||||
}
|
||||
|
||||
enum ServiceSuggestionStatusChange {
|
||||
STATUS_CHANGED_TO_PENDING
|
||||
STATUS_CHANGED_TO_APPROVED
|
||||
STATUS_CHANGED_TO_REJECTED
|
||||
STATUS_CHANGED_TO_WITHDRAWN
|
||||
}
|
||||
|
||||
model Notification {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
user User @relation("NotificationOwner", fields: [userId], references: [id], onDelete: Cascade)
|
||||
type NotificationType
|
||||
read Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
aboutComment Comment? @relation(fields: [aboutCommentId], references: [id])
|
||||
aboutCommentId Int?
|
||||
aboutEvent Event? @relation(fields: [aboutEventId], references: [id])
|
||||
aboutEventId Int?
|
||||
aboutService Service? @relation(fields: [aboutServiceId], references: [id])
|
||||
aboutServiceId Int?
|
||||
aboutServiceSuggestion ServiceSuggestion? @relation(fields: [aboutServiceSuggestionId], references: [id])
|
||||
aboutServiceSuggestionId Int?
|
||||
aboutServiceSuggestionMessage ServiceSuggestionMessage? @relation(fields: [aboutServiceSuggestionMessageId], references: [id])
|
||||
aboutServiceSuggestionMessageId Int?
|
||||
aboutAccountStatusChange AccountStatusChange?
|
||||
aboutCommentStatusChange CommentStatusChange?
|
||||
aboutServiceVerificationStatusChange ServiceVerificationStatusChange?
|
||||
aboutSuggestionStatusChange ServiceSuggestionStatusChange?
|
||||
|
||||
@@index([userId])
|
||||
@@index([read])
|
||||
@@index([createdAt])
|
||||
@@index([userId, read, createdAt])
|
||||
@@index([userId, type, aboutCommentId])
|
||||
@@index([userId, type, aboutServiceSuggestionMessageId], map: "idx_notification_suggestion_message")
|
||||
@@index([userId, type, aboutServiceSuggestionId], map: "idx_notification_suggestion_status")
|
||||
@@index([userId, type, aboutAccountStatusChange], map: "idx_notification_account_status")
|
||||
}
|
||||
|
||||
model NotificationPreferences {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
userId Int @unique
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
enableOnMyCommentStatusChange Boolean @default(true)
|
||||
enableAutowatchMyComments Boolean @default(true)
|
||||
enableNotifyPendingRepliesOnWatch Boolean @default(false)
|
||||
|
||||
onEventCreatedForServices Service[] @relation("onEventCreatedForServices")
|
||||
onRootCommentCreatedForServices Service[] @relation("onRootCommentCreatedForServices")
|
||||
onVerificationChangeForServices Service[] @relation("onVerificationChangeForServices")
|
||||
watchedComments Comment[] @relation("watchedComments")
|
||||
|
||||
onServiceVerificationChangeFilter NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
|
||||
}
|
||||
|
||||
model NotificationPreferenceOnServiceVerificationChangeFilterFilter {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
verificationStatus VerificationStepStatus
|
||||
notificationPreferences NotificationPreferences @relation(fields: [notificationPreferencesId], references: [id], onDelete: Cascade)
|
||||
notificationPreferencesId Int
|
||||
|
||||
categories Category[]
|
||||
attributes Attribute[]
|
||||
currencies Currency[]
|
||||
/// 0-10
|
||||
scores Int[]
|
||||
|
||||
@@unique([verificationStatus, notificationPreferencesId])
|
||||
}
|
||||
|
||||
model Event {
|
||||
id Int @id @default(autoincrement())
|
||||
title String
|
||||
content String
|
||||
source String?
|
||||
type EventType
|
||||
visible Boolean @default(true)
|
||||
startedAt DateTime
|
||||
/// If null, the event is ongoing. If same as startedAt, the event is a one-time event. If startedAt is in the future, the event is upcoming.
|
||||
endedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
serviceId Int
|
||||
Notification Notification[]
|
||||
|
||||
@@index([visible])
|
||||
@@index([startedAt])
|
||||
@@index([createdAt])
|
||||
@@index([endedAt])
|
||||
@@index([type])
|
||||
@@index([serviceId])
|
||||
}
|
||||
|
||||
enum ServiceSuggestionStatus {
|
||||
PENDING
|
||||
APPROVED
|
||||
REJECTED
|
||||
WITHDRAWN
|
||||
}
|
||||
|
||||
enum ServiceSuggestionType {
|
||||
CREATE_SERVICE
|
||||
EDIT_SERVICE
|
||||
}
|
||||
|
||||
model ServiceSuggestion {
|
||||
id Int @id @default(autoincrement())
|
||||
type ServiceSuggestionType
|
||||
status ServiceSuggestionStatus @default(PENDING)
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
userId Int
|
||||
serviceId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
messages ServiceSuggestionMessage[]
|
||||
Notification Notification[]
|
||||
KarmaTransaction KarmaTransaction[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([serviceId])
|
||||
}
|
||||
|
||||
model ServiceSuggestionMessage {
|
||||
id Int @id @default(autoincrement())
|
||||
content String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
userId Int
|
||||
suggestionId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
suggestion ServiceSuggestion @relation(fields: [suggestionId], references: [id], onDelete: Cascade)
|
||||
notifications Notification[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([suggestionId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model Service {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
slug String @unique
|
||||
description String
|
||||
categories Category[] @relation("ServiceToCategory")
|
||||
kycLevel Int @default(4)
|
||||
overallScore Int @default(0)
|
||||
privacyScore Int @default(0)
|
||||
trustScore Int @default(0)
|
||||
/// Computed via trigger. Do not update through prisma.
|
||||
isRecentlyListed Boolean @default(false)
|
||||
/// Computed via trigger. Do not update through prisma.
|
||||
averageUserRating Float?
|
||||
serviceVisibility ServiceVisibility @default(PUBLIC)
|
||||
serviceInfoBanner ServiceInfoBanner @default(NONE)
|
||||
serviceInfoBannerNotes String?
|
||||
verificationStatus VerificationStatus @default(COMMUNITY_CONTRIBUTED)
|
||||
verificationSummary String?
|
||||
verificationRequests ServiceVerificationRequest[]
|
||||
verificationProofMd String?
|
||||
/// Computed via trigger when the service status is VERIFICATION_SUCCESS. Do not update through prisma.
|
||||
verifiedAt DateTime?
|
||||
/// [UserSentiment]
|
||||
userSentiment Json?
|
||||
userSentimentAt DateTime?
|
||||
referral String?
|
||||
acceptedCurrencies Currency[] @default([])
|
||||
serviceUrls String[]
|
||||
tosUrls String[] @default([])
|
||||
onionUrls String[] @default([])
|
||||
i2pUrls String[] @default([])
|
||||
imageUrl String?
|
||||
/// [TosReview]
|
||||
tosReview Json?
|
||||
tosReviewAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
listedAt DateTime?
|
||||
comments Comment[]
|
||||
events Event[]
|
||||
contactMethods ServiceContactMethod[] @relation("ServiceToContactMethod")
|
||||
attributes ServiceAttribute[]
|
||||
verificationSteps VerificationStep[]
|
||||
suggestions ServiceSuggestion[]
|
||||
|
||||
onEventCreatedForServices NotificationPreferences[] @relation("onEventCreatedForServices")
|
||||
onRootCommentCreatedForServices NotificationPreferences[] @relation("onRootCommentCreatedForServices")
|
||||
onVerificationChangeForServices NotificationPreferences[] @relation("onVerificationChangeForServices")
|
||||
Notification Notification[]
|
||||
affiliatedUsers ServiceUser[] @relation("ServiceUsers")
|
||||
|
||||
@@index([listedAt])
|
||||
@@index([overallScore])
|
||||
@@index([privacyScore])
|
||||
@@index([trustScore])
|
||||
@@index([averageUserRating])
|
||||
@@index([name])
|
||||
@@index([verificationStatus])
|
||||
@@index([kycLevel])
|
||||
@@index([createdAt])
|
||||
@@index([updatedAt])
|
||||
@@index([slug])
|
||||
}
|
||||
|
||||
model ServiceContactMethod {
|
||||
id Int @id @default(autoincrement())
|
||||
label String
|
||||
/// Including the protocol (e.g. "mailto:", "tel:", "https://")
|
||||
value String
|
||||
iconId String
|
||||
info String
|
||||
services Service @relation("ServiceToContactMethod", fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
serviceId Int
|
||||
}
|
||||
|
||||
enum AttributeCategory {
|
||||
PRIVACY
|
||||
TRUST
|
||||
}
|
||||
|
||||
enum AttributeType {
|
||||
GOOD
|
||||
BAD
|
||||
WARNING
|
||||
INFO
|
||||
}
|
||||
|
||||
model Attribute {
|
||||
id Int @id @default(autoincrement())
|
||||
slug String @unique
|
||||
title String
|
||||
/// Markdown
|
||||
description String
|
||||
privacyPoints Int @default(0)
|
||||
trustPoints Int @default(0)
|
||||
category AttributeCategory
|
||||
type AttributeType
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
services ServiceAttribute[]
|
||||
|
||||
notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
|
||||
}
|
||||
|
||||
model InternalUserNote {
|
||||
id Int @id @default(autoincrement())
|
||||
content String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
user User @relation("UserRecievedNotes", fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
addedByUser User? @relation("UserAddedNotes", fields: [addedByUserId], references: [id], onDelete: SetNull)
|
||||
addedByUserId Int?
|
||||
|
||||
@@index([userId])
|
||||
@@index([addedByUserId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
displayName String?
|
||||
link String?
|
||||
picture String?
|
||||
spammer Boolean @default(false)
|
||||
verified Boolean @default(false)
|
||||
admin Boolean @default(false)
|
||||
verifier Boolean @default(false)
|
||||
verifiedLink String?
|
||||
secretTokenHash String @unique
|
||||
/// Computed via trigger. Do not update through prisma.
|
||||
totalKarma Int @default(0)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
lastLoginAt DateTime @default(now())
|
||||
comments Comment[]
|
||||
karmaTransactions KarmaTransaction[]
|
||||
commentVotes CommentVote[]
|
||||
suggestions ServiceSuggestion[]
|
||||
suggestionMessages ServiceSuggestionMessage[]
|
||||
internalNotes InternalUserNote[] @relation("UserRecievedNotes")
|
||||
addedInternalNotes InternalUserNote[] @relation("UserAddedNotes")
|
||||
verificationRequests ServiceVerificationRequest[]
|
||||
notifications Notification[] @relation("NotificationOwner")
|
||||
notificationPreferences NotificationPreferences?
|
||||
serviceAffiliations ServiceUser[] @relation("UserServices")
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([totalKarma])
|
||||
}
|
||||
|
||||
model CommentVote {
|
||||
id Int @id @default(autoincrement())
|
||||
downvote Boolean @default(false) // false = upvote, true = downvote
|
||||
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
||||
commentId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([commentId, userId]) // Ensure one vote per user per comment
|
||||
@@index([commentId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model ServiceAttribute {
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
serviceId Int
|
||||
attribute Attribute @relation(fields: [attributeId], references: [id], onDelete: Cascade)
|
||||
attributeId Int
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@id([serviceId, attributeId])
|
||||
}
|
||||
|
||||
model KarmaTransaction {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
action String
|
||||
points Int @default(0)
|
||||
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
||||
commentId Int?
|
||||
suggestion ServiceSuggestion? @relation(fields: [suggestionId], references: [id], onDelete: Cascade)
|
||||
suggestionId Int?
|
||||
description String
|
||||
processed Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([userId])
|
||||
@@index([processed])
|
||||
@@index([suggestionId])
|
||||
@@index([commentId])
|
||||
}
|
||||
|
||||
enum VerificationStepStatus {
|
||||
PENDING
|
||||
IN_PROGRESS
|
||||
PASSED
|
||||
FAILED
|
||||
}
|
||||
|
||||
model VerificationStep {
|
||||
id Int @id @default(autoincrement())
|
||||
title String
|
||||
description String
|
||||
status VerificationStepStatus @default(PENDING)
|
||||
evidenceMd String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
serviceId Int
|
||||
|
||||
@@index([serviceId])
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model Category {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
icon String
|
||||
slug String @unique
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
services Service[] @relation("ServiceToCategory")
|
||||
|
||||
notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
|
||||
|
||||
@@index([name])
|
||||
@@index([slug])
|
||||
}
|
||||
|
||||
model ServiceVerificationRequest {
|
||||
id Int @id @default(autoincrement())
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
serviceId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
@@unique([serviceId, userId])
|
||||
@@index([serviceId])
|
||||
@@index([userId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model ServiceScoreRecalculationJob {
|
||||
id Int @id @default(autoincrement())
|
||||
serviceId Int @unique
|
||||
createdAt DateTime @default(now())
|
||||
processedAt DateTime? @updatedAt
|
||||
|
||||
@@index([processedAt])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model ServiceUser {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
user User @relation("UserServices", fields: [userId], references: [id], onDelete: Cascade)
|
||||
serviceId Int
|
||||
service Service @relation("ServiceUsers", fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
role ServiceUserRole
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([userId, serviceId])
|
||||
@@index([userId])
|
||||
@@index([serviceId])
|
||||
@@index([role])
|
||||
}
|
||||
265
web/prisma/triggers/01_karma_tx.sql
Normal file
265
web/prisma/triggers/01_karma_tx.sql
Normal file
@@ -0,0 +1,265 @@
|
||||
-- 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();
|
||||
264
web/prisma/triggers/02_service_score.sql
Normal file
264
web/prisma/triggers/02_service_score.sql
Normal file
@@ -0,0 +1,264 @@
|
||||
-- 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();
|
||||
57
web/prisma/triggers/03_service_user_rating.sql
Normal file
57
web/prisma/triggers/03_service_user_rating.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
-- 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();
|
||||
48
web/prisma/triggers/04_service_verification_status.sql
Normal file
48
web/prisma/triggers/04_service_verification_status.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- 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();
|
||||
399
web/prisma/triggers/05_service_events.sql
Normal file
399
web/prisma/triggers/05_service_events.sql
Normal file
@@ -0,0 +1,399 @@
|
||||
-- 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();
|
||||
227
web/prisma/triggers/06_notifications_comments.sql
Normal file
227
web/prisma/triggers/06_notifications_comments.sql
Normal file
@@ -0,0 +1,227 @@
|
||||
-- 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();
|
||||
72
web/prisma/triggers/07_notifications_service_suggestion.sql
Normal file
72
web/prisma/triggers/07_notifications_service_suggestion.sql
Normal file
@@ -0,0 +1,72 @@
|
||||
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();
|
||||
28
web/prisma/triggers/08_notifications_service_events.sql
Normal file
28
web/prisma/triggers/08_notifications_service_events.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
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();
|
||||
@@ -0,0 +1,37 @@
|
||||
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();
|
||||
62
web/prisma/triggers/10_notifications_user_status_change.sql
Normal file
62
web/prisma/triggers/10_notifications_user_status_change.sql
Normal file
@@ -0,0 +1,62 @@
|
||||
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