From 6d7a7641fad4c56e5f9ed0a9f079ed41e2c32574 Mon Sep 17 00:00:00 2001 From: zackhow Date: Mon, 5 Jun 2023 17:09:42 -0400 Subject: [PATCH] feat: add support for PostgreSQL ENVs for pgsql - DB_TYPE=postgres - DB_HOST=${ip/host} - DB_USER=${pg user} - DB_PASS=${pg pass} - DB_NAME=${db name} (defaults to 'overseerr') --- package.json | 1 + server/datasource.ts | 48 +++++++++++++++-- server/entity/Media.ts | 5 +- server/entity/MediaRequest.ts | 17 +++++- server/routes/request.ts | 2 +- server/utils/DbColumnHelper.ts | 22 ++++++++ yarn.lock | 95 ++++++++++++++++++++++++++++++++++ 7 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 server/utils/DbColumnHelper.ts diff --git a/package.json b/package.json index 8b82e45d..19a88c7e 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "node-schedule": "2.1.1", "nodemailer": "6.9.1", "openpgp": "5.7.0", + "pg": "8.11.0", "plex-api": "5.3.2", "pug": "3.0.2", "react": "18.2.0", diff --git a/server/datasource.ts b/server/datasource.ts index d4eadaa1..9e697214 100644 --- a/server/datasource.ts +++ b/server/datasource.ts @@ -30,9 +30,51 @@ const prodConfig: DataSourceOptions = { subscribers: ['dist/subscriber/**/*.js'], }; -const dataSource = new DataSource( - process.env.NODE_ENV !== 'production' ? devConfig : prodConfig -); +const postgresDevConfig: DataSourceOptions = { + type: 'postgres', + name: 'pgdb', + host: process.env.DB_HOST, + username: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME ?? 'overseerr', + synchronize: true, + migrationsRun: false, + logging: false, + entities: ['server/entity/**/*.ts'], + migrations: ['server/migration/**/*.ts'], + subscribers: ['server/subscriber/**/*.ts'], +}; + +const postgresProdConfig: DataSourceOptions = { + type: 'postgres', + name: 'pgdb', + host: process.env.DB_HOST, + username: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME ?? 'overseerr', + synchronize: false, + migrationsRun: false, + logging: false, + entities: ['dist/entity/**/*.js'], + migrations: ['dist/migration/**/*.js'], + subscribers: ['dist/subscriber/**/*.js'], +}; + +export const isPgsql = process.env.DB_TYPE === 'postgres'; + +function getDataSource(): DataSourceOptions { + if (process.env.NODE_ENV === 'production') { + if (isPgsql) { + return postgresProdConfig; + } + return prodConfig; + } else if (isPgsql) { + return postgresDevConfig; + } + return devConfig; +} + +const dataSource = new DataSource(getDataSource()); export const getRepository = ( target: EntityTarget diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 2d169172..90f58317 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -6,6 +6,7 @@ import type { DownloadingItem } from '@server/lib/downloadtracker'; import downloadTracker from '@server/lib/downloadtracker'; import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; +import { DbAwareColumn } from '@server/utils/DbColumnHelper'; import { AfterLoad, Column, @@ -108,10 +109,10 @@ class Media { @UpdateDateColumn() public updatedAt: Date; - @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) + @DbAwareColumn({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) public lastSeasonChange: Date; - @Column({ type: 'datetime', nullable: true }) + @DbAwareColumn({ type: 'datetime', nullable: true }) public mediaAddedAt: Date; @Column({ nullable: true, type: 'int' }) diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 5a8d3988..f42d31cb 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -12,7 +12,7 @@ import { MediaStatus, MediaType, } from '@server/constants/media'; -import { getRepository } from '@server/datasource'; +import { getRepository, isPgsql } from '@server/datasource'; import type { MediaRequestBody } from '@server/interfaces/api/requestInterfaces'; import notificationManager, { Notification } from '@server/lib/notifications'; import { Permission } from '@server/lib/permissions'; @@ -569,6 +569,14 @@ export class MediaRequest { }); return; } + + // Typeorm pgsql doesn't load this relation even though the query specifically + // calls for it. The fk will get blown away from media_request when it is + // saved. Adding request back in prior to saving. + if (isPgsql) { + media.requests.push(this); + } + const seasonRequestRepository = getRepository(SeasonRequest); if ( this.status === MediaRequestStatus.APPROVED && @@ -627,6 +635,13 @@ export class MediaRequest { relations: { requests: true }, }); + // Typeorm pgsql doesn't load this relation even though the query specifically + // calls for it. The fk will get blown away from media_request when it is + // saved. Adding request back in prior to saving. + if (isPgsql) { + fullMedia.requests.push(this); + } + if ( !fullMedia.requests.some((request) => !request.is4k) && fullMedia.status !== MediaStatus.AVAILABLE diff --git a/server/routes/request.ts b/server/routes/request.ts index 83c05b48..3ab1685b 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -109,7 +109,7 @@ requestRoutes.get, RequestResultsResponse>( requestStatus: statusFilter, }) .andWhere( - '((request.is4k = 0 AND media.status IN (:...mediaStatus)) OR (request.is4k = 1 AND media.status4k IN (:...mediaStatus)))', + '((request.is4k = false AND media.status IN (:...mediaStatus)) OR (request.is4k = true AND media.status4k IN (:...mediaStatus)))', { mediaStatus: mediaStatusFilter, } diff --git a/server/utils/DbColumnHelper.ts b/server/utils/DbColumnHelper.ts new file mode 100644 index 00000000..15ee9e3e --- /dev/null +++ b/server/utils/DbColumnHelper.ts @@ -0,0 +1,22 @@ +import type { ColumnOptions, ColumnType } from 'typeorm'; +import { Column } from 'typeorm'; + +export const isPostgres = process.env.DB_TYPE === 'postgres'; + +const pgTypeMapping: { [key: string]: ColumnType } = { + datetime: 'timestamp with time zone', +}; + +export function resolveDbType(pgType: ColumnType): ColumnType { + if (isPostgres && pgType.toString() in pgTypeMapping) { + return pgTypeMapping[pgType.toString()]; + } + return pgType; +} + +export function DbAwareColumn(columnOptions: ColumnOptions) { + if (columnOptions.type) { + columnOptions.type = resolveDbType(columnOptions.type); + } + return Column(columnOptions); +} diff --git a/yarn.lock b/yarn.lock index 886aee53..5ee7b61e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4976,6 +4976,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -10853,6 +10858,11 @@ p-wait-for@3.2.0: dependencies: p-timeout "^3.0.0" +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + pacote@^13.0.3, pacote@^13.6.1, pacote@^13.6.2: version "13.6.2" resolved "https://registry.yarnpkg.com/pacote/-/pacote-13.6.2.tgz#0d444ba3618ab3e5cd330b451c22967bbd0ca48a" @@ -11037,6 +11047,64 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== +pg-cloudflare@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz#833d70870d610d14bf9df7afb40e1cba310c17a0" + integrity sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA== + +pg-connection-string@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.0.tgz#12a36cc4627df19c25cc1b9b736cc39ee1f73ae8" + integrity sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e" + integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ== + +pg-protocol@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" + integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.0.tgz#a37e534e94b57a7ed811e926f23a7c56385f55d9" + integrity sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.6.0" + pg-pool "^3.6.0" + pg-protocol "^1.6.0" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.0" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -11183,6 +11251,28 @@ postcss@8.4.21, postcss@^8.0.9: picocolors "^1.0.0" source-map-js "^1.0.2" +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -12584,6 +12674,11 @@ split2@^3.0.0: dependencies: readable-stream "^3.0.0" +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + split2@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/split2/-/split2-1.0.0.tgz#52e2e221d88c75f9a73f90556e263ff96772b314"