import PlexAPI from '@server/api/plexapi' ;
import dataSource , { getRepository } from '@server/datasource' ;
import DiscoverSlider from '@server/entity/DiscoverSlider' ;
import { Session } from '@server/entity/Session' ;
import { User } from '@server/entity/User' ;
import { startJobs } from '@server/job/schedule' ;
import notificationManager from '@server/lib/notifications' ;
import DiscordAgent from '@server/lib/notifications/agents/discord' ;
import EmailAgent from '@server/lib/notifications/agents/email' ;
import GotifyAgent from '@server/lib/notifications/agents/gotify' ;
import LunaSeaAgent from '@server/lib/notifications/agents/lunasea' ;
import PushbulletAgent from '@server/lib/notifications/agents/pushbullet' ;
import PushoverAgent from '@server/lib/notifications/agents/pushover' ;
import SlackAgent from '@server/lib/notifications/agents/slack' ;
import TelegramAgent from '@server/lib/notifications/agents/telegram' ;
import WebhookAgent from '@server/lib/notifications/agents/webhook' ;
import WebPushAgent from '@server/lib/notifications/agents/webpush' ;
import { getSettings } from '@server/lib/settings' ;
import logger from '@server/logger' ;
import routes from '@server/routes' ;
import imageproxy from '@server/routes/imageproxy' ;
import { getAppVersion } from '@server/utils/appVersion' ;
import restartFlag from '@server/utils/restartFlag' ;
import { getClientIp } from '@supercharge/request-ip' ;
import { TypeormStore } from 'connect-typeorm/out' ;
import cookieParser from 'cookie-parser' ;
import csurf from 'csurf' ;
import type { NextFunction , Request , Response } from 'express' ;
import express from 'express' ;
import * as OpenApiValidator from 'express-openapi-validator' ;
import type { Store } from 'express-session' ;
import session from 'express-session' ;
import next from 'next' ;
import path from 'path' ;
import swaggerUi from 'swagger-ui-express' ;
import YAML from 'yamljs' ;
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 ( ) ;
const logMiddleware = ( req : Request , res : Response , next : NextFunction ) = > {
// Log information about the incoming request
console . log ( ` Request Method: ${ req . method } ` ) ;
console . log ( ` Request URL: ${ req . url } ` ) ;
console . log ( ` Request Headers: ${ JSON . stringify ( req . headers ) } ` ) ;
console . log ( ` Request Body: ${ JSON . stringify ( req . body ) } ` ) ;
// Continue processing the request
next ( ) ;
} ;
app
. prepare ( )
. then ( async ( ) = > {
const dbConnection = await dataSource . initialize ( ) ;
// 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 ( ) ;
restartFlag . initializeSettings ( settings . main ) ;
// Migrate library types
if (
settings . plex . libraries . length > 1 &&
! settings . plex . libraries [ 0 ] . type
) {
const userRepository = getRepository ( User ) ;
const admin = await userRepository . findOne ( {
select : { id : true , plexToken : true } ,
where : { id : 1 } ,
} ) ;
if ( admin ) {
logger . info ( 'Migrating Plex libraries to include media type' , {
label : 'Settings' ,
} ) ;
const plexapi = new PlexAPI ( { plexToken : admin.plexToken } ) ;
await plexapi . syncLibraries ( ) ;
}
}
// Register Notification Agents
notificationManager . registerAgents ( [
new DiscordAgent ( ) ,
new EmailAgent ( ) ,
feat(notif): add Gotify agent (#2196)
* feat(notifications): adds gotify notifications
adds new settings screen for gotify notifications including url, token and types settings
fix #2183
* feat(notif): add Gotify agent
addresses PR comments, runs i18n:extract
fix #2183
* reword validationTokenRequired
change wording to indicate presence, not validity
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
* feat(notifications): gotify notifications fix
applies changes from #2077 in which Yup validation was failing for types
fix #2183
* feat(notifications): adds gotify notifications
adds new settings screen for gotify notifications including url, token and types settings
fix #2183
* feat(notif): add Gotify agent
addresses PR comments, runs i18n:extract
fix #2183
* reword validationTokenRequired
change wording to indicate presence, not validity
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
* feat(notifications): gotify notifications fix
applies changes from #2077 in which Yup validation was failing for types
fix #2183
* feat(notifications): incorporate issue feature into gotify notifications
* feat(notifications): adds gotify notifications
adds new settings screen for gotify notifications including url, token and types settings
fix #2183
* feat(notif): add Gotify agent
addresses PR comments, runs i18n:extract
fix #2183
* reword validationTokenRequired
change wording to indicate presence, not validity
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
* feat: add missing ts field
include notifyAdmin in test notification endpoint
* feat: apply formatting/line break items
add addition line break before conditional, change ordering of notifyAdmin/notifyUser in test
endpoint
* feat: remove duplicated endpoints
during rebase, notification endpoints were duplicated upon rebasing. remove duplicate routes
* feat: correct linting quirks
* feat: formatting improvements
* feat(gotify): refactor axios post to leverage 'getNotificationPayload'
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
3 years ago
new GotifyAgent ( ) ,
new LunaSeaAgent ( ) ,
new PushbulletAgent ( ) ,
new PushoverAgent ( ) ,
new SlackAgent ( ) ,
new TelegramAgent ( ) ,
new WebhookAgent ( ) ,
new WebPushAgent ( ) ,
] ) ;
// Start Jobs
startJobs ( ) ;
// Bootstrap Discovery Sliders
await DiscoverSlider . bootstrapSliders ( ) ;
const server = express ( ) ;
server . use ( logMiddleware ) ;
if ( settings . main . trustProxy ) {
server . enable ( 'trust proxy' ) ;
}
server . use ( cookieParser ( ) ) ;
server . use ( express . json ( ) ) ;
server . use ( express . urlencoded ( { extended : true } ) ) ;
server . use ( ( req , _res , next ) = > {
try {
const descriptor = Object . getOwnPropertyDescriptor ( req , 'ip' ) ;
if ( descriptor ? . writable === true ) {
req . ip = getClientIp ( req ) ? ? '' ;
}
} catch ( e ) {
logger . error ( 'Failed to attach the ip to the request' , {
label : 'Middleware' ,
message : e.message ,
} ) ;
} finally {
next ( ) ;
}
} ) ;
if ( settings . main . csrfProtection ) {
server . use (
csurf ( {
cookie : {
httpOnly : true ,
sameSite : true ,
secure : ! dev ,
} ,
} )
) ;
server . use ( ( req , res , next ) = > {
res . cookie ( 'XSRF-TOKEN' , req . csrfToken ( ) , {
sameSite : true ,
secure : ! dev ,
} ) ;
next ( ) ;
} ) ;
}
// 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 ,
httpOnly : true ,
sameSite : true ,
secure : 'auto' ,
} ,
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 . use ( '/imageproxy' , imageproxy ) ;
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 ) ;
} ) ;