Skip to content
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

feat: add postgres support + migrations #628

Open
wants to merge 66 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
7b82ced
Merge pull request #312 from Fallenbagel/develop
Fallenbagel Jan 29, 2023
20c821e
chore(release): 1.4.0
semantic-release-bot Jan 29, 2023
d3622f7
Merge pull request #316 from Fallenbagel/develop
Fallenbagel Jan 31, 2023
57e7d68
chore(release): 1.4.1
semantic-release-bot Jan 31, 2023
f3cc8cb
Merge pull request #368 from Fallenbagel/develop
Fallenbagel Apr 20, 2023
24151d2
chore(release): 1.5.0
semantic-release-bot Apr 20, 2023
4b54976
Merge branch 'develop'
Fallenbagel Aug 4, 2023
5712e19
chore(release): 1.6.0
semantic-release-bot Aug 4, 2023
e7c11da
Merge pull request #477 from Fallenbagel/develop
Fallenbagel Sep 14, 2023
325e2ed
chore(release): 1.7.0
semantic-release-bot Sep 14, 2023
b39a5a7
feat: support for postgresql
zackhow Jul 4, 2023
573b64f
test(pgsql): disable root certificate verification
ralgar Nov 13, 2023
b6592bf
test(ci): temporarily change CI for local repo
ralgar Nov 13, 2023
abd80c1
fix: don't use SQLite idiom when using PgSQL
ralgar Nov 15, 2023
44aaca0
feat(db): add flag to toggle TLS for Postgres
ralgar Dec 16, 2023
4d85f29
feat(postgres and migrations): added migrations for postgres & imporv…
dr-carrot Jan 19, 2024
e6cc2c5
Merged changes from develop
dr-carrot Jan 19, 2024
ed57911
fix: restored workflow actions
dr-carrot Jan 19, 2024
14d3ec2
fix: access order
dr-carrot Jan 19, 2024
00c811d
fix: added pushover sound migration tto initial migration
dr-carrot Jan 20, 2024
0581d7b
fix: added option to log queries
dr-carrot Jan 20, 2024
e93ab06
fix: issue with session migration
dr-carrot Jan 20, 2024
b594dec
chore: relocate pushover sound migration
dr-carrot Jan 20, 2024
8c7004c
feat: added logging option to other datasources
dr-carrot Jan 20, 2024
5018592
chore: small tweaks for the datasource. Added docs for db setup
dr-carrot Jan 20, 2024
42ad4e0
chore: cleanup logs
dr-carrot Jan 20, 2024
106cd19
fix: added default dates to postgres migration
dr-carrot Jan 21, 2024
eb111ac
fix: removed psql specific relation checks
dr-carrot Jan 22, 2024
b1b4dd9
chore: added some debug sanity checks
dr-carrot Jan 27, 2024
f08c537
chore: added some more debug sanity checks
dr-carrot Jan 27, 2024
87c8444
chore: added some more additional debug sanity checks
dr-carrot Jan 27, 2024
0d6a1f1
chore: added some more+ additional debug sanity checks
dr-carrot Jan 27, 2024
0bab688
chore: mild log cleanup
dr-carrot Jan 27, 2024
7aca0be
chore: more log cleanup
dr-carrot Jan 27, 2024
610c372
chore: finish log cleanup
dr-carrot Jan 28, 2024
5f76799
fix: added not null to migration so typeorm doesn't delete ids
dr-carrot Jan 28, 2024
86444b8
chore: cleanup extra psql code
dr-carrot Jan 28, 2024
88b6768
fix: remove eager load
dr-carrot Jan 28, 2024
3ebf47f
fix: ensure requests are not disassociated with media
dr-carrot Jan 28, 2024
637e7db
Merge remote-tracking branch 'og/develop' into v1.7.0/postgresql
dr-carrot Mar 3, 2024
ef78fdd
docs: added documentation for migration to postgres
dr-carrot Mar 3, 2024
ff7bb88
docs: added database option to bug template
dr-carrot Mar 3, 2024
326d2cb
Merge remote-tracking branch 'og/develop' into v1.7.0/postgresql
dr-carrot Apr 4, 2024
cf4e3fd
Merge branch 'develop' of https://github.com/Fallenbagel/jellyseerr i…
dr-carrot Apr 23, 2024
f6b5a6f
Merge branch 'develop' of https://github.com/Fallenbagel/jellyseerr i…
dr-carrot May 8, 2024
2945264
feat: created docker-compose postgres file
dr-carrot May 8, 2024
caaed7c
fix: updated ts schema to align with change to migration
dr-carrot May 8, 2024
94a9806
fix: switch timestamp to include timezone
dr-carrot May 29, 2024
0280121
Merge branch 'develop' of https://github.com/Fallenbagel/jellyseerr i…
dr-carrot May 29, 2024
2b0d497
fix: fixed indentation in psql docker-compose
dr-carrot May 29, 2024
e2992fe
chore: merge from develop
dr-carrot Jun 21, 2024
f8926fa
fix: changed version to 0.1.0 to remove ui notification
dr-carrot Jun 21, 2024
357b927
style: fixed prettier in docker-compose.pastgres.yaml
dr-carrot Jun 21, 2024
66e308b
Merge branch 'develop' of https://github.com/Fallenbagel/jellyseerr i…
dr-carrot Jun 25, 2024
bd7339a
chore: restored CHANGELOG.md
dr-carrot Jun 25, 2024
3b12c98
chore: revverted ts commit
dr-carrot Aug 19, 2024
2add7af
chore: merge from dev
dr-carrot Aug 19, 2024
5ee2982
fix: update pnpm lock with pg package
dr-carrot Aug 19, 2024
4e63cee
Merge pull request #3 from dr-carrot/v1.7.0/postgresql-dev-merge
dr-carrot Aug 19, 2024
46c3af1
chore: merge from dev
dr-carrot Nov 4, 2024
96591a9
chore(pnpm-lock.yaml): updated pnpm-lock
dr-carrot Nov 4, 2024
7a5ee18
docs: update docs to add psql set up info
dr-carrot Nov 4, 2024
d3805d9
refactor: clean up code from cr comments
dr-carrot Nov 4, 2024
ab5cdf5
feat: migrate blacklist
dr-carrot Nov 4, 2024
94efdf7
fix: fix issue with cypress tests
dr-carrot Nov 5, 2024
830f431
docs: update psql docs
dr-carrot Nov 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ body:
- tablet
validations:
required: true
- type: dropdown
id: database
attributes:
options:
- SQLite (default)
- PostgreSQL
label: Database
description: Which database backend are you using?
- type: input
id: device
attributes:
Expand Down
159 changes: 159 additions & 0 deletions CHANGELOG.md
gauthier-th marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions README.md
gauthier-th marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,54 @@ _*On Jellyfin/Emby, ensure the `Settings > Home > Automatically group content fr
Check out our docker hub for instructions on how to install and run Jellyseerr:
https://hub.docker.com/r/fallenbagel/jellyseerr

### Database configuration

Jellyseerr supports sqlite and postgres. The database connection can be configured using the following options:

#### SQLite Options

```dotenv
DB_TYPE="sqlite" # Which DB engine to use. The default is "sqlite"
CONFIG_DIRECTORY="config" # The path to the config directory where the db file is stored
DB_LOG_QUERIES="false" # Whether to log the DB queries for debugging
```

#### PostgreSQL Options

```dotenv
DB_TYPE="postgres" # Which DB engine to use. The default is "sqlite". To use postgres, this needs to be set to "postgres"
DB_HOST= # The host (url) of the database
DB_PORT="5432" # The port to connect to
DB_USER= # Username used to connect to the database
DB_PASS= # Password of the user used to connect to the database
DB_NAME="jellyseerr" # The name of the database to connect to
DB_LOG_QUERIES="false" # Whether to log the DB queries for debugging
DB_USE_SSL="false" # Whether to enable ssl for database connection

# The following options can be used to further configure ssl:
DB_SSL_REJECT_UNAUTHORIZED="true" # Whether to reject ssl connections with unverifiable certificates i.e. self-signed certificates without providing the below settings
DB_SSL_CA= # The CA certificate to verify the connection, provided as a string
DB_SSL_CA_FILE= # The path to a CA certificate to verify the connection
DB_SSL_KEY= # The private key for the connection in PEM format, provided as a string
DB_SSL_KEY_FILE= # Path to the private key for the connection in PEM format
DB_SSL_CERT= # Certificate chain in pem format for the private key, provided as a string
DB_SSL_CERT_FILE= # Path to certificate chain in pem format for the private key
```

#### Migrating from SQLite to PostgreSQL

1. Set up your PostgreSQL database and configure Jellyseerr to use it
2. Run Jellyseerr to create the tables in the PostgreSQL database
3. Stop Jellyseerr
4. Run the following command to export the data from the SQLite database and import it into the PostgreSQL database:
- Edit the postgres connection string to match your setup
- WARNING: The most recent release of pgloader has an issue quoting the table columns. Use the version in the docker container to avoid this issue.
- "I don't have or don't want to use docker" - You can build the working pgloader version [in this PR](https://github.com/dimitri/pgloader/pull/1531) from source and use the same options as below.
```bash
docker run --rm -v config/db.sqlite3:/db.sqlite3:ro -v pgloader/pgloader.load:/pgloader.load ghcr.io/ralgar/pgloader:pr-1531 pgloader --with "quote identifiers" --with "data only" /db.sqlite3 postgresql://{{DB_USER}}:{{DB_PASS}}@{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}
```
5. Start Jellyseerr

### Building from source (ADVANCED):

#### Windows
Expand Down
38 changes: 38 additions & 0 deletions docker-compose.postgres.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
version: '3.8'
services:
jellyseerr:
build:
context: .
dockerfile: Dockerfile.local
ports:
- '5055:5055'
environment:
DB_TYPE: 'postgres' # Which DB engine to use. The default is "sqlite". To use postgres, this needs to be set to "postgres"
DB_HOST: 'postgres' # The host (url) of the database
DB_PORT: '5432' # The port to connect to
DB_USER: 'jellyseerr' # Username used to connect to the database
DB_PASS: 'jellyseerr' # Password of the user used to connect to the database
DB_NAME: 'jellyseerr' # The name of the database to connect to
DB_LOG_QUERIES: 'false' # Whether to log the DB queries for debugging
DB_USE_SSL: 'false' # Whether to enable ssl for database connection
volumes:
- .:/app:rw,cached
- /app/node_modules
- /app/.next
depends_on:
- postgres
links:
- postgres
postgres:
image: postgres
environment:
POSTGRES_USER: jellyseerr
POSTGRES_PASSWORD: jellyseerr
POSTGRES_DB: jellyseerr
ports:
- '5432:5432'
volumes:
- postgres:/var/lib/postgresql/data
volumes:
postgres:
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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",
Expand Down
97 changes: 89 additions & 8 deletions server/datasource.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,55 @@
import 'reflect-metadata';
import fs from 'fs';
import * as process from 'process';
import type { TlsOptions } from 'tls';
import type { DataSourceOptions, EntityTarget, Repository } from 'typeorm';
import { DataSource } from 'typeorm';

const DB_SSL_PREFIX = 'DB_SSL_';

function boolFromEnv(envVar: string, defaultVal = false) {
if (process.env[envVar]) {
return process.env[envVar]?.toLowerCase() === 'true';
}
return defaultVal;
}

function stringOrReadFileFromEnv(envVar: string): Buffer | string | undefined {
if (process.env[envVar]) {
return process.env[envVar];
}
const filePath = process.env[`${envVar}_FILE`];
if (filePath) {
return fs.readFileSync(filePath);
}
return undefined;
}

function buildSslConfig(): TlsOptions | undefined {
if (process.env.DB_USE_SSL?.toLowerCase() !== 'true') {
return undefined;
}
return {
rejectUnauthorized: boolFromEnv(
`${DB_SSL_PREFIX}REJECT_UNAUTHORIZED`,
true
),
ca: stringOrReadFileFromEnv(`${DB_SSL_PREFIX}CA`),
key: stringOrReadFileFromEnv(`${DB_SSL_PREFIX}KEY`),
cert: stringOrReadFileFromEnv(`${DB_SSL_PREFIX}CERT`),
};
}

const devConfig: DataSourceOptions = {
type: 'sqlite',
database: process.env.CONFIG_DIRECTORY
? `${process.env.CONFIG_DIRECTORY}/db/db.sqlite3`
: 'config/db/db.sqlite3',
synchronize: true,
migrationsRun: false,
logging: false,
logging: boolFromEnv('DB_LOG_QUERIES'),
enableWAL: true,
entities: ['server/entity/**/*.ts'],
migrations: ['server/migration/**/*.ts'],
migrations: ['server/migration/sqlite/**/*.ts'],
subscribers: ['server/subscriber/**/*.ts'],
};

Expand All @@ -23,16 +60,60 @@ const prodConfig: DataSourceOptions = {
: 'config/db/db.sqlite3',
synchronize: false,
migrationsRun: false,
logging: false,
logging: boolFromEnv('DB_LOG_QUERIES'),
enableWAL: true,
entities: ['dist/entity/**/*.js'],
migrations: ['dist/migration/**/*.js'],
migrations: ['dist/migration/sqlite/**/*.js'],
subscribers: ['dist/subscriber/**/*.js'],
};

const dataSource = new DataSource(
process.env.NODE_ENV !== 'production' ? devConfig : prodConfig
);
const postgresDevConfig: DataSourceOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT ?? '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME ?? 'jellyseerr',
ssl: buildSslConfig(),
synchronize: false,
migrationsRun: true,
logging: boolFromEnv('DB_LOG_QUERIES'),
entities: ['server/entity/**/*.ts'],
migrations: ['server/migration/postgres/**/*.ts'],
subscribers: ['server/subscriber/**/*.ts'],
};

const postgresProdConfig: DataSourceOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT ?? '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME ?? 'jellyseerr',
ssl: buildSslConfig(),
synchronize: false,
migrationsRun: false,
logging: boolFromEnv('DB_LOG_QUERIES'),
entities: ['dist/entity/**/*.js'],
migrations: ['dist/migration/postgres/**/*.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;
}
gauthier-th marked this conversation as resolved.
Show resolved Hide resolved

const dataSource = new DataSource(getDataSource());

export const getRepository = <Entity extends object>(
target: EntityTarget<Entity>
Expand Down
5 changes: 3 additions & 2 deletions server/entity/Media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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 { getHostname } from '@server/utils/getHostname';
import {
AfterLoad,
Expand Down Expand Up @@ -122,10 +123,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' })
Expand Down
6 changes: 4 additions & 2 deletions server/entity/MediaRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ export class MediaRequest {
@ManyToOne(() => Media, (media) => media.requests, {
eager: true,
onDelete: 'CASCADE',
nullable: false,
})
public media: Media;

Expand Down Expand Up @@ -848,7 +849,7 @@ export class MediaRequest {
const requestRepository = getRepository(MediaRequest);

this.status = MediaRequestStatus.FAILED;
requestRepository.save(this);
await requestRepository.save(this);

logger.warn(
'Something went wrong sending movie request to Radarr, marking status as FAILED',
Expand Down Expand Up @@ -1123,13 +1124,14 @@ export class MediaRequest {
media[this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug'] =
sonarrSeries.titleSlug;
media[this.is4k ? 'serviceId4k' : 'serviceId'] = sonarrSettings?.id;

await mediaRepository.save(media);
})
.catch(async () => {
const requestRepository = getRepository(MediaRequest);

this.status = MediaRequestStatus.FAILED;
requestRepository.save(this);
await requestRepository.save(this);

logger.warn(
'Something went wrong sending series request to Sonarr, marking status as FAILED',
Expand Down
5 changes: 4 additions & 1 deletion server/entity/Season.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ class Season {
@Column({ type: 'int', default: MediaStatus.UNKNOWN })
public status4k: MediaStatus;

@ManyToOne(() => Media, (media) => media.seasons, { onDelete: 'CASCADE' })
@ManyToOne(() => Media, (media) => media.seasons, {
onDelete: 'CASCADE',
nullable: false,
})
public media: Promise<Media>;

@CreateDateColumn()
Expand Down
1 change: 1 addition & 0 deletions server/entity/Watchlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class Watchlist implements WatchlistItem {
@ManyToOne(() => Media, (media) => media.watchlists, {
eager: true,
onDelete: 'CASCADE',
nullable: false,
})
public media: Media;

Expand Down
12 changes: 8 additions & 4 deletions server/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PlexAPI from '@server/api/plexapi';
import dataSource, { getRepository } from '@server/datasource';
import dataSource, { getRepository, isPgsql } from '@server/datasource';
import DiscoverSlider from '@server/entity/DiscoverSlider';
import { Session } from '@server/entity/Session';
import { User } from '@server/entity/User';
Expand Down Expand Up @@ -75,9 +75,13 @@ app

// 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');
if (isPgsql) {
await dbConnection.runMigrations();
} else {
await dbConnection.query('PRAGMA foreign_keys=OFF');
await dbConnection.runMigrations();
await dbConnection.query('PRAGMA foreign_keys=ON');
}
}

// Load Settings
Expand Down
Loading