refactor: road to prisma

Converting this to use prisma with postgres instead of using typeorm and sqlite

BREAKING CHANGE: Incomplete index page in server folder
This commit is contained in:
jumail
2022-03-27 02:55:17 +05:00
parent 8a8953d52e
commit 7e7efc06ba
7 changed files with 366 additions and 34 deletions

7
.env Normal file
View File

@@ -0,0 +1,7 @@
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB (Preview) and CockroachDB (Preview).
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="postgresql://jumail:1DontGive1@localhost:5432/jellyseerr?schema=public"

View File

@@ -19,6 +19,7 @@
"dependencies": {
"@headlessui/react": "^1.4.1",
"@heroicons/react": "^1.0.4",
"@prisma/client": "^3.11.1",
"@supercharge/request-ip": "^1.1.2",
"@svgr/webpack": "^5.5.0",
"@tanem/react-nprogress": "^3.0.79",
@@ -46,6 +47,7 @@
"nodemailer": "^6.6.3",
"openpgp": "^5.0.0-3",
"plex-api": "^5.3.1",
"prisma": "^3.11.1",
"pug": "^3.0.2",
"react": "17.0.2",
"react-ace": "^9.3.0",

View File

@@ -0,0 +1,163 @@
-- CreateTable
CREATE TABLE "media" (
"id" SERIAL NOT NULL,
"mediaType" TEXT NOT NULL,
"tmdbId" INTEGER NOT NULL,
"tvdbId" INTEGER,
"imdbId" TEXT,
"status" INTEGER NOT NULL DEFAULT 1,
"status4k" INTEGER NOT NULL DEFAULT 1,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastSeasonChange" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"mediaAddedAt" TIMESTAMP(3),
"serviceId" INTEGER,
"serviceId4k" INTEGER,
"externalServiceId" INTEGER,
"externalServiceId4k" INTEGER,
"externalServiceSlug" TEXT,
"externalServiceSlug4k" TEXT,
"ratingKey" TEXT,
"ratingKey4k" TEXT,
"jellyfinMediaId" TEXT,
"jellyfinMediaId4k" TEXT,
CONSTRAINT "media_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "media_request" (
"id" SERIAL NOT NULL,
"status" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"type" TEXT NOT NULL,
"mediaId" INTEGER,
"requestedById" INTEGER,
"modifiedById" INTEGER,
"is4k" BOOLEAN NOT NULL DEFAULT false,
"serverId" INTEGER,
"profileId" INTEGER,
"rootFolder" TEXT,
"languageProfileId" INTEGER,
CONSTRAINT "media_request_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "migrations" (
"id" SERIAL NOT NULL,
"timestamp" BIGINT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "migrations_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "season" (
"id" SERIAL NOT NULL,
"seasonNumber" INTEGER NOT NULL,
"status" INTEGER NOT NULL DEFAULT 1,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"mediaId" INTEGER,
"status4k" INTEGER NOT NULL DEFAULT 1,
CONSTRAINT "season_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "season_request" (
"id" SERIAL NOT NULL,
"seasonNumber" INTEGER NOT NULL,
"status" INTEGER NOT NULL DEFAULT 1,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"requestId" INTEGER,
CONSTRAINT "season_request_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "session" (
"expiredAt" BIGINT NOT NULL,
"id" TEXT NOT NULL,
"json" TEXT NOT NULL,
CONSTRAINT "session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user" (
"id" SERIAL NOT NULL,
"email" TEXT NOT NULL,
"username" TEXT,
"plexId" INTEGER,
"plexToken" TEXT,
"permissions" INTEGER NOT NULL DEFAULT 0,
"avatar" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"password" TEXT,
"userType" INTEGER NOT NULL DEFAULT 1,
"plexUsername" TEXT,
"resetPasswordGuid" TEXT,
"recoveryLinkExpirationDate" TIMESTAMP(3),
"jellyfinUsername" TEXT,
"jellyfinAuthToken" TEXT,
"jellyfinUserId" TEXT,
"jellyfinDeviceId" TEXT,
CONSTRAINT "user_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_settings" (
"id" SERIAL NOT NULL,
"enableNotifications" BOOLEAN NOT NULL DEFAULT true,
"discordId" TEXT,
"userId" INTEGER,
"region" TEXT,
"originalLanguage" TEXT,
CONSTRAINT "user_settings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "sqlite_autoindex_media_1" ON "media"("tvdbId");
-- CreateIndex
CREATE INDEX "IDX_7ff2d11f6a83cb52386eaebe74" ON "media"("imdbId");
-- CreateIndex
CREATE INDEX "IDX_41a289eb1fa489c1bc6f38d9c3" ON "media"("tvdbId");
-- CreateIndex
CREATE INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5" ON "media"("tmdbId");
-- CreateIndex
CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session"("expiredAt");
-- CreateIndex
CREATE UNIQUE INDEX "sqlite_autoindex_user_1" ON "user"("email");
-- CreateIndex
CREATE UNIQUE INDEX "sqlite_autoindex_user_settings_1" ON "user_settings"("userId");
-- AddForeignKey
ALTER TABLE "media_request" ADD CONSTRAINT "media_request_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "media"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "media_request" ADD CONSTRAINT "media_request_modifiedById_fkey" FOREIGN KEY ("modifiedById") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "media_request" ADD CONSTRAINT "media_request_requestedById_fkey" FOREIGN KEY ("requestedById") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "season" ADD CONSTRAINT "season_mediaId_fkey" FOREIGN KEY ("mediaId") REFERENCES "media"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "season_request" ADD CONSTRAINT "season_request_requestId_fkey" FOREIGN KEY ("requestId") REFERENCES "media_request"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION;

View File

@@ -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"

127
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,127 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgres"
url = env("DATABASE_URL")
}
model media {
id Int @id @default(autoincrement())
mediaType String
tmdbId Int
tvdbId Int? @unique(map: "sqlite_autoindex_media_1")
imdbId String?
status Int @default(1)
status4k Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
lastSeasonChange DateTime @default(now())
mediaAddedAt DateTime?
serviceId Int?
serviceId4k Int?
externalServiceId Int?
externalServiceId4k Int?
externalServiceSlug String?
externalServiceSlug4k String?
ratingKey String?
ratingKey4k String?
jellyfinMediaId String?
jellyfinMediaId4k String?
media_request media_request[]
season season[]
@@index([imdbId], map: "IDX_7ff2d11f6a83cb52386eaebe74")
@@index([tvdbId], map: "IDX_41a289eb1fa489c1bc6f38d9c3")
@@index([tmdbId], map: "IDX_7157aad07c73f6a6ae3bbd5ef5")
}
model media_request {
id Int @id @default(autoincrement())
status Int
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
type String
mediaId Int?
requestedById Int?
modifiedById Int?
is4k Boolean @default(false)
serverId Int?
profileId Int?
rootFolder String?
languageProfileId Int?
media media? @relation(fields: [mediaId], references: [id], onDelete: Cascade, onUpdate: NoAction)
user_media_request_modifiedByIdTouser user? @relation("media_request_modifiedByIdTouser", fields: [modifiedById], references: [id], onUpdate: NoAction)
user_media_request_requestedByIdTouser user? @relation("media_request_requestedByIdTouser", fields: [requestedById], references: [id], onDelete: Cascade, onUpdate: NoAction)
season_request season_request[]
}
model migrations {
id Int @id @default(autoincrement())
timestamp BigInt
name String
}
model season {
id Int @id @default(autoincrement())
seasonNumber Int
status Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
mediaId Int?
status4k Int @default(1)
media media? @relation(fields: [mediaId], references: [id], onDelete: Cascade, onUpdate: NoAction)
}
model season_request {
id Int @id @default(autoincrement())
seasonNumber Int
status Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
requestId Int?
media_request media_request? @relation(fields: [requestId], references: [id], onDelete: Cascade, onUpdate: NoAction)
}
model session {
expiredAt BigInt
id String @id
json String
@@index([expiredAt], map: "IDX_28c5d1d16da7908c97c9bc2f74")
}
model user {
id Int @id @default(autoincrement())
email String @unique(map: "sqlite_autoindex_user_1")
username String?
plexId Int?
plexToken String?
permissions Int @default(0)
avatar String
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
password String?
userType Int @default(1)
plexUsername String?
resetPasswordGuid String?
recoveryLinkExpirationDate DateTime?
jellyfinUsername String?
jellyfinAuthToken String?
jellyfinUserId String?
jellyfinDeviceId String?
media_request_media_request_modifiedByIdTouser media_request[] @relation("media_request_modifiedByIdTouser")
media_request_media_request_requestedByIdTouser media_request[] @relation("media_request_requestedByIdTouser")
user_settings user_settings?
}
model user_settings {
id Int @id @default(autoincrement())
enableNotifications Boolean @default(true)
discordId String?
userId Int? @unique(map: "sqlite_autoindex_user_settings_1")
region String?
originalLanguage String?
user user? @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction)
}

View File

@@ -1,18 +1,14 @@
import { PrismaClient } from '@prisma/client';
import { getClientIp } from '@supercharge/request-ip';
import { TypeormStore } from 'connect-typeorm/out';
import cookieParser from 'cookie-parser';
import csurf from 'csurf';
import express, { NextFunction, Request, Response } from 'express';
import * as OpenApiValidator from 'express-openapi-validator';
import session, { Store } from 'express-session';
import next from 'next';
import path from 'path';
import swaggerUi from 'swagger-ui-express';
import { createConnection, getRepository } from 'typeorm';
import YAML from 'yamljs';
import PlexAPI from './api/plexapi';
import { Session } from './entity/Session';
import { User } from './entity/User';
import { startJobs } from './job/schedule';
import notificationManager from './lib/notifications';
import DiscordAgent from './lib/notifications/agents/discord';
@@ -35,18 +31,17 @@ logger.info(`Starting Overseerr version ${getAppVersion()}`);
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const prisma = new PrismaClient();
app
.prepare()
.then(async () => {
const dbConnection = await createConnection();
// Run migrations in production
if (process.env.NODE_ENV === 'production') {
await dbConnection.query('PRAGMA foreign_keys=OFF');
await dbConnection.runMigrations();
await dbConnection.query('PRAGMA foreign_keys=ON');
}
// // Run migrations in production
// if (process.env.NODE_ENV === 'production') {
// await dbConnection.query('PRAGMA foreign_keys=OFF');
// await dbConnection.runMigrations();
// await dbConnection.query('PRAGMA foreign_keys=ON');
// }
// Load Settings
const settings = getSettings().load();
@@ -56,14 +51,23 @@ app
settings.plex.libraries.length > 1 &&
!settings.plex.libraries[0].type
) {
const userRepository = getRepository(User);
const admin = await userRepository.findOne({
select: ['id', 'plexToken'],
order: { id: 'ASC' },
const admin = await prisma.user.findFirst({
select: {
id: true,
plexToken: true,
},
orderBy: {
id: 'asc',
},
});
// const userRepository = getRepository(User);
// const admin = await userRepository.findOne({
// select: ['id', 'plexToken'],
// order: { id: 'ASC' },
// });
if (admin) {
const plexapi = new PlexAPI({ plexToken: admin.plexToken });
const plexapi = new PlexAPI({ plexToken: admin.plexToken! });
await plexapi.syncLibraries();
logger.info('Migrating libraries to include media type', {
label: 'Settings',
@@ -129,22 +133,24 @@ app
}
// Set up sessions
const sessionRespository = getRepository(Session);
server.use(
'/api',
session({
secret: settings.clientId,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 30,
},
store: new TypeormStore({
cleanupLimit: 2,
ttl: 1000 * 60 * 60 * 24 * 30,
}).connect(sessionRespository) as Store,
})
);
// const sessionRespository = getRepository(Session);
// const sessionRespository = await prisma.session.findMany();
// server.use(
// '/api',
// session({
// secret: settings.clientId,
// resave: false,
// saveUninitialized: false,
// cookie: {
// maxAge: 1000 * 60 * 60 * 24 * 30,
// },
// store: new TypeormStore({
// cleanupLimit: 2,
// ttl: 1000 * 60 * 60 * 24 * 30,
// }).connect(sessionRespository) as Store,
// })
// );
const apiDocs = YAML.load(API_SPEC_PATH);
server.use('/api-docs', swaggerUi.serve, swaggerUi.setup(apiDocs));
server.use(

View File

@@ -2018,6 +2018,23 @@
"@octokit/openapi-types" "^1.2.0"
"@types/node" ">= 8"
"@prisma/client@^3.11.1":
version "3.11.1"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.11.1.tgz#bde6dec71ae133d04ce1c6658e3d76627a3c6dc7"
integrity sha512-B3C7zQG4HbjJzUr2Zg9UVkBJutbqq9/uqkl1S138+keZCubJrwizx3RuIvGwI+s+pm3qbsyNqXiZgL3Ir0fSng==
dependencies:
"@prisma/engines-version" "3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9"
"@prisma/engines-version@3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9":
version "3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9.tgz#81a1835b495ad287ad7824dbd62f74e9eee90fb9"
integrity sha512-HkcsDniA4iNb/gi0iuyOJNAM7nD/LwQ0uJm15v360O5dee3TM4lWdSQiTYBMK6FF68ACUItmzSur7oYuUZ2zkQ==
"@prisma/engines@3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9":
version "3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9.tgz#09ac23f8f615a8586d8d44538060ada199fe872c"
integrity sha512-MILbsGnvmnhCbFGa2/iSnsyGyazU3afzD7ldjCIeLIGKkNBMSZgA2IvpYsAXl+6qFHKGrS3B2otKfV31dwMSQw==
"@react-spring/animated@~9.2.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.2.0.tgz#02ea2a75c3b1557c9878f248227451119a9eb874"
@@ -11091,6 +11108,13 @@ preview-email@^3.0.5:
pug "^3.0.2"
uuid "^8.3.2"
prisma@^3.11.1:
version "3.11.1"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.11.1.tgz#fff9c0bcf83cb30c2e1d650882d5eb3c5565e028"
integrity sha512-aYn8bQwt1xwR2oSsVNHT4PXU7EhsThIwmpNB/MNUaaMx5OPLTro6VdNJe/sJssXFLxhamfWeMjwmpXjljo6xkg==
dependencies:
"@prisma/engines" "3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9"
proc-log@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-1.0.0.tgz#0d927307401f69ed79341e83a0b2c9a13395eb77"