-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[recnet-api] Connect environment to rds database and generate DB schema #160
Changes from all commits
044bf1b
27d450b
5eef3a3
ed557ad
8adbdec
cf93673
ba570c1
d6dc44b
4c1dd00
65be335
7306579
99a0760
25a7991
d32a292
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,4 +55,7 @@ Thumbs.db | |
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts | ||
next-env.d.ts | ||
|
||
# recnet-api specific | ||
apps/**/pnpm-lock.yaml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
web: pnpm nx deploy recnet-api | ||
web: cd dist/apps/recnet-api && node main.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
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 | ||
- cp -R apps/recnet-api/prisma/ dist/apps/recnet-api/prisma | ||
- cd dist/apps/recnet-api | ||
- pnpm install --prod --frozen-lockfile | ||
- pnpx prisma generate --schema=prisma/schema.prisma | ||
|
||
artifacts: | ||
files: | ||
- "dist/apps/recnet-api/**/*" | ||
- "Procfile" | ||
enable-symlinks: yes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" VARCHAR(64) 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" VARCHAR(64) NOT NULL, | ||
"followerId" VARCHAR(64) NOT NULL, | ||
"createdAt" TIMESTAMP(3) NOT NULL, | ||
|
||
CONSTRAINT "FollowingRecord_pkey" PRIMARY KEY ("userId","followerId") | ||
); | ||
|
||
-- CreateTable | ||
CREATE TABLE "Recommendation" ( | ||
"id" VARCHAR(64) NOT NULL, | ||
"userId" VARCHAR(64) NOT NULL, | ||
"articleId" VARCHAR(64) 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" VARCHAR(64) 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(64) NOT NULL, | ||
"issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"usedById" VARCHAR(64), | ||
"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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// This is your Prisma schema file, | ||
// learn more about it in the docs: https://pris.ly/d/prisma-schema | ||
|
||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? | ||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init | ||
|
||
generator client { | ||
provider = "prisma-client-js" | ||
} | ||
|
||
datasource db { | ||
provider = "postgresql" | ||
url = env("PRISMA_DATABASE_URL") | ||
} | ||
|
||
// Define enum types | ||
enum Provider { | ||
// Add other providers here as needed | ||
} | ||
|
||
enum Role { | ||
ADMIN | ||
USER | ||
} | ||
|
||
model User { | ||
id String @id @db.VarChar(64) @default(uuid()) // Primary key, UUID type | ||
provider Provider // Enum type | ||
providerId String @db.VarChar(128) | ||
email String @db.VarChar(128) @unique | ||
handle String @db.VarChar(32) @unique | ||
displayName String @db.VarChar(32) | ||
inviteCode String? @db.VarChar(64) | ||
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 @db.VarChar(64) | ||
followerId String @db.VarChar(64) | ||
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 @db.VarChar(64) @default(uuid()) | ||
userId String @db.VarChar(64) | ||
articleId String @db.VarChar(64) | ||
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 @db.VarChar(64) @default(uuid()) | ||
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(64) | ||
issuedAt DateTime @default(now()) | ||
usedById String? @db.VarChar(64) @unique | ||
usedAt DateTime? | ||
|
||
owner User @relation("InviteCodeOwner", fields: [ownerId], references: [id]) | ||
usedBy User? @relation("InviteCodeUsedBy", fields: [usedById], references: [id]) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { execSync } from "child_process"; | ||
|
||
import { Injectable, OnModuleInit } from "@nestjs/common"; | ||
import { PrismaClient } from "@prisma/client"; | ||
|
||
const postgresConfig = { | ||
host: process.env.RDS_HOSTNAME, | ||
port: parseInt(process.env.RDS_PORT, 10), | ||
database: process.env.RDS_DB_NAME, | ||
username: process.env.RDS_USERNAME, | ||
password: process.env.RDS_PASSWORD, | ||
}; | ||
Comment on lines
+6
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit]: you can use
The error message would be better since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same thing apply to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tend to use the config service provided by nestJS to deal with these configs. I will create another PR to enhance the configuration management and will consider to take in this advice. |
||
|
||
@Injectable() | ||
export class PrismaConnectionProvider | ||
extends PrismaClient | ||
implements OnModuleInit | ||
{ | ||
private prismaUrl: string; | ||
|
||
constructor() { | ||
const connectionUrl = `postgresql://${postgresConfig.username}:${encodeURIComponent( | ||
postgresConfig.password | ||
)}@${postgresConfig.host}:${postgresConfig.port}/${ | ||
postgresConfig.database | ||
}?schema=recnet`; | ||
|
||
super({ | ||
datasources: { | ||
db: { | ||
url: `${connectionUrl}&pool_timeout=60`, | ||
}, | ||
}, | ||
}); | ||
|
||
this.prismaUrl = connectionUrl; | ||
} | ||
|
||
async onModuleInit() { | ||
await this.$connect(); | ||
|
||
const prismaSchema = process.env.PRISMA_SCHEMA || "prisma/schema.prisma"; | ||
|
||
if (process.env.DB_MIGRATE === "true") { | ||
execSync( | ||
`export PRISMA_DATABASE_URL=${this.prismaUrl} && pnpx prisma migrate deploy --schema=${prismaSchema}`, | ||
{ stdio: "inherit" } | ||
); | ||
} | ||
} | ||
} | ||
|
||
export default PrismaConnectionProvider; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Module } from "@nestjs/common"; | ||
|
||
import PrismaConnectionProvider from "./prisma.connection.provider"; | ||
|
||
@Module({ | ||
providers: [PrismaConnectionProvider], | ||
exports: [PrismaConnectionProvider], | ||
}) | ||
export class PrismaModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Module } from "@nestjs/common"; | ||
|
||
import UserRepository from "./user.repository"; | ||
import { PrismaModule } from "../prisma/prisma.module"; | ||
|
||
@Module({ | ||
imports: [PrismaModule], | ||
providers: [UserRepository], | ||
exports: [UserRepository], | ||
}) | ||
export class DbRepositoryModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Injectable } from "@nestjs/common"; | ||
import { User } from "@prisma/client"; | ||
|
||
import PrismaConnectionProvider from "src/database/prisma/prisma.connection.provider"; | ||
|
||
@Injectable() | ||
export default class UserRepository { | ||
constructor(private readonly prisma: PrismaConnectionProvider) {} | ||
|
||
public async findByHandle(handle: string): Promise<User> { | ||
return this.prisma.user.findFirst({ | ||
where: { handle }, | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also add down migration file
down.sql
for each up migration?Docs: https://www.prisma.io/docs/orm/prisma-migrate/workflows/generating-down-migrations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea, I will create another PR to add it.