From 01335c4e158f4ba79da0a53b8285e32df5ca35f4 Mon Sep 17 00:00:00 2001
From: "Mike P. Sinn" <m@crowdsourcingcures.org>
Date: Thu, 5 Sep 2024 16:05:48 -0500
Subject: [PATCH] Add comprehensive article management schema

This migration introduces a new schema for managing articles, categories, tags, sources, search results, generation options, and comments. It includes enums for status, visibility, and comment status, along with necessary foreign key constraints and indexes for efficient querying.
---
 .env.example                                  |   4 +-
 .../20240905205856_articles/migration.sql     | 176 ++++++++++++++++++
 prisma/schema.prisma                          | 127 +++++++++++++
 3 files changed, 306 insertions(+), 1 deletion(-)
 create mode 100644 prisma/migrations/20240905205856_articles/migration.sql

diff --git a/.env.example b/.env.example
index 1cd945fa..db66cb92 100644
--- a/.env.example
+++ b/.env.example
@@ -44,4 +44,6 @@ LANGCHAIN_API_KEY=<your-api-key>
 LANGCHAIN_CALLBACKS_BACKGROUND=true
 
 # Get key from https://www.perplexity.ai/settings/api
-PERPLEXITY_API_KEY="KEY_HERE"
\ No newline at end of file
+PERPLEXITY_API_KEY="KEY_HERE"
+# Get key from https://dashboard.exa.ai/api-keys
+EXASEARCH_API_KEY="KEY_HERE"
\ No newline at end of file
diff --git a/prisma/migrations/20240905205856_articles/migration.sql b/prisma/migrations/20240905205856_articles/migration.sql
new file mode 100644
index 00000000..2b71f5ce
--- /dev/null
+++ b/prisma/migrations/20240905205856_articles/migration.sql
@@ -0,0 +1,176 @@
+-- CreateEnum
+CREATE TYPE "ArticleStatus" AS ENUM ('DRAFT', 'PENDING', 'PRIVATE', 'PUBLISH');
+
+-- CreateEnum
+CREATE TYPE "ArticleVisibility" AS ENUM ('PUBLIC', 'PASSWORD_PROTECTED', 'PRIVATE');
+
+-- CreateEnum
+CREATE TYPE "ArticleCommentStatus" AS ENUM ('OPEN', 'CLOSED');
+
+-- CreateTable
+CREATE TABLE "Article" (
+    "id" SERIAL NOT NULL,
+    "title" TEXT NOT NULL,
+    "slug" TEXT NOT NULL,
+    "description" TEXT NOT NULL,
+    "content" TEXT NOT NULL,
+    "status" "ArticleStatus" NOT NULL DEFAULT 'DRAFT',
+    "visibility" "ArticleVisibility" NOT NULL DEFAULT 'PUBLIC',
+    "password" TEXT,
+    "publishedAt" TIMESTAMP(3),
+    "updatedAt" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deletedAt" TIMESTAMP(3),
+    "commentStatus" "ArticleCommentStatus" NOT NULL DEFAULT 'OPEN',
+    "sticky" BOOLEAN NOT NULL DEFAULT false,
+    "featuredImage" TEXT,
+    "promptedTopic" TEXT,
+    "userId" TEXT NOT NULL,
+    "categoryId" TEXT NOT NULL,
+
+    CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ArticleCategory" (
+    "id" TEXT NOT NULL,
+    "name" TEXT NOT NULL,
+    "slug" TEXT NOT NULL,
+    "description" TEXT,
+    "updatedAt" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deletedAt" TIMESTAMP(3),
+
+    CONSTRAINT "ArticleCategory_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ArticleTag" (
+    "id" TEXT NOT NULL,
+    "name" TEXT NOT NULL,
+    "slug" TEXT NOT NULL,
+    "updatedAt" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deletedAt" TIMESTAMP(3),
+
+    CONSTRAINT "ArticleTag_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ArticleSource" (
+    "id" TEXT NOT NULL,
+    "url" TEXT NOT NULL,
+    "title" TEXT NOT NULL,
+    "description" TEXT,
+    "articleId" INTEGER NOT NULL,
+    "updatedAt" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deletedAt" TIMESTAMP(3),
+
+    CONSTRAINT "ArticleSource_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ArticleSearchResult" (
+    "id" TEXT NOT NULL,
+    "score" DOUBLE PRECISION NOT NULL,
+    "title" TEXT NOT NULL,
+    "url" TEXT NOT NULL,
+    "publishedDate" TIMESTAMP(3),
+    "author" TEXT,
+    "text" TEXT NOT NULL,
+    "articleId" INTEGER NOT NULL,
+    "updatedAt" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deletedAt" TIMESTAMP(3),
+
+    CONSTRAINT "ArticleSearchResult_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ArticleGenerationOptions" (
+    "id" TEXT NOT NULL,
+    "numberOfSearchQueryVariations" INTEGER NOT NULL,
+    "numberOfWebResultsToInclude" INTEGER NOT NULL,
+    "audience" TEXT NOT NULL,
+    "purpose" TEXT NOT NULL,
+    "maxCharactersOfSearchContentToUse" INTEGER NOT NULL,
+    "tone" TEXT NOT NULL,
+    "format" TEXT NOT NULL,
+    "articleId" INTEGER NOT NULL,
+    "updatedAt" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deletedAt" TIMESTAMP(3),
+
+    CONSTRAINT "ArticleGenerationOptions_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ArticleComment" (
+    "id" TEXT NOT NULL,
+    "content" TEXT NOT NULL,
+    "articleId" INTEGER NOT NULL,
+    "userId" TEXT NOT NULL,
+    "updatedAt" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "deletedAt" TIMESTAMP(3),
+
+    CONSTRAINT "ArticleComment_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "_ArticleToArticleTag" (
+    "A" INTEGER NOT NULL,
+    "B" TEXT NOT NULL
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Article_slug_key" ON "Article"("slug");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "ArticleCategory_name_key" ON "ArticleCategory"("name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "ArticleCategory_slug_key" ON "ArticleCategory"("slug");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "ArticleTag_name_key" ON "ArticleTag"("name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "ArticleTag_slug_key" ON "ArticleTag"("slug");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "ArticleGenerationOptions_articleId_key" ON "ArticleGenerationOptions"("articleId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "_ArticleToArticleTag_AB_unique" ON "_ArticleToArticleTag"("A", "B");
+
+-- CreateIndex
+CREATE INDEX "_ArticleToArticleTag_B_index" ON "_ArticleToArticleTag"("B");
+
+-- AddForeignKey
+ALTER TABLE "Article" ADD CONSTRAINT "Article_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Article" ADD CONSTRAINT "Article_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "ArticleCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "ArticleSource" ADD CONSTRAINT "ArticleSource_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "ArticleSearchResult" ADD CONSTRAINT "ArticleSearchResult_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "ArticleGenerationOptions" ADD CONSTRAINT "ArticleGenerationOptions_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "ArticleComment" ADD CONSTRAINT "ArticleComment_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "ArticleComment" ADD CONSTRAINT "ArticleComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "_ArticleToArticleTag" ADD CONSTRAINT "_ArticleToArticleTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "_ArticleToArticleTag" ADD CONSTRAINT "_ArticleToArticleTag_B_fkey" FOREIGN KEY ("B") REFERENCES "ArticleTag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 875cdffe..9dc45c3f 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -150,6 +150,8 @@ model User {
   symptomReports                        DfdaUserSymptomReport[]
   treatmentReports                      DfdaUserTreatmentReport[]
   sideEffectReports                     DfdaUserSideEffectReport[]
+  Article                               Article[]
+  ArticleComment                        ArticleComment[]
 }
 
 model Education {
@@ -1407,3 +1409,128 @@ enum Effectiveness {
   MODERATE_IMPROVEMENT
   MAJOR_IMPROVEMENT
 }
+
+model Article {
+  id            Int                  @id @default(autoincrement())
+  title         String
+  slug          String               @unique
+  description   String
+  content       String               @db.Text
+  status        ArticleStatus        @default(DRAFT)
+  visibility    ArticleVisibility    @default(PUBLIC)
+  password      String?
+  publishedAt   DateTime?
+  updatedAt     DateTime             @updatedAt
+  createdAt     DateTime             @default(now())
+  deletedAt     DateTime?
+  commentStatus ArticleCommentStatus @default(OPEN)
+  sticky        Boolean              @default(false)
+  featuredImage String?
+  promptedTopic String?
+
+  userId String
+  user   User   @relation(fields: [userId], references: [id])
+
+  categoryId String
+  category   ArticleCategory @relation(fields: [categoryId], references: [id])
+
+  tags              ArticleTag[]
+  sources           ArticleSource[]
+  searchResults     ArticleSearchResult[]
+  generationOptions ArticleGenerationOptions?
+  comments          ArticleComment[]
+}
+
+enum ArticleStatus {
+  DRAFT
+  PENDING
+  PRIVATE
+  PUBLISH
+}
+
+enum ArticleVisibility {
+  PUBLIC
+  PASSWORD_PROTECTED
+  PRIVATE
+}
+
+enum ArticleCommentStatus {
+  OPEN
+  CLOSED
+}
+
+model ArticleCategory {
+  id          String    @id @default(cuid())
+  name        String    @unique
+  slug        String    @unique
+  description String?
+  articles    Article[]
+  updatedAt   DateTime  @updatedAt
+  createdAt   DateTime  @default(now())
+  deletedAt   DateTime?
+}
+
+model ArticleTag {
+  id        String    @id @default(cuid())
+  name      String    @unique
+  slug      String    @unique
+  articles  Article[]
+  updatedAt DateTime  @updatedAt
+  createdAt DateTime  @default(now())
+  deletedAt DateTime?
+}
+
+model ArticleSource {
+  id          String    @id @default(cuid())
+  url         String
+  title       String
+  description String?
+  articleId   Int
+  article     Article   @relation(fields: [articleId], references: [id])
+  updatedAt   DateTime  @updatedAt
+  createdAt   DateTime  @default(now())
+  deletedAt   DateTime?
+}
+
+model ArticleSearchResult {
+  id            String    @id @default(cuid())
+  score         Float
+  title         String
+  url           String
+  publishedDate DateTime?
+  author        String?
+  text          String    @db.Text
+  articleId     Int
+  article       Article   @relation(fields: [articleId], references: [id])
+  updatedAt     DateTime  @updatedAt
+  createdAt     DateTime  @default(now())
+  deletedAt     DateTime?
+}
+
+model ArticleGenerationOptions {
+  id                                String    @id @default(cuid())
+  numberOfSearchQueryVariations     Int
+  numberOfWebResultsToInclude       Int
+  audience                          String
+  purpose                           String
+  maxCharactersOfSearchContentToUse Int
+  tone                              String
+  format                            String
+  articleId                         Int       @unique
+  article                           Article   @relation(fields: [articleId], references: [id])
+  updatedAt                         DateTime  @updatedAt
+  createdAt                         DateTime  @default(now())
+  deletedAt                         DateTime?
+}
+
+model ArticleComment {
+  id        String    @id @default(cuid())
+  content   String    @db.Text
+  articleId Int
+  article   Article   @relation(fields: [articleId], references: [id])
+  userId    String
+  user      User      @relation(fields: [userId], references: [id])
+  updatedAt DateTime  @updatedAt
+  createdAt DateTime  @default(now())
+  deletedAt DateTime?
+}