From 65be33500c26bbb47c8513bb683fd8625be30e36 Mon Sep 17 00:00:00 2001 From: joannechen1223 Date: Tue, 5 Mar 2024 23:44:00 -0500 Subject: [PATCH] feat: add prisma migration --- apps/recnet-api/buildspec.yml | 33 ++++++ .../20240306035932_init/migration.sql | 102 ++++++++++++++++++ .../prisma/migrations/migration_lock.toml | 3 + apps/recnet-api/prisma/schema.prisma | 58 +++++++++- .../prisma/prisma.connection.provider.ts | 29 ++++- 5 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 apps/recnet-api/buildspec.yml create mode 100644 apps/recnet-api/prisma/migrations/20240306035932_init/migration.sql create mode 100644 apps/recnet-api/prisma/migrations/migration_lock.toml diff --git a/apps/recnet-api/buildspec.yml b/apps/recnet-api/buildspec.yml new file mode 100644 index 00000000..0c81fcd6 --- /dev/null +++ b/apps/recnet-api/buildspec.yml @@ -0,0 +1,33 @@ +version: 0.2 + +phases: + install: + runtime-versions: + nodejs: 18 + commands: + - npm i -g pnpm + - pnpm i --frozen-lockfile + pre_build: + commands: + - pnpm nx clean recnet-api + - pnpm nx prisma:generate recnet-api + build: + commands: + - pnpm nx build recnet-api + post_build: + commands: + - rm -rf node_modules + - mv package.json package.json.bak + - mv pnpm-lock.yaml pnpm-lock.yaml.bak + - mv dist/apps/recnet-api/package.json package.json + - mv dist/apps/recnet-api/pnpm-lock.yaml pnpm-lock.yaml + - pnpm install --prod --frozen-lockfile + - pnpx prisma generate --schema=apps/recnet-api/prisma/schema.prisma + - cp -R apps/recnet-api/prisma/ dist/apps/recnet-api/prisma +artifacts: + files: + - "dist/apps/recnet-api/**/*" + - "node_modules/**/*" + - "package.json" + - "Procfile" + enable-symlinks: yes diff --git a/apps/recnet-api/prisma/migrations/20240306035932_init/migration.sql b/apps/recnet-api/prisma/migrations/20240306035932_init/migration.sql new file mode 100644 index 00000000..921da429 --- /dev/null +++ b/apps/recnet-api/prisma/migrations/20240306035932_init/migration.sql @@ -0,0 +1,102 @@ +-- CreateEnum +CREATE TYPE "Provider" AS ENUM ('FACEBOOK', 'GOOGLE'); + +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('ADMIN', 'USER'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "provider" "Provider" NOT NULL, + "providerId" VARCHAR(128) NOT NULL, + "email" VARCHAR(128) NOT NULL, + "handle" VARCHAR(32) NOT NULL, + "displayName" VARCHAR(32) NOT NULL, + "inviteCode" VARCHAR(64), + "photoUrl" VARCHAR(256) NOT NULL, + "affiliation" VARCHAR(32), + "bio" TEXT, + "lastLoginAt" TIMESTAMP(3) NOT NULL, + "role" "Role" NOT NULL DEFAULT 'USER', + "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 "FollowingRecord" ( + "userId" TEXT NOT NULL, + "followerId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FollowingRecord_pkey" PRIMARY KEY ("userId","followerId") +); + +-- CreateTable +CREATE TABLE "Recommendation" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "articleId" TEXT NOT NULL, + "description" TEXT NOT NULL, + "cutoff" TIMESTAMP(3) NOT NULL, + "createAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updateAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Recommendation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Article" ( + "id" TEXT NOT NULL, + "doi" VARCHAR(32), + "title" VARCHAR(256) NOT NULL, + "author" VARCHAR(256) NOT NULL, + "link" VARCHAR(256) NOT NULL, + "year" SMALLINT NOT NULL, + "month" SMALLINT, + "isVerified" BOOLEAN NOT NULL DEFAULT false, + "createAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updateAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Article_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "InviteCode" ( + "id" SERIAL NOT NULL, + "code" VARCHAR(64) NOT NULL, + "ownerId" VARCHAR(32) NOT NULL, + "issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "usedById" VARCHAR(32), + "usedAt" TIMESTAMP(3), + + CONSTRAINT "InviteCode_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_handle_key" ON "User"("handle"); + +-- CreateIndex +CREATE UNIQUE INDEX "InviteCode_usedById_key" ON "InviteCode"("usedById"); + +-- AddForeignKey +ALTER TABLE "FollowingRecord" ADD CONSTRAINT "FollowingRecord_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FollowingRecord" ADD CONSTRAINT "FollowingRecord_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Recommendation" ADD CONSTRAINT "Recommendation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Recommendation" ADD CONSTRAINT "Recommendation_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InviteCode" ADD CONSTRAINT "InviteCode_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InviteCode" ADD CONSTRAINT "InviteCode_usedById_fkey" FOREIGN KEY ("usedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/recnet-api/prisma/migrations/migration_lock.toml b/apps/recnet-api/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/apps/recnet-api/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/apps/recnet-api/prisma/schema.prisma b/apps/recnet-api/prisma/schema.prisma index 9f2ec4a3..7afc3102 100644 --- a/apps/recnet-api/prisma/schema.prisma +++ b/apps/recnet-api/prisma/schema.prisma @@ -33,11 +33,67 @@ model User { handle String @db.VarChar(32) @unique displayName String @db.VarChar(32) inviteCode String? @db.VarChar(64) - photoUrl String @db.VarChar(128) + photoUrl String @db.VarChar(256) affiliation String? @db.VarChar(32) bio String? lastLoginAt DateTime role Role @default(USER) // Enum type createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + + following FollowingRecord[] @relation("Following") + followers FollowingRecord[] @relation("Follower") + recommendations Recommendation[] + inviteCodeOwner InviteCode[] @relation("InviteCodeOwner") + inviteCodeUsed InviteCode? @relation("InviteCodeUsedBy") +} + +model FollowingRecord { + userId String + followerId String + createdAt DateTime + + @@id([userId, followerId]) + user User @relation("Following", fields: [userId], references: [id]) + follower User @relation("Follower", fields: [followerId], references: [id]) +} + +model Recommendation { + id String @id @default(uuid()) + userId String + articleId String + description String + cutoff DateTime + createAt DateTime @default(now()) + updateAt DateTime @default(now()) @updatedAt + + user User @relation(fields: [userId], references: [id]) // User who made the recommendation + article Article @relation(fields: [articleId], references: [id]) // Article being recommended +} + +model Article { + id String @id + doi String? @db.VarChar(32) + title String @db.VarChar(256) + author String @db.VarChar(256) + link String @db.VarChar(256) + year Int @db.SmallInt + month Int? @db.SmallInt + isVerified Boolean @default(false) + createAt DateTime @default(now()) + updateAt DateTime @default(now()) @updatedAt + + recommendations Recommendation[] +} + +model InviteCode { + id Int @id @default(autoincrement()) + code String @db.VarChar(64) + ownerId String @db.VarChar(32) + issuedAt DateTime @default(now()) + usedById String? @db.VarChar(32) @unique + usedAt DateTime? + + owner User @relation("InviteCodeOwner", fields: [ownerId], references: [id]) + usedBy User? @relation("InviteCodeUsedBy", fields: [usedById], references: [id]) } diff --git a/apps/recnet-api/src/database/prisma/prisma.connection.provider.ts b/apps/recnet-api/src/database/prisma/prisma.connection.provider.ts index 0b111667..2b59e4b4 100644 --- a/apps/recnet-api/src/database/prisma/prisma.connection.provider.ts +++ b/apps/recnet-api/src/database/prisma/prisma.connection.provider.ts @@ -1,3 +1,5 @@ +import { execSync } from "child_process"; + import { Injectable, OnModuleInit } from "@nestjs/common"; import { PrismaClient } from "@prisma/client"; @@ -14,22 +16,39 @@ export class PrismaConnectionProvider extends PrismaClient implements OnModuleInit { + // prisma url + private prismaUrl: string; + constructor() { + const connectionUrl = `postgresql://${postgresConfig.username}:${encodeURIComponent( + postgresConfig.password + )}@${postgresConfig.host}:${postgresConfig.port}/${ + postgresConfig.database + }?schema=recnet`; + super({ datasources: { db: { - url: `postgresql://${postgresConfig.username}:${encodeURIComponent( - postgresConfig.password - )}@${postgresConfig.host}:${postgresConfig.port}/${ - postgresConfig.database - }?schema=recnet&pool_timeout=60`, + url: `${connectionUrl}&pool_timeout=60`, }, }, }); + + this.prismaUrl = connectionUrl; } async onModuleInit() { await this.$connect(); + + const prismaSchema = + process.env.PRISMA_SCHEMA || "dist/apps/recnet-api/prisma/schema.prisma"; + + if (process.env.DB_MIGRATE === "true") { + execSync( + `export PRISMA_DATABASE_URL=${this.prismaUrl} && pnpx prisma migrate deploy --schema=${prismaSchema}`, + { stdio: "inherit" } + ); + } } }