import express , { Request , Response , NextFunction } from 'express' ;
import next from 'next' ;
import path from 'path' ;
import { createConnection , getRepository } from 'typeorm' ;
import routes from './routes' ;
import bodyParser from 'body-parser' ;
import cookieParser from 'cookie-parser' ;
import session , { Store } from 'express-session' ;
import { TypeormStore } from 'connect-typeorm/out' ;
import YAML from 'yamljs' ;
import swaggerUi from 'swagger-ui-express' ;
import * as OpenApiValidator from 'express-openapi-validator' ;
import { Session } from './entity/Session' ;
import { getSettings } from './lib/settings' ;
import logger from './logger' ;
import { startJobs } from './job/schedule' ;
import notificationManager from './lib/notifications' ;
import DiscordAgent from './lib/notifications/agents/discord' ;
import EmailAgent from './lib/notifications/agents/email' ;
import { getAppVersion } from './utils/appVersion' ;
const API_SPEC_PATH = path . join ( __dirname , '../overseerr-api.yml' ) ;
logger . info ( ` Starting Overseerr version ${ getAppVersion ( ) } ` ) ;
const dev = process . env . NODE_ENV !== 'production' ;
const app = next ( { dev } ) ;
const handle = app . getRequestHandler ( ) ;
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' ) ;
}
// Load Settings
const settings = getSettings ( ) . load ( ) ;
// Register Notification Agents
notificationManager . registerAgents ( [ new DiscordAgent ( ) , new EmailAgent ( ) ] ) ;
// Start Jobs
startJobs ( ) ;
const server = express ( ) ;
server . use ( cookieParser ( ) ) ;
server . use ( bodyParser . json ( ) ) ;
server . use ( bodyParser . urlencoded ( { extended : true } ) ) ;
// Setup 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 apiDocs = YAML . load ( API_SPEC_PATH ) ;
server . use ( '/api-docs' , swaggerUi . serve , swaggerUi . setup ( apiDocs ) ) ;
server . use (
OpenApiValidator . middleware ( {
apiSpec : API_SPEC_PATH ,
validateRequests : true ,
} )
) ;
/ * *
* This is a workaround to convert dates to strings before they are validated by
* OpenAPI validator . Otherwise , they are treated as objects instead of strings
* and response validation will fail
* /
server . use ( ( _req , res , next ) = > {
const original = res . json ;
res . json = function jsonp ( json ) {
return original . call ( this , JSON . parse ( JSON . stringify ( json ) ) ) ;
} ;
next ( ) ;
} ) ;
server . use ( '/api/v1' , routes ) ;
server . get ( '*' , ( req , res ) = > handle ( req , res ) ) ;
server . use (
(
err : { status : number ; message : string ; errors : string [ ] } ,
_req : Request ,
res : Response ,
// We must provide a next function for the function signature here even though its not used
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_next : NextFunction
) = > {
// format error
res . status ( err . status || 500 ) . json ( {
message : err.message ,
errors : err.errors ,
} ) ;
}
) ;
const port = Number ( process . env . PORT ) || 5055 ;
const host = process . env . HOST ;
if ( host ) {
server . listen ( port , host , ( ) = > {
logger . info ( ` Server ready on ${ host } port ${ port } ` , {
label : 'Server' ,
} ) ;
} ) ;
} else {
server . listen ( port , ( ) = > {
logger . info ( ` Server ready on port ${ port } ` , {
label : 'Server' ,
} ) ;
} ) ;
}
} )
. catch ( ( err ) = > {
logger . error ( err . stack ) ;
process . exit ( 1 ) ;
} ) ;