diff --git a/apps/web/prisma/migrations/20250909092755_workspace_discussions/migration.sql b/apps/web/prisma/migrations/20250909092755_workspace_discussions/migration.sql new file mode 100644 index 000000000..a931da569 --- /dev/null +++ b/apps/web/prisma/migrations/20250909092755_workspace_discussions/migration.sql @@ -0,0 +1,95 @@ +-- CreateEnum +CREATE TYPE "InterviewsDiscussionCommentDomain" AS ENUM ('QUESTION', 'OFFICIAL_SOLUTION'); + +-- CreateEnum +CREATE TYPE "InterviewsActivityCategory" AS ENUM ('DISCUSSION', 'DISCUSSION_UPVOTE'); + +-- CreateTable +CREATE TABLE "InterviewsDiscussionComment" ( + "id" UUID NOT NULL DEFAULT uuid_generate_v4(), + "entityId" TEXT NOT NULL, + "domain" "InterviewsDiscussionCommentDomain" NOT NULL, + "body" TEXT NOT NULL, + "profileId" UUID NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "parentCommentId" UUID, + "repliedToId" UUID, + + CONSTRAINT "InterviewsDiscussionComment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "InterviewsDiscussionCommentVote" ( + "id" UUID NOT NULL DEFAULT uuid_generate_v4(), + "commentId" UUID NOT NULL, + "profileId" UUID NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "InterviewsDiscussionCommentVote_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "InterviewsActivity" ( + "id" UUID NOT NULL DEFAULT uuid_generate_v4(), + "actorId" UUID NOT NULL, + "recipientId" UUID, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "category" "InterviewsActivityCategory" NOT NULL, + "read" BOOLEAN NOT NULL DEFAULT false, + "commentId" UUID, + "voteId" UUID, + + CONSTRAINT "InterviewsActivity_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "InterviewsDiscussionComment_entityId_idx" ON "InterviewsDiscussionComment"("entityId"); + +-- CreateIndex +CREATE INDEX "InterviewsDiscussionComment_profileId_idx" ON "InterviewsDiscussionComment"("profileId"); + +-- CreateIndex +CREATE INDEX "InterviewsDiscussionCommentVote_commentId_idx" ON "InterviewsDiscussionCommentVote"("commentId"); + +-- CreateIndex +CREATE INDEX "InterviewsDiscussionCommentVote_profileId_idx" ON "InterviewsDiscussionCommentVote"("profileId"); + +-- CreateIndex +CREATE UNIQUE INDEX "InterviewsDiscussionCommentVote_commentId_profileId_key" ON "InterviewsDiscussionCommentVote"("commentId", "profileId"); + +-- CreateIndex +CREATE UNIQUE INDEX "InterviewsActivity_voteId_key" ON "InterviewsActivity"("voteId"); + +-- CreateIndex +CREATE INDEX "InterviewsActivity_actorId_idx" ON "InterviewsActivity"("actorId"); + +-- CreateIndex +CREATE INDEX "InterviewsActivity_recipientId_idx" ON "InterviewsActivity"("recipientId"); + +-- AddForeignKey +ALTER TABLE "InterviewsDiscussionComment" ADD CONSTRAINT "InterviewsDiscussionComment_parentCommentId_fkey" FOREIGN KEY ("parentCommentId") REFERENCES "InterviewsDiscussionComment"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "InterviewsDiscussionComment" ADD CONSTRAINT "InterviewsDiscussionComment_repliedToId_fkey" FOREIGN KEY ("repliedToId") REFERENCES "InterviewsDiscussionComment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InterviewsDiscussionComment" ADD CONSTRAINT "InterviewsDiscussionComment_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "Profile"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "InterviewsDiscussionCommentVote" ADD CONSTRAINT "InterviewsDiscussionCommentVote_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "InterviewsDiscussionComment"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "InterviewsDiscussionCommentVote" ADD CONSTRAINT "InterviewsDiscussionCommentVote_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "Profile"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "InterviewsActivity" ADD CONSTRAINT "InterviewsActivity_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "InterviewsDiscussionComment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InterviewsActivity" ADD CONSTRAINT "InterviewsActivity_voteId_fkey" FOREIGN KEY ("voteId") REFERENCES "InterviewsDiscussionCommentVote"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InterviewsActivity" ADD CONSTRAINT "InterviewsActivity_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "Profile"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InterviewsActivity" ADD CONSTRAINT "InterviewsActivity_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Profile"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/web/prisma/schema.prisma b/apps/web/prisma/schema.prisma index ae03666de..5354e4558 100644 --- a/apps/web/prisma/schema.prisma +++ b/apps/web/prisma/schema.prisma @@ -75,6 +75,10 @@ model Profile { projectsProfile ProjectsProfile? rewardsTaskCompletions RewardsTaskCompletion[] sponsorsAdRequestReview SponsorsAdRequestReview[] + discussionComments InterviewsDiscussionComment[] + discussionCommentVotes InterviewsDiscussionCommentVote[] + activitiesGiven InterviewsActivity[] @relation("ActivityActor") + activitiesReceived InterviewsActivity[] @relation("ActivityRecipient") @@index([username]) @@index([stripeCustomer]) @@ -276,6 +280,74 @@ model LearningSessionProgress { @@index([sessionId]) } +enum InterviewsDiscussionCommentDomain { + QUESTION + OFFICIAL_SOLUTION +} + +model InterviewsDiscussionComment { + id String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid + entityId String + domain InterviewsDiscussionCommentDomain + body String + profileId String @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @default(now()) @updatedAt + + parentCommentId String? @db.Uuid // For replying to a comment + parentComment InterviewsDiscussionComment? @relation("DiscussionThread", fields: [parentCommentId], references: [id], onDelete: Cascade, onUpdate: NoAction) + replies InterviewsDiscussionComment[] @relation("DiscussionThread") + + repliedToId String? @db.Uuid // For replying to a reply + repliedTo InterviewsDiscussionComment? @relation("DirectReply", fields: [repliedToId], references: [id], onDelete: Cascade) + directReplies InterviewsDiscussionComment[] @relation("DirectReply") + + author Profile @relation(fields: [profileId], references: [id], onDelete: Cascade, onUpdate: NoAction) + votes InterviewsDiscussionCommentVote[] + activities InterviewsActivity[] + + @@index([entityId]) + @@index([profileId]) +} + +model InterviewsDiscussionCommentVote { + id String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid + commentId String @db.Uuid + profileId String @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(6) + + comment InterviewsDiscussionComment @relation(fields: [commentId], references: [id], onDelete: Cascade, onUpdate: NoAction) + author Profile @relation(fields: [profileId], references: [id], onDelete: Cascade, onUpdate: NoAction) + activity InterviewsActivity? + + @@unique([commentId, profileId]) + @@index([commentId]) + @@index([profileId]) +} + +enum InterviewsActivityCategory { + DISCUSSION + DISCUSSION_UPVOTE +} +model InterviewsActivity { + id String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid + actorId String @db.Uuid // who performed the action + recipientId String? @db.Uuid // who receives the notification + createdAt DateTime @default(now()) @db.Timestamptz(6) + category InterviewsActivityCategory + read Boolean @default(false) + commentId String? @db.Uuid // For "discussion comment" + voteId String? @unique @db.Uuid // For "discussion comment upvote" + + comment InterviewsDiscussionComment? @relation(fields: [commentId], references: [id], onDelete: Cascade) + vote InterviewsDiscussionCommentVote? @relation(fields: [voteId], references: [id], onDelete: Cascade) + actor Profile @relation("ActivityActor", fields: [actorId], references: [id], onDelete: Cascade) + recipient Profile? @relation("ActivityRecipient", fields: [recipientId], references: [id], onDelete: Cascade) + + @@index([actorId]) + @@index([recipientId]) +} + enum ProjectsSubscriptionPlan { MONTH ANNUAL diff --git a/apps/web/src/components/interviews/marketing/embed/InterviewsMarketingEmbedJavaScriptQuestion.tsx b/apps/web/src/components/interviews/marketing/embed/InterviewsMarketingEmbedJavaScriptQuestion.tsx index c3e2809e8..320f3f1ec 100644 --- a/apps/web/src/components/interviews/marketing/embed/InterviewsMarketingEmbedJavaScriptQuestion.tsx +++ b/apps/web/src/components/interviews/marketing/embed/InterviewsMarketingEmbedJavaScriptQuestion.tsx @@ -55,6 +55,7 @@ export default function InterviewsMarketingEmbedJavaScriptQuestion({