Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"preinstall": "npx only-allow pnpm",
"postinstall": "next telemetry disable",
"dev": "nodemon -e ts,json,yml --watch server --watch seerr-api.yml --exec 'ts-node -r tsconfig-paths/register --files --project server/tsconfig.json server/index.ts'",
"build:server": "tsc --project server/tsconfig.json && copyfiles -u 2 server/templates/**/*.{html,pug} dist/templates && copyfiles -u 2 \"server/i18n/locale/*.json\" dist/i18n && tsc-alias -p server/tsconfig.json",
"build:server": "tsc --project server/tsconfig.build.json && copyfiles -u 2 server/templates/**/*.{html,pug} dist/templates && copyfiles -u 2 \"server/i18n/locale/*.json\" dist/i18n && tsc-alias -p server/tsconfig.build.json",
"build:next": "next build",
"build": "pnpm build:next && pnpm build:server",
"lint": "eslint \"./server/**/*.{ts,tsx}\" \"./src/**/*.{ts,tsx}\" --cache",
Expand Down
62 changes: 52 additions & 10 deletions server/datasource.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,52 @@
import { Blocklist } from '@server/entity/Blocklist';
import DiscoverSlider from '@server/entity/DiscoverSlider';
import Issue from '@server/entity/Issue';
import IssueComment from '@server/entity/IssueComment';
import Media from '@server/entity/Media';
import { MediaRequest } from '@server/entity/MediaRequest';
import OverrideRule from '@server/entity/OverrideRule';
import Season from '@server/entity/Season';
import SeasonRequest from '@server/entity/SeasonRequest';
import { Session } from '@server/entity/Session';
import { User } from '@server/entity/User';
import { UserPushSubscription } from '@server/entity/UserPushSubscription';
import { UserSettings } from '@server/entity/UserSettings';
import { Watchlist } from '@server/entity/Watchlist';
import { IssueCommentSubscriber } from '@server/subscriber/IssueCommentSubscriber';
import { IssueSubscriber } from '@server/subscriber/IssueSubscriber';
import { MediaRequestSubscriber } from '@server/subscriber/MediaRequestSubscriber';
import { MediaSubscriber } from '@server/subscriber/MediaSubscriber';
import fs from 'fs';
import type { TlsOptions } from 'tls';
import type { DataSourceOptions, EntityTarget, Repository } from 'typeorm';
import { DataSource } from 'typeorm';

const DB_SSL_PREFIX = 'DB_SSL_';

const entities = [
Blocklist,
DiscoverSlider,
Issue,
IssueComment,
Media,
MediaRequest,
OverrideRule,
Season,
SeasonRequest,
Session,
User,
UserPushSubscription,
UserSettings,
Watchlist,
];

const subscribers = [
IssueCommentSubscriber,
IssueSubscriber,
MediaRequestSubscriber,
MediaSubscriber,
];

function boolFromEnv(envVar: string, defaultVal = false) {
if (process.env[envVar]) {
return process.env[envVar]?.toLowerCase() === 'true';
Expand Down Expand Up @@ -53,9 +95,9 @@ const testConfig: DataSourceOptions = {
synchronize: true,
dropSchema: true,
logging: boolFromEnv('DB_LOG_QUERIES'),
entities: ['server/entity/**/*.ts'],
entities,
migrations: ['server/migration/sqlite/**/*.ts'],
subscribers: ['server/subscriber/**/*.ts'],
subscribers,
};

const devConfig: DataSourceOptions = {
Expand All @@ -67,9 +109,9 @@ const devConfig: DataSourceOptions = {
migrationsRun: false,
logging: boolFromEnv('DB_LOG_QUERIES'),
enableWAL: true,
entities: ['server/entity/**/*.ts'],
entities,
migrations: ['server/migration/sqlite/**/*.ts'],
subscribers: ['server/subscriber/**/*.ts'],
subscribers,
};

const prodConfig: DataSourceOptions = {
Expand All @@ -81,9 +123,9 @@ const prodConfig: DataSourceOptions = {
migrationsRun: false,
logging: boolFromEnv('DB_LOG_QUERIES'),
enableWAL: true,
entities: ['dist/entity/**/*.js'],
entities,
migrations: ['dist/migration/sqlite/**/*.js'],
subscribers: ['dist/subscriber/**/*.js'],
subscribers,
};

const postgresDevConfig: DataSourceOptions = {
Expand All @@ -100,9 +142,9 @@ const postgresDevConfig: DataSourceOptions = {
synchronize: false,
migrationsRun: true,
logging: boolFromEnv('DB_LOG_QUERIES'),
entities: ['server/entity/**/*.ts'],
entities,
migrations: ['server/migration/postgres/**/*.ts'],
subscribers: ['server/subscriber/**/*.ts'],
subscribers,
};

const postgresProdConfig: DataSourceOptions = {
Expand All @@ -119,9 +161,9 @@ const postgresProdConfig: DataSourceOptions = {
synchronize: false,
migrationsRun: false,
logging: boolFromEnv('DB_LOG_QUERIES'),
entities: ['dist/entity/**/*.js'],
entities,
migrations: ['dist/migration/postgres/**/*.js'],
subscribers: ['dist/subscriber/**/*.js'],
subscribers,
};

export const isPgsql = process.env.DB_TYPE === 'postgres';
Expand Down
1 change: 1 addition & 0 deletions server/scripts/prepareTestDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const prepareDb = async () => {
await seedTestDb({
preserveDb: process.env.PRESERVE_DB === 'true',
withMigrations: process.env.WITH_MIGRATIONS === 'true',
allowOutsideTest: true,
});
};

Expand Down
4 changes: 4 additions & 0 deletions server/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["**/*.test.ts"]
}
29 changes: 29 additions & 0 deletions server/utils/seedTestDb.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import assert from 'node:assert/strict';
import { afterEach, describe, it } from 'node:test';

import { resetTestDb, seedTestDb } from '@server/utils/seedTestDb';

// NODE_ENV is typed readonly, so it is swapped through the whole env object
function setNodeEnv(value: string | undefined) {
Object.assign(process.env, { NODE_ENV: value });
}

describe('test database guard', () => {
const originalNodeEnv = process.env.NODE_ENV;

afterEach(() => {
setNodeEnv(originalNodeEnv);
});

it('refuses to seed when NODE_ENV is not test', async () => {
setNodeEnv('production');

await assert.rejects(() => seedTestDb(), /Refusing to seed/);
});

it('refuses to reset when NODE_ENV is not test', async () => {
setNodeEnv('production');

await assert.rejects(() => resetTestDb(), /Refusing to reset/);
});
});
16 changes: 16 additions & 0 deletions server/utils/seedTestDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@ export interface SeedDbOptions {
preserveDb?: boolean;
/** If true, runs migrations instead of synchronizing schema */
withMigrations?: boolean;
/** If true, permits seeding while NODE_ENV is not test */
allowOutsideTest?: boolean;
}

// Precomputed bcrypt hash of 'test1234'. We precompute this to avoid
// having to hash the password every time we seed the database.
const TEST_USER_PASSWORD_HASH =
'$2b$12$Z5V2P5HZgmx4/AnWFMZN1.aD5AM1NucNi.mhNTSQ9oVtmdzu7Le/a';

function assertTestDatabase(operation: string, allowOutsideTest = false): void {
if (allowOutsideTest || process.env.NODE_ENV === 'test') {
return;
}

throw new Error(
`Refusing to ${operation} while NODE_ENV is not test: this drops every table and seeds accounts with a known password.`
);
}

/**
* Seeds test users into the database.
* Assumes the database schema is already set up.
Expand Down Expand Up @@ -68,6 +80,8 @@ async function seedTestUsers(): Promise<void> {
* Used by both Cypress tests and Vitest unit tests.
*/
export async function seedTestDb(options: SeedDbOptions = {}): Promise<void> {
assertTestDatabase('seed the test database', options.allowOutsideTest);

const dbConnection = dataSource.isInitialized
? dataSource
: await dataSource.initialize();
Expand All @@ -91,6 +105,8 @@ export async function seedTestDb(options: SeedDbOptions = {}): Promise<void> {
* Assumes DB has been initialized.
*/
export async function resetTestDb(): Promise<void> {
assertTestDatabase('reset the test database');

await dataSource.synchronize(true);
await seedTestUsers();
}
3 changes: 2 additions & 1 deletion src/components/Settings/SettingsNetwork/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ const SettingsNetwork = () => {
apiRequestTimeout: Number(values.apiRequestTimeout) * 1000,
});
mutate('/api/v1/settings/public');
mutate('/api/v1/status');
// the key StatusChecker polls on, so the restart modal shows at once
mutate('/api/v1/status?checkUpdateAvailable=false');

addToast(intl.formatMessage(messages.toastSettingsSuccess), {
autoDismiss: true,
Expand Down
Loading