import config from 'config'; import path from 'path'; import express from 'express'; import boolParser from 'express-query-boolean'; import Router from 'express-promise-router'; import session from 'express-session'; import RedisStore from 'connect-redis'; import compression from 'compression'; import cookie from 'cookie'; import redis from '../redis.js'; import errorHandler from './error.js'; import consentHandler from './consent.js'; import initRestrictionHandler from './restrictions.js'; import { scenesRouter } from './scenes.js'; import { actorsRouter } from './actors.js'; import { fetchMoviesApi } from './movies.js'; import { fetchEntitiesApi } from './entities.js'; import { fetchTagsApi } from './tags.js'; import { verifyKey } from '../auth.js'; import { graphqlApi } from './graphql.js'; import mainHandler from './main.js'; import { setUserApi, loginApi, logoutApi, signupApi, fetchUserKeysApi, createKeyApi, removeUserKeyApi, flushUserKeysApi, } from './auth.js'; import { router as userRouter } from './users.js'; import { router as stashesRouter } from './stashes.js'; import { router as alertsRouter } from './alerts.js'; import { initCachesApi } from './system.js'; import initLogger from '../logger.js'; const logger = initLogger(); const isProduction = process.env.NODE_ENV === 'production'; export default async function initServer() { const app = express(); const router = Router(); const restrictionHandler = await initRestrictionHandler(); app.use(compression()); app.disable('x-powered-by'); app.set('view engine', 'ejs'); router.use(boolParser()); router.use('/', express.static('public')); router.use('/', express.static('static')); router.use('/media', express.static(config.media.path)); router.use((req, _res, next) => { if (req.headers.cookie) { const cookies = cookie.parse(req.headers.cookie); /* eslint-disable no-param-reassign */ req.cookies = cookies; req.tagFilter = cookies.tags ? JSON.parse(cookies.tags) : []; /* eslint-enable no-param-reassign */ } next(); }); router.use(express.json()); const redisStore = new RedisStore({ client: redis, prefix: 'traxxx:session:', }); router.use(session({ ...config.web.session, store: redisStore, })); router.use(setUserApi); // Vite integration if (isProduction) { app.enable('trust proxy'); // In production, we need to serve our static assets ourselves. // (In dev, Vite's middleware serves our static assets.) const sirv = (await import('sirv')).default; router.use(sirv('dist/client')); } else { // We instantiate Vite's development server and integrate its middleware to our server. // ⚠️ We instantiate it only in development. (It isn't needed in production and it // would unnecessarily bloat our production server.) const vite = await import('vite'); const viteDevMiddleware = ( await vite.createServer({ // root, server: { middlewareMode: true }, }) ).middlewares; router.use(viteDevMiddleware); } router.use(restrictionHandler); router.get('/consent', (_req, res) => { res.sendFile(path.join(import.meta.dirname, '../../assets/consent.html')); }); router.use('/api/*', async (req, _res, next) => { if (req.headers['api-user']) { await verifyKey(req.headers['api-user'], req.headers['api-key'], req); req.user = { // eslint-disable-line no-param-reassign id: Number(req.headers['api-user']), }; } next(); }); // SESSION router.post('/api/session', loginApi); router.delete('/api/session', logoutApi); // USERS router.post('/api/users', signupApi); // API KEYS router.get('/api/me/keys', fetchUserKeysApi); router.post('/api/me/keys', createKeyApi); router.delete('/api/me/keys/:keyIdentifier', removeUserKeyApi); router.delete('/api/me/keys', flushUserKeysApi); router.use(userRouter); router.use(stashesRouter); router.use(alertsRouter); router.use(scenesRouter); router.use(actorsRouter); // MOVIES router.get('/api/movies', fetchMoviesApi); // ENTITIES router.get('/api/entities', fetchEntitiesApi); // TAGS router.get('/api/tags', fetchTagsApi); router.post('/api/caches', initCachesApi); if (config.apiAccess.graphqlEnabled) { router.post('/graphql', graphqlApi); } router.use(consentHandler); router.use((_req, res, next) => { /* eslint-disable no-param-reassign */ res.set('Accept-CH', 'Sec-CH-Prefers-Color-Scheme'); res.set('Vary', 'Sec-CH-Prefers-Color-Scheme'); res.set('Critical-CH', 'Sec-CH-Prefers-Color-Scheme'); /* eslint-enable no-param-reassign */ next(); }); router.get('*', mainHandler); router.use(errorHandler); app.use(router); const port = process.env.PORT || config.web.port || 3000; // const port = Math.round(Math.random() * 10000); app.listen(port, config.web.host); logger.info(`Server running at http://${config.web.host}:${port}`); }