From 5399a299b2f54e1ae664be9215b68e1776aab1d5 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 28 Apr 2024 18:03:24 +0000 Subject: [PATCH] refactor: moved to using znv for env and moved logger to infra --- .../znv-npm-0.4.0-5eeccddb6c-7dac62d9b8.zip | Bin 0 -> 36453 bytes pkg/api/package.json | 3 +- pkg/api/src/api/index.ts | 7 +- pkg/api/src/api/middleware/auth.ts | 2 +- pkg/api/src/api/middleware/cors.ts | 14 +- pkg/api/src/api/middleware/logging.ts | 2 +- pkg/api/src/api/routes/api.ts | 2 - pkg/api/src/api/routes/config.ts | 2 +- pkg/api/src/api/routes/discovery.ts | 2 +- pkg/api/src/api/routes/filter.ts | 4 +- pkg/api/src/api/routes/history.ts | 6 +- pkg/api/src/api/routes/issue.ts | 7 +- pkg/api/src/api/routes/log.ts | 40 ------ pkg/api/src/api/routes/login.ts | 3 +- pkg/api/src/api/routes/mail.ts | 2 +- pkg/api/src/api/routes/plex.ts | 19 ++- pkg/api/src/api/routes/profiles.ts | 5 +- pkg/api/src/api/routes/request.ts | 2 +- pkg/api/src/api/routes/review.ts | 2 +- pkg/api/src/api/routes/search.ts | 2 +- pkg/api/src/api/routes/services.ts | 130 ++++++++---------- pkg/api/src/api/routes/sessions.ts | 2 +- pkg/api/src/api/routes/setup.ts | 6 +- pkg/api/src/api/routes/user.ts | 15 +- pkg/api/src/api/routes/web.ts | 9 +- pkg/api/src/app.ts | 12 +- pkg/api/src/config/config.ts | 12 +- pkg/api/src/config/env/app.ts | 9 -- pkg/api/src/config/env/external.ts | 11 -- pkg/api/src/config/env/http.ts | 14 -- pkg/api/src/config/env/node.ts | 18 --- pkg/api/src/config/env/paths.ts | 26 ---- pkg/api/src/config/env/views.ts | 12 -- pkg/api/src/config/migration.ts | 9 +- pkg/api/src/infra/config/env.ts | 65 +++++++++ pkg/api/src/infra/fanart/api.ts | 4 +- .../src/{loaders => infra/logger}/logger.ts | 7 +- pkg/api/src/infra/tmdb/tmdb.ts | 6 +- pkg/api/src/infra/worker/master.ts | 23 ++-- pkg/api/src/loaders/index.ts | 14 +- pkg/api/src/loaders/mongoose.ts | 3 +- pkg/api/src/models/user.ts | 10 +- pkg/api/src/services/cron/service.ts | 2 +- pkg/api/src/services/discovery/build.ts | 2 +- pkg/api/src/services/discovery/display.ts | 12 +- pkg/api/src/services/discovery/index.ts | 2 +- pkg/api/src/services/downloaders/radarr.ts | 2 +- pkg/api/src/services/downloaders/sonarr.ts | 2 +- pkg/api/src/services/fanart/index.ts | 2 +- pkg/api/src/services/mail/mailer.ts | 2 +- pkg/api/src/services/meta/imdb.ts | 6 +- pkg/api/src/services/notifications/discord.ts | 2 +- .../services/notifications/notification.ts | 3 +- .../src/services/notifications/telegram.ts | 2 +- pkg/api/src/services/plex/bandwidth.ts | 2 +- pkg/api/src/services/plex/history.ts | 2 +- pkg/api/src/services/plex/library.ts | 10 +- pkg/api/src/services/plex/top.ts | 2 +- pkg/api/src/services/requests/display.ts | 101 ++++++++++---- pkg/api/src/services/requests/filter.ts | 2 +- pkg/api/src/services/requests/process.ts | 2 +- pkg/api/src/services/requests/quotas.ts | 2 +- pkg/api/src/services/setup/setup.ts | 13 +- pkg/api/src/services/tmdb/movie.ts | 8 +- pkg/api/src/services/tmdb/person.ts | 12 +- pkg/api/src/services/tmdb/search.ts | 12 +- pkg/api/src/services/tmdb/show.ts | 20 +-- pkg/api/src/services/tmdb/trending.ts | 7 +- pkg/api/src/types/node.d.ts | 3 - pkg/api/src/utils/startupMessage.ts | 7 +- yarn.lock | 14 +- 71 files changed, 393 insertions(+), 408 deletions(-) create mode 100644 .yarn/cache/znv-npm-0.4.0-5eeccddb6c-7dac62d9b8.zip delete mode 100644 pkg/api/src/api/routes/log.ts delete mode 100644 pkg/api/src/config/env/app.ts delete mode 100644 pkg/api/src/config/env/external.ts delete mode 100644 pkg/api/src/config/env/http.ts delete mode 100644 pkg/api/src/config/env/node.ts delete mode 100644 pkg/api/src/config/env/paths.ts delete mode 100644 pkg/api/src/config/env/views.ts create mode 100644 pkg/api/src/infra/config/env.ts rename pkg/api/src/{loaders => infra/logger}/logger.ts (93%) delete mode 100644 pkg/api/src/types/node.d.ts diff --git a/.yarn/cache/znv-npm-0.4.0-5eeccddb6c-7dac62d9b8.zip b/.yarn/cache/znv-npm-0.4.0-5eeccddb6c-7dac62d9b8.zip new file mode 100644 index 0000000000000000000000000000000000000000..70f6cf5bd4cf8761c13a3cf0b02b7dcf65f0d5d2 GIT binary patch literal 36453 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 literal 0 HcmV?d00001 diff --git a/pkg/api/package.json b/pkg/api/package.json index 4b5ca6148..ba0b8e15d 100644 --- a/pkg/api/package.json +++ b/pkg/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "9.9.9", + "version": "0.6.0", "description": "Petio API", "main": "./src/app.ts", "scripts": { @@ -74,6 +74,7 @@ "xhr-request": "^1.1.0", "xml-js": "^1.6.11", "yargs-parser": "^21.1.1", + "znv": "^0.4.0", "zod": "^3.22.4", "zod-validation-error": "^3.2.0" }, diff --git a/pkg/api/src/api/index.ts b/pkg/api/src/api/index.ts index de9f7d2a2..cb2f96504 100644 --- a/pkg/api/src/api/index.ts +++ b/pkg/api/src/api/index.ts @@ -8,12 +8,13 @@ import errorHandler from '@/api/middleware/errorHandling'; import api from '@/api/routes/api'; import web from '@/api/routes/web'; import { config } from '@/config/index'; -import logger from "@/loaders/logger"; +import logger from '@/infra/logger/logger'; import listen from '@/utils/http'; import { removeSlashes } from '@/utils/urls'; -import responseHandler from "./http/responseHandler"; -import options from "./middleware/options"; + +import responseHandler from './http/responseHandler'; import logging from './middleware/logging'; +import options from './middleware/options'; const routes = (subpath: string): Koa => { const app = new Koa(); diff --git a/pkg/api/src/api/middleware/auth.ts b/pkg/api/src/api/middleware/auth.ts index b86631eae..9eb35f08f 100644 --- a/pkg/api/src/api/middleware/auth.ts +++ b/pkg/api/src/api/middleware/auth.ts @@ -2,7 +2,7 @@ import * as HttpStatus from 'http-status-codes'; import jwt from 'jsonwebtoken'; import { Context, Next } from 'koa'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import { UserModel } from '@/models/user'; const logger = loggerMain.child({ module: 'middleware.auth' }); diff --git a/pkg/api/src/api/middleware/cors.ts b/pkg/api/src/api/middleware/cors.ts index 47324754e..7a5888b97 100644 --- a/pkg/api/src/api/middleware/cors.ts +++ b/pkg/api/src/api/middleware/cors.ts @@ -1,26 +1,18 @@ import Cors from '@koa/cors'; import Koa from 'koa'; -import httpConfig from "@/config/env/http"; -import envConfig from "@/config/env/node"; +import { HTTP_CORS_DOMAINS } from '@/infra/config/env'; export default () => { - // Enable cors - const whitelist = httpConfig.corsDomains; - if (envConfig.isDevelopment) { - // add local react dev - whitelist.push('http://localhost:3000'); // frontend - } - const corsOptions = { origin: async (ctx: Koa.Context): Promise => { if ( ctx.request.header.origin && - whitelist.indexOf(ctx.request.header.origin) !== -1 + HTTP_CORS_DOMAINS.indexOf(ctx.request.header.origin) !== -1 ) { return ctx.request.header.origin; } - return 'http://localhost:7777'; + return 'http://localhost:7777'; }, credentials: true, }; diff --git a/pkg/api/src/api/middleware/logging.ts b/pkg/api/src/api/middleware/logging.ts index 05fab95ee..d5e76bb1d 100644 --- a/pkg/api/src/api/middleware/logging.ts +++ b/pkg/api/src/api/middleware/logging.ts @@ -1,7 +1,7 @@ import { Context, Next } from 'koa'; import PinoHttp from 'pino-http'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; const httpLogger = PinoHttp({ logger: logger.core(), diff --git a/pkg/api/src/api/routes/api.ts b/pkg/api/src/api/routes/api.ts index dac4d620a..7ad06e907 100644 --- a/pkg/api/src/api/routes/api.ts +++ b/pkg/api/src/api/routes/api.ts @@ -10,7 +10,6 @@ import filter from '@/api/routes/filter'; import health from '@/api/routes/health'; import history from '@/api/routes/history'; import issue from '@/api/routes/issue'; -import log from '@/api/routes/log'; import login from '@/api/routes/login'; import mail from '@/api/routes/mail'; import movie from '@/api/routes/movie'; @@ -67,7 +66,6 @@ export default (app: Koa, subpath: string) => { filter(api); history(api); issue(api); - log(api); login(api); mail(api); movie(api); diff --git a/pkg/api/src/api/routes/config.ts b/pkg/api/src/api/routes/config.ts index e5008be6b..6ef7fc0c3 100644 --- a/pkg/api/src/api/routes/config.ts +++ b/pkg/api/src/api/routes/config.ts @@ -6,7 +6,7 @@ import * as z from 'zod'; import { validateRequest } from '@/api/middleware/validation'; import { HasConfig, WriteConfig } from '@/config/config'; import { config } from '@/config/index'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import setupReady from '@/services/setup/setup'; const getConfig = async (ctx: Context) => { diff --git a/pkg/api/src/api/routes/discovery.ts b/pkg/api/src/api/routes/discovery.ts index 478ef5af4..21966315e 100644 --- a/pkg/api/src/api/routes/discovery.ts +++ b/pkg/api/src/api/routes/discovery.ts @@ -2,7 +2,7 @@ import Router from '@koa/router'; import { Context } from 'koa'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import { UserModel } from '@/models/user'; import cache from '@/services/cache/cache'; import { DiscoveryResult } from '@/services/discovery/display'; diff --git a/pkg/api/src/api/routes/filter.ts b/pkg/api/src/api/routes/filter.ts index 55373dd70..94b338a3c 100644 --- a/pkg/api/src/api/routes/filter.ts +++ b/pkg/api/src/api/routes/filter.ts @@ -2,7 +2,7 @@ import Router from '@koa/router'; import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import Filter from '@/models/filter'; const getFilters = async (ctx: Context) => { @@ -80,4 +80,4 @@ export default (app: Router) => { route.post('/update', updateFilter); app.use(route.routes()); -}; \ No newline at end of file +}; diff --git a/pkg/api/src/api/routes/history.ts b/pkg/api/src/api/routes/history.ts index 0c5208eb6..b28e51afb 100644 --- a/pkg/api/src/api/routes/history.ts +++ b/pkg/api/src/api/routes/history.ts @@ -2,13 +2,13 @@ import Router from '@koa/router'; import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import getBandwidth from '@/services/plex/bandwidth'; import getHistory from '@/services/plex/history'; import getServerInfo from '@/services/plex/serverInfo'; const listHistory = async (ctx: Context) => { - let {id} = ctx.request.body; + let { id } = ctx.request.body; if (id === 'admin') id = 1; try { const data = await getHistory(id, ctx.request.body.type); @@ -52,4 +52,4 @@ export default (app: Router) => { route.get('/bandwidth', collectBandwidth); app.use(route.routes()); -}; \ No newline at end of file +}; diff --git a/pkg/api/src/api/routes/issue.ts b/pkg/api/src/api/routes/issue.ts index f0dfbd711..a5bf39aee 100644 --- a/pkg/api/src/api/routes/issue.ts +++ b/pkg/api/src/api/routes/issue.ts @@ -2,7 +2,7 @@ import Router from '@koa/router'; import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import Issue from '@/models/issue'; import { UserModel } from '@/models/user'; import Mailer from '@/services/mail/mailer'; @@ -14,7 +14,6 @@ const listAllIssues = async (ctx: Context) => { ctx.body = await Issue.find(); }; - async function mailIssue(user_id, media_id, type, title) { const userData = await UserModel.findOne({ id: user_id }); let media: any = false; @@ -80,7 +79,7 @@ async function mailIssueResolve(user_id, media_id, type, title, message) { const deleteIssues = async (ctx: Context) => { const body = ctx.request.body as any; const issueId = body.id; - const {message} = body; + const { message } = body; try { const issue = await Issue.findById(issueId); @@ -147,4 +146,4 @@ export default (app: Router) => { route.post('/remove', deleteIssues); app.use(route.routes()); -}; \ No newline at end of file +}; diff --git a/pkg/api/src/api/routes/log.ts b/pkg/api/src/api/routes/log.ts deleted file mode 100644 index d9e92bffa..000000000 --- a/pkg/api/src/api/routes/log.ts +++ /dev/null @@ -1,40 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import Router from '@koa/router'; -import { StatusCodes } from 'http-status-codes'; -import { Context } from 'koa'; - -import { adminRequired } from '@/api/middleware/auth'; -import pathsConfig from "@/config/env/paths"; - -const liveLogfile = path.join(pathsConfig.dataDir, './logs/live1.log'); -const liveLogfile2 = path.join(pathsConfig.dataDir, './logs/live.log'); - -const streamLog = async (ctx: Context) => { - let dataNew; let dataOld; - try { - const logsNew = fs.readFileSync(liveLogfile, 'utf8'); - dataNew = JSON.parse(`[${logsNew.replace(/,\s*$/, '')}]`); - } catch { - dataNew = []; - } - - try { - const logsOld = fs.readFileSync(liveLogfile2, 'utf8'); - dataOld = JSON.parse(`[${logsOld.replace(/,\s*$/, '')}]`); - } catch { - dataOld = []; - } - - const data = [...dataNew, ...dataOld]; - - ctx.status = StatusCodes.OK; - ctx.body = data; -}; - -const route = new Router({ prefix: '/logs' }); -export default (app: Router) => { - route.get('/stream', adminRequired, streamLog); - - app.use(route.routes()); -}; diff --git a/pkg/api/src/api/routes/login.ts b/pkg/api/src/api/routes/login.ts index e8d4278f1..a24ea397b 100644 --- a/pkg/api/src/api/routes/login.ts +++ b/pkg/api/src/api/routes/login.ts @@ -9,7 +9,7 @@ import xmlParser from 'xml-js'; import { authenticate } from '@/api/middleware/auth'; import { config } from '@/config/index'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import { GetUserByPlexID, UserModel, UserRole } from '@/models/user'; const logger = loggerMain.child({ module: 'routes.login' }); @@ -71,7 +71,6 @@ const attemptPlexAuth = async (ctx: Context) => { const userId = await plexOauth(token); const dbUser = await GetUserByPlexID(userId); - if (!dbUser) { ctx.error({ statusCode: StatusCodes.FORBIDDEN, diff --git a/pkg/api/src/api/routes/mail.ts b/pkg/api/src/api/routes/mail.ts index 294f39084..236e27a4e 100644 --- a/pkg/api/src/api/routes/mail.ts +++ b/pkg/api/src/api/routes/mail.ts @@ -6,7 +6,7 @@ import { z } from 'zod'; import { validateRequest } from '@/api/middleware/validation'; import { WriteConfig } from '@/config/config'; import { config } from '@/config/index'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import Mailer from '@/services/mail/mailer'; const createMail = async (ctx: Context) => { diff --git a/pkg/api/src/api/routes/plex.ts b/pkg/api/src/api/routes/plex.ts index 1366f0cd5..2fbed3627 100644 --- a/pkg/api/src/api/routes/plex.ts +++ b/pkg/api/src/api/routes/plex.ts @@ -5,14 +5,14 @@ import { Context } from 'koa'; import { WriteConfig } from '@/config/config'; import { config } from '@/config/index'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import { UserModel } from '@/models/user'; import plexLookup from '@/services/plex/lookup'; import MakePlexURL from '@/services/plex/util'; const lookupByIdAndType = async (ctx: Context) => { - const {type} = ctx.params; - const {id} = ctx.params; + const { type } = ctx.params; + const { id } = ctx.params; if (!type || !id || type === 'undefined' || type === 'undefined') { ctx.status = StatusCodes.BAD_REQUEST; ctx.body = { error: 'invalid fields provided' }; @@ -63,13 +63,12 @@ const testPlexConnection = async (ctx: Context) => { }; return; } - ctx.status = StatusCodes.OK; - ctx.body = { - connection: false, - error: 'You are not the owner of this server', - }; - return; - + ctx.status = StatusCodes.OK; + ctx.body = { + connection: false, + error: 'You are not the owner of this server', + }; + return; } catch (err) { logger.log({ level: 'error', message: err }); diff --git a/pkg/api/src/api/routes/profiles.ts b/pkg/api/src/api/routes/profiles.ts index 9e875fe6e..99c1ea24a 100644 --- a/pkg/api/src/api/routes/profiles.ts +++ b/pkg/api/src/api/routes/profiles.ts @@ -3,7 +3,7 @@ import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; import { adminRequired } from '@/api/middleware/auth'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import Profile from '@/models/profile'; import { UserModel } from '@/models/user'; @@ -22,7 +22,6 @@ const listProfiles = async (ctx: Context) => { } catch (err) { ctx.status = StatusCodes.INTERNAL_SERVER_ERROR; ctx.body = { error: err }; - } }; @@ -161,4 +160,4 @@ export default (app: Router) => { route.post('/delete_profile', adminRequired, deleteProfile); app.use(route.routes()); -}; \ No newline at end of file +}; diff --git a/pkg/api/src/api/routes/request.ts b/pkg/api/src/api/routes/request.ts index a7a8dff8b..5f53c4558 100644 --- a/pkg/api/src/api/routes/request.ts +++ b/pkg/api/src/api/routes/request.ts @@ -3,7 +3,7 @@ import Bluebird from 'bluebird'; import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import { GetAllDownloaders, IDownloader } from '@/models/downloaders'; import Request from '@/models/request'; import { UserModel } from '@/models/user'; diff --git a/pkg/api/src/api/routes/review.ts b/pkg/api/src/api/routes/review.ts index 12069f3a0..4a62c865c 100644 --- a/pkg/api/src/api/routes/review.ts +++ b/pkg/api/src/api/routes/review.ts @@ -2,7 +2,7 @@ import Router from '@koa/router'; import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import Review from '@/models/review'; import { UserModel } from '@/models/user'; diff --git a/pkg/api/src/api/routes/search.ts b/pkg/api/src/api/routes/search.ts index 8a22af338..24a23ba5d 100644 --- a/pkg/api/src/api/routes/search.ts +++ b/pkg/api/src/api/routes/search.ts @@ -2,7 +2,7 @@ import Router from '@koa/router'; import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import search from '@/services/tmdb/search'; const searchByTerm = async (ctx: Context) => { diff --git a/pkg/api/src/api/routes/services.ts b/pkg/api/src/api/routes/services.ts index 3cb297449..939cf66fe 100644 --- a/pkg/api/src/api/routes/services.ts +++ b/pkg/api/src/api/routes/services.ts @@ -4,28 +4,31 @@ import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; import { z } from 'zod'; -import { StatusBadRequest, StatusInternalServerError } from '@/api/http/request'; +import { + StatusBadRequest, + StatusInternalServerError, +} from '@/api/http/request'; import { adminRequired } from '@/api/middleware/auth'; import { validateRequest } from '@/api/middleware/validation'; import { ArrError } from '@/infra/arr/error'; import RadarrAPI, { GetRadarrInstanceFromDb } from '@/infra/arr/radarr'; import SonarrAPI, { GetSonarrInstanceFromDb } from '@/infra/arr/sonarr'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import { CreateOrUpdateDownloader, DeleteDownloaderById, DownloaderType, GetAllDownloaders, } from '@/models/downloaders'; -import { ArrInput, ArrInputSchema } from "@/schemas/downloaders"; +import { ArrInput, ArrInputSchema } from '@/schemas/downloaders'; const getSonarrOptionsById = async (ctx: Context) => { const instance = await GetSonarrInstanceFromDb(ctx.params.id); if (!instance) { ctx.error({ statusCode: StatusCodes.BAD_REQUEST, - code: "INVALID_INSTANCE", - message: `no instance was found with the id: ${ctx.params.id}` + code: 'INVALID_INSTANCE', + message: `no instance was found with the id: ${ctx.params.id}`, }); return; } @@ -44,7 +47,7 @@ const getSonarrOptionsById = async (ctx: Context) => { profiles, languages, tags, - } + }, }); }; @@ -213,13 +216,7 @@ const updateSonarrConfig = async (ctx: Context) => { } const url = new URL( - `${instance.protocol - }://${ - instance.host - }:${ - instance.port - }/${ - instance.subpath}`, + `${instance.protocol}://${instance.host}:${instance.port}/${instance.subpath}`, ); const api = new SonarrAPI(url, instance.token); @@ -283,7 +280,7 @@ const deleteSonarrById = async (ctx: Context) => { const deleted = await DeleteDownloaderById(ctx.params.id); if (!deleted) { ctx.status = StatusCodes.NOT_FOUND; - ctx.body = `failed to delete instance with the id: ${ ctx.params.id}`; + ctx.body = `failed to delete instance with the id: ${ctx.params.id}`; return; } @@ -526,61 +523,55 @@ const updateRadarrConfig = async (ctx: Context) => { const instance = ctx.request.body as ArrInput; try { - if (instance.subpath.startsWith('/')) { - instance.subpath = instance.subpath.substring(1); - } - - const url = new URL( - `${instance.protocol - }://${ - instance.host - }:${ - instance.port - }/${ - instance.subpath}`, - ); - - const api = new RadarrAPI(url, instance.token); - await api.TestConnection(); - - const newInstance = await CreateOrUpdateDownloader({ - name: instance.name, - type: DownloaderType.Radarr, - url: url.toString(), - token: instance.token, - version: api.GetVersion().toString(), - path: instance.path, - profile: instance.profile, - language: instance.language, - availability: instance.availability, - enabled: instance.enabled, - }); - - const [paths, profiles, languages, availabilities] = await Promise.all([ - api.GetRootPaths(), - api.GetQualityProfiles(), - api.GetLanguages(), - api.GetMinimumAvailability(), - ]); + if (instance.subpath.startsWith('/')) { + instance.subpath = instance.subpath.substring(1); + } + + const url = new URL( + `${instance.protocol}://${instance.host}:${instance.port}/${instance.subpath}`, + ); + + const api = new RadarrAPI(url, instance.token); + await api.TestConnection(); + + const newInstance = await CreateOrUpdateDownloader({ + name: instance.name, + type: DownloaderType.Radarr, + url: url.toString(), + token: instance.token, + version: api.GetVersion().toString(), + path: instance.path, + profile: instance.profile, + language: instance.language, + availability: instance.availability, + enabled: instance.enabled, + }); + + const [paths, profiles, languages, availabilities] = await Promise.all([ + api.GetRootPaths(), + api.GetQualityProfiles(), + api.GetLanguages(), + api.GetMinimumAvailability(), + ]); - const result = { - id: newInstance.id, - name: instance.name, - protocol: instance.protocol, - host: instance.host, - port: instance.port, - subpath: instance.subpath === '' ? '/' : instance.subpath, - token: instance.token, - path: instance.path, - profile: instance.profile, - language: instance.language, - availability: instance.availability, - enabled: instance.enabled, - paths, - profiles, - languages, - availabilities, - }; + const result = { + id: newInstance.id, + name: instance.name, + protocol: instance.protocol, + host: instance.host, + port: instance.port, + subpath: instance.subpath === '' ? '/' : instance.subpath, + token: instance.token, + path: instance.path, + profile: instance.profile, + language: instance.language, + availability: instance.availability, + enabled: instance.enabled, + paths, + profiles, + languages, + availabilities, + }; ctx.status = StatusCodes.OK; ctx.body = result; @@ -601,7 +592,7 @@ const deleteRadarrById = async (ctx: Context) => { const deleted = await DeleteDownloaderById(ctx.params.id); if (!deleted) { ctx.status = StatusCodes.NOT_FOUND; - ctx.body = `failed to delete instance with the id: ${ ctx.params.id}`; + ctx.body = `failed to delete instance with the id: ${ctx.params.id}`; return; } @@ -618,7 +609,6 @@ const deleteRadarrById = async (ctx: Context) => { } }; - const route = new Router({ prefix: '/services' }); export default (app: Router) => { route.get( diff --git a/pkg/api/src/api/routes/sessions.ts b/pkg/api/src/api/routes/sessions.ts index 80178104b..d4738f800 100644 --- a/pkg/api/src/api/routes/sessions.ts +++ b/pkg/api/src/api/routes/sessions.ts @@ -3,7 +3,7 @@ import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; import { adminRequired } from '@/api/middleware/auth'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import getSessions from '@/services/plex/sessions'; const getSessionsData = async (ctx: Context) => { diff --git a/pkg/api/src/api/routes/setup.ts b/pkg/api/src/api/routes/setup.ts index 3a1c7d990..14ef6ee39 100644 --- a/pkg/api/src/api/routes/setup.ts +++ b/pkg/api/src/api/routes/setup.ts @@ -1,3 +1,4 @@ +import { SharedCache } from '@david.uhlir/shared-cache'; import Router from '@koa/router'; import bcrypt from 'bcryptjs'; import { StatusCodes } from 'http-status-codes'; @@ -6,12 +7,11 @@ import mongoose from 'mongoose'; import { validateRequest } from '@/api/middleware/validation'; import { WriteConfig, config } from '@/config/index'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; +import { Worker } from '@/infra/worker/worker'; import { CreateOrUpdateUser, UserRole } from '@/models/user'; import { SetupTestInput, SetupTestInputSchema } from '@/schemas/setup'; import testConnection from '@/services/plex/connection'; -import { Worker } from '@/infra/worker/worker'; -import { SharedCache } from '@david.uhlir/shared-cache'; const testServer = async (ctx: Context) => { const body = ctx.request.body as SetupTestInput; diff --git a/pkg/api/src/api/routes/user.ts b/pkg/api/src/api/routes/user.ts index ed7ac706c..d60b557c7 100644 --- a/pkg/api/src/api/routes/user.ts +++ b/pkg/api/src/api/routes/user.ts @@ -1,4 +1,3 @@ -import path from 'path'; import multer from '@koa/multer'; import Router from '@koa/router'; import axios from 'axios'; @@ -6,14 +5,16 @@ import bcrypt from 'bcryptjs'; import { StatusCodes } from 'http-status-codes'; import { Context } from 'koa'; import send from 'koa-send'; +import path from 'path'; -import pathsConfig from '@/config/env/paths'; -import logger from '@/loaders/logger'; +import { DATA_DIR } from '@/infra/config/env'; +import logger from '@/infra/logger/logger'; import Profile from '@/models/profile'; import { UserModel, UserRole } from '@/models/user'; -import { adminRequired } from "../middleware/auth"; -const UPLOAD_DIR = path.join(pathsConfig.dataDir, './uploads'); +import { adminRequired } from '../middleware/auth'; + +const UPLOAD_DIR = path.join(DATA_DIR, './uploads'); const storage = multer.diskStorage({ destination(_req, _file, cb) { @@ -178,7 +179,7 @@ const editUser = async (ctx: Context) => { message: 'User edited', }; } catch (err) { - logger.error("failed to get user to edit", err); + logger.error('failed to get user to edit', err); ctx.status = StatusCodes.INTERNAL_SERVER_ERROR; ctx.body = { error: 'Error editing user', @@ -293,7 +294,7 @@ const updateUserThumbnail = async (ctx: Context) => { ctx.status = StatusCodes.OK; ctx.body = {}; } catch (err) { - logger.error("failed to update user", err); + logger.error('failed to update user', err); logger.warn('ROUTE: Failed to update user thumb in db'); ctx.status = StatusCodes.INTERNAL_SERVER_ERROR; diff --git a/pkg/api/src/api/routes/web.ts b/pkg/api/src/api/routes/web.ts index c2a3b3aaa..a53c38632 100644 --- a/pkg/api/src/api/routes/web.ts +++ b/pkg/api/src/api/routes/web.ts @@ -5,7 +5,7 @@ import addTrailingSlashes from 'koa-add-trailing-slashes'; import mount from 'koa-mount'; import serve from 'koa-static'; -import viewsConfig from "@/config/env/views"; +import { NODE_ENV } from '@/infra/config/env'; const pathExists = async (file: string): Promise => { try { @@ -24,7 +24,12 @@ function serveReact(app: Koa, dir: string, urlPath: string) { } export default async (app: Koa) => { - let frontendPath = viewsConfig.frontend; + const viewsPath = + NODE_ENV === 'production' ? + path.join(__dirname, './pkg/frontend/build') : + path.join(__dirname, '../../../../frontend/build'); + + let frontendPath = viewsPath; if (!(await pathExists(path.join(frontendPath, 'index.html')))) { const frontendBuildPath = path.join(frontendPath, './build'); if (!(await pathExists(path.join(frontendBuildPath, './index.html')))) { diff --git a/pkg/api/src/app.ts b/pkg/api/src/app.ts index ffce282fd..f3e0430a5 100644 --- a/pkg/api/src/app.ts +++ b/pkg/api/src/app.ts @@ -1,8 +1,17 @@ import 'reflect-metadata'; -import logger from './loaders/logger'; + +import logger from './infra/logger/logger'; import('dotenv/config'); +declare global { + namespace NodeJS { + interface Process { + pkg?: any; + } + } +} + // // Loads the app via loaders module and catches any app errors // @@ -11,4 +20,3 @@ import('dotenv/config'); logger.error('something unexpected went wrong', error); }); })(); - diff --git a/pkg/api/src/config/config.ts b/pkg/api/src/config/config.ts index bcc67246e..a56025ecf 100644 --- a/pkg/api/src/config/config.ts +++ b/pkg/api/src/config/config.ts @@ -2,9 +2,9 @@ import fs from 'fs/promises'; import path from 'path'; import configSchema from '@/config/schema'; -import logger from '@/loaders/logger'; +import { DATA_DIR } from '@/infra/config/env'; +import logger from '@/infra/logger/logger'; import { fileExists } from '@/utils/file'; -import pathsConfig from "./env/paths"; /** * The name of the config file to use @@ -15,13 +15,14 @@ const PETIO_CONFIG_FILE = 'petio.json'; * Gets the path of the config file * @returns the path of the config file */ -export const getConfigPath = (): string => path.join(pathsConfig.dataDir, PETIO_CONFIG_FILE); +export const getConfigPath = (): string => + path.join(DATA_DIR, PETIO_CONFIG_FILE); /** * Attempts to write the config data to the config file * @returns true if the config was written and false if it failed */ - export const WriteConfig = async (): Promise => { +export const WriteConfig = async (): Promise => { try { const properties = configSchema.getProperties(); const data = JSON.stringify(properties, null, 2); @@ -67,7 +68,8 @@ export const LoadConfig = async (): Promise => { * Checks if the config has been loaded * @returns true if config had been loaded else false */ -export const HasConfig = async (): Promise => fileExists(getConfigPath()); +export const HasConfig = async (): Promise => + fileExists(getConfigPath()); /** * Get the properties of the config diff --git a/pkg/api/src/config/env/app.ts b/pkg/api/src/config/env/app.ts deleted file mode 100644 index 8bdd0d516..000000000 --- a/pkg/api/src/config/env/app.ts +++ /dev/null @@ -1,9 +0,0 @@ -type AppConfig = { - version: string; -}; - -const appConfig: AppConfig = { - version: '0.6.0', -}; - -export default appConfig; diff --git a/pkg/api/src/config/env/external.ts b/pkg/api/src/config/env/external.ts deleted file mode 100644 index e3c19e4db..000000000 --- a/pkg/api/src/config/env/external.ts +++ /dev/null @@ -1,11 +0,0 @@ -type ExternalConfig = { - tmdbApiKey: string; - fanartApiKey: string; -}; - -const externalConfig: ExternalConfig = { - tmdbApiKey: process.env.TMDB_API_KEY || '1af5ad19a2d972a67cd27eb033979c4c', - fanartApiKey: process.env.FANART_API_KEY || 'ee409f6fb0c5cd2352e7a454d3f580d4', -}; - -export default externalConfig; diff --git a/pkg/api/src/config/env/http.ts b/pkg/api/src/config/env/http.ts deleted file mode 100644 index adcdcc52a..000000000 --- a/pkg/api/src/config/env/http.ts +++ /dev/null @@ -1,14 +0,0 @@ -type HttpConfig = { - corsDomains: string[]; -}; - -const domains = process.env.CORS_DOMAINS ? - process.env.CORS_DOMAINS.toLocaleLowerCase() - .split(",") - .map((domain) => domain.trim()) : []; - -const httpConfig: HttpConfig = { - corsDomains: domains, -} - -export default httpConfig; diff --git a/pkg/api/src/config/env/node.ts b/pkg/api/src/config/env/node.ts deleted file mode 100644 index d5ed7773b..000000000 --- a/pkg/api/src/config/env/node.ts +++ /dev/null @@ -1,18 +0,0 @@ -type EnvConfig = { - nodeEnv: string; - isProduction: boolean; - isDevelopment: boolean; -}; - -const allowEnv: string[] = ['development', 'production']; - -process.env.NODE_ENV = process.env.NODE_ENV && allowEnv.includes((process.env.NODE_ENV).toLocaleLowerCase()) ? - (process.env.NODE_ENV).toLocaleLowerCase() : 'development'; - -const envConfig: EnvConfig = { - nodeEnv: process.env.NODE_ENV, - isDevelopment: process.env.NODE_ENV === 'development', - isProduction: process.env.NODE_ENV === 'production' -}; - -export default envConfig; diff --git a/pkg/api/src/config/env/paths.ts b/pkg/api/src/config/env/paths.ts deleted file mode 100644 index c7dc0f919..000000000 --- a/pkg/api/src/config/env/paths.ts +++ /dev/null @@ -1,26 +0,0 @@ -import path from "path"; - -// ! This is a workaround for the fact that the process.cwd() is not the same as the root directory of the project -// ! because pkg from vercel adds an additional pkg field that points to the root directory of the project -const proc: Process = process; - -type PathsConfig = { - rootDir: string; - appDir: string; - dataDir: string; - viewsDir: string; -}; - -const ROOT_DIR = proc.pkg ? proc.cwd() : path.join(__dirname, '../../../../..'); -const APP_DIR = proc.pkg ? path.join(__dirname, "../..") : (proc.env.APP_DIR ?? path.join(__dirname, '../../../../..')); -const VIEWS_DIR = process.env.VIEWS_FOLDER ?? path.join(APP_DIR, './pkg'); -const DATA_DIR = process.env.DATA_FOLDER ?? path.join(ROOT_DIR, './data'); - -const pathsConfig: PathsConfig = { - rootDir: ROOT_DIR, - appDir: APP_DIR, - dataDir: DATA_DIR, - viewsDir: VIEWS_DIR, -}; - -export default pathsConfig; diff --git a/pkg/api/src/config/env/views.ts b/pkg/api/src/config/env/views.ts deleted file mode 100644 index 444731d59..000000000 --- a/pkg/api/src/config/env/views.ts +++ /dev/null @@ -1,12 +0,0 @@ -import path from "path"; -import pathConfig from "./paths"; - -type ViewsConfig = { - frontend: string; -}; - -const viewsConfig: ViewsConfig = { - frontend: path.join(pathConfig.viewsDir, "./frontend"), -}; - -export default viewsConfig; diff --git a/pkg/api/src/config/migration.ts b/pkg/api/src/config/migration.ts index 31cbc8130..47e2dad30 100644 --- a/pkg/api/src/config/migration.ts +++ b/pkg/api/src/config/migration.ts @@ -2,9 +2,9 @@ import fs from 'fs/promises'; import path from 'path'; import * as z from 'zod'; -import pathsConfig from "./env/paths"; import config from '@/config/schema'; -import logger from '@/loaders/logger'; +import { DATA_DIR } from '@/infra/config/env'; +import logger from '@/infra/logger/logger'; import { fileExists } from '@/utils/file'; const MainConfigSchema = z.object({ @@ -80,7 +80,6 @@ const configFiles: ConfigParse[] = [ }, ]; - const transformMainConfig = (data: any): void => { const output = data as MainConfig; config.set('db.url', output.DB_URL); @@ -182,13 +181,13 @@ const findParseAndMergeConfigs = async (): Promise => { let isModified = false; Object.values(configFiles).forEach(async (cfg) => { - const file = path.join(pathsConfig.dataDir, `${config.file}.json`); + const file = path.join(DATA_DIR, `${config.file}.json`); const exists = await fileExists(file); if (exists) { const content = await fs.readFile(file); const parsed = await config.schema.safeParseAsync(content); if (!parsed.success) { - logger.error(`failed to parse config '${ config.file }.json`); + logger.error(`failed to parse config '${config.file}.json`); return; } diff --git a/pkg/api/src/infra/config/env.ts b/pkg/api/src/infra/config/env.ts new file mode 100644 index 000000000..0642996a2 --- /dev/null +++ b/pkg/api/src/infra/config/env.ts @@ -0,0 +1,65 @@ +import path from "path"; +import { parseEnv, port } from "znv"; +import { z } from 'zod'; + +export const { + NODE_ENV, + LOG_LEVEL, + HTTP_ADDR, + HTTP_PORT, + HTTP_BASE_PATH, + HTTP_TRUSTED_PROXIES, + HTTP_CORS_DOMAINS, + DATA_DIR, + TMDB_API_KEY, + FANART_API_KEY, +} = parseEnv(process.env, { + NODE_ENV: { + schema: z.string().min(1), + description: 'The environment the application is running in.', + defaults: { + development: 'development', + _: 'production', + }, + }, + LOG_LEVEL: { + schema: z.enum(["trace", "debug", "info", "warn", "error", "fatal"]).default("info"), + description: 'The log level to use.', + }, + HTTP_ADDR: { + schema: z.string().min(1).default('127.0.0.1'), + description: 'The address the HTTP server should listen on.', + }, + HTTP_PORT: { + schema: port().default(7777), + description: 'The port the HTTP server should listen on.', + }, + HTTP_BASE_PATH: { + schema: z.string().min(1).default('/'), + description: 'The base path to use for the HTTP server.', + }, + HTTP_TRUSTED_PROXIES: { + schema: z.string().transform((value) => value.split(',')).default(""), + description: 'The trusted proxies to use.', + }, + HTTP_CORS_DOMAINS: { + schema: z.string().transform((value) => value.split(',')), + description: 'The CORS domains to use.', + defaults: { + development: 'http://localhost:3000', + _: 'http://localhost:7777', + } + }, + DATA_DIR: { + schema: z.string().min(1).default(path.join(process.cwd(), "./data")), + description: 'The directory to store data in.', + }, + TMDB_API_KEY: { + schema: z.string().min(1).default('1af5ad19a2d972a67cd27eb033979c4c'), + description: 'The API key to use for The Movie Database.', + }, + FANART_API_KEY: { + schema: z.string().min(1).default('ee409f6fb0c5cd2352e7a454d3f580d4'), + description: 'The API key to use for Fanart TV.', + }, +}); diff --git a/pkg/api/src/infra/fanart/api.ts b/pkg/api/src/infra/fanart/api.ts index 34b60ff87..3b87aaf1c 100644 --- a/pkg/api/src/infra/fanart/api.ts +++ b/pkg/api/src/infra/fanart/api.ts @@ -1,9 +1,9 @@ import { Zodios } from '@zodios/core'; import * as z from 'zod'; -import externalConfig from "@/config/env/external"; import { pluginQuery } from '@/utils/zodios'; +import { FANART_API_KEY } from '../config/env'; export enum FanartTypes { Movies = 'movies', @@ -208,4 +208,4 @@ export const FanartAPI = new Zodios('https://webservice.fanart.tv/v3', [ response: FanartSchema, }, ]); -FanartAPI.use(pluginQuery('api_key', async () => externalConfig.fanartApiKey)); +FanartAPI.use(pluginQuery('api_key', async () => FANART_API_KEY)); diff --git a/pkg/api/src/loaders/logger.ts b/pkg/api/src/infra/logger/logger.ts similarity index 93% rename from pkg/api/src/loaders/logger.ts rename to pkg/api/src/infra/logger/logger.ts index b3db17258..6825454ef 100644 --- a/pkg/api/src/loaders/logger.ts +++ b/pkg/api/src/infra/logger/logger.ts @@ -4,7 +4,7 @@ import path from 'path'; import pino, { Logger as PinoLogger } from 'pino'; import PinoPretty from 'pino-pretty'; -import pathsConfig from '@/config/env/paths'; +import { DATA_DIR, LOG_LEVEL } from '../config/env'; /** * The Logger class provides logging functionality for the application. @@ -15,9 +15,8 @@ export class Logger { private logger: PinoLogger; private constructor() { - const level = process.env.LOG_LEVEL || 'info'; this.logger = this.createLogger( - process.env.NODE_ENV === 'development' ? 'debug' : level, + process.env.NODE_ENV === 'development' ? 'debug' : LOG_LEVEL, ); } @@ -27,7 +26,7 @@ export class Logger { * @returns The created logger instance. */ private createLogger(level: string): PinoLogger { - const logsFolder = path.join(pathsConfig.dataDir, './logs'); + const logsFolder = path.join(DATA_DIR, './logs'); return pino( { level, diff --git a/pkg/api/src/infra/tmdb/tmdb.ts b/pkg/api/src/infra/tmdb/tmdb.ts index 9f68a5c91..de8019795 100644 --- a/pkg/api/src/infra/tmdb/tmdb.ts +++ b/pkg/api/src/infra/tmdb/tmdb.ts @@ -1,12 +1,12 @@ import { Zodios } from '@zodios/core'; +import { pluginQuery } from '@/utils/zodios'; +import { TMDB_API_KEY } from '../config/env'; import { DiscoverAPI } from './discover/discover'; import { MovieAPI } from './movie/movies'; import { TrendingAPI } from './trending/trending'; import { TVAPI } from './tv/tv'; -import externalConfig from "@/config/env/external"; -import { pluginQuery } from '@/utils/zodios'; export const TMDBAPI = new Zodios('https://api.themoviedb.org/3', [ ...DiscoverAPI, @@ -14,4 +14,4 @@ export const TMDBAPI = new Zodios('https://api.themoviedb.org/3', [ ...TVAPI, ...TrendingAPI, ]); -TMDBAPI.use(pluginQuery('api_key', async () => externalConfig.tmdbApiKey)); +TMDBAPI.use(pluginQuery('api_key', async () => TMDB_API_KEY)); diff --git a/pkg/api/src/infra/worker/master.ts b/pkg/api/src/infra/worker/master.ts index 3b481a715..c768a0a16 100644 --- a/pkg/api/src/infra/worker/master.ts +++ b/pkg/api/src/infra/worker/master.ts @@ -1,7 +1,9 @@ -import { IpcMethodHandler } from "@david.uhlir/ipc-method"; -import cluster from "cluster"; -import logger from "@/loaders/logger"; -import { masterReciever, workerReciever } from "./recievers"; +import { IpcMethodHandler } from '@david.uhlir/ipc-method'; +import cluster from 'cluster'; + +import logger from '@/infra/logger/logger'; + +import { masterReciever, workerReciever } from './recievers'; export class Master { private static instance: Master; @@ -10,7 +12,9 @@ export class Master { private constructor() { if (!cluster.isPrimary) { - throw new Error("Master class should be instantiated in the primary cluster"); + throw new Error( + 'Master class should be instantiated in the primary cluster', + ); } this.handler = new IpcMethodHandler(['worker-com'], masterReciever); } @@ -27,10 +31,7 @@ export class Master { } async runWorkers() { - return Promise.all( - ["job", "web"] - .map(async (w) => this.createWorker(w)), - ); + return Promise.all(['job', 'web'].map(async (w) => this.createWorker(w))); } private async createWorker(type: string) { @@ -47,7 +48,9 @@ export class Master { reject(error); }); worker.on('exit', (code, signal) => { - logger.debug(`Worker ${worker.process.pid} has exited with code ${code} and signal ${signal}`); + logger.debug( + `Worker ${worker.process.pid} has exited with code ${code} and signal ${signal}`, + ); if (!worker.exitedAfterDisconnect) { this.createWorker(type); } diff --git a/pkg/api/src/loaders/index.ts b/pkg/api/src/loaders/index.ts index 6c37c32f1..de01ff6b6 100644 --- a/pkg/api/src/loaders/index.ts +++ b/pkg/api/src/loaders/index.ts @@ -1,14 +1,14 @@ +import { SharedCache } from '@david.uhlir/shared-cache'; import cluster from 'cluster'; import api from '@/api'; +import { HasConfig, config, toObject } from '@/config'; +import logger from '@/infra/logger/logger'; +import { Master } from '@/infra/worker/master'; import ConfigLoader from '@/loaders/config'; import mongoose from '@/loaders/mongoose'; import { runCron } from '@/services/cron'; import startupMessage from '@/utils/startupMessage'; -import { SharedCache } from '@david.uhlir/shared-cache'; -import { HasConfig, config, toObject } from '@/config'; -import { Master } from '@/infra/worker/master'; -import logger from './logger'; /** * Checks if the application has a valid configuration. @@ -43,9 +43,7 @@ async function doPrimary() { } // run workers - await Master - .getInstance() - .runWorkers(); + await Master.getInstance().runWorkers(); } /** @@ -86,4 +84,4 @@ export default async () => { } else if (cluster.isWorker) { await doWorker(); } -} +}; diff --git a/pkg/api/src/loaders/mongoose.ts b/pkg/api/src/loaders/mongoose.ts index 06646b67f..95c40112a 100644 --- a/pkg/api/src/loaders/mongoose.ts +++ b/pkg/api/src/loaders/mongoose.ts @@ -1,8 +1,7 @@ import { Connection, connect } from 'mongoose'; import { config } from '@/config/index'; - -import logger from './logger'; +import logger from '@/infra/logger/logger'; export default async (): Promise => { const connection = await connect(config.get('db.url'), { diff --git a/pkg/api/src/models/user.ts b/pkg/api/src/models/user.ts index 4d62f9e23..09cd7c35c 100644 --- a/pkg/api/src/models/user.ts +++ b/pkg/api/src/models/user.ts @@ -1,10 +1,12 @@ /* eslint-disable no-param-reassign */ + /* eslint-disable no-underscore-dangle */ -import logger from '@/loaders/logger'; import { ObjectId } from 'bson'; import { Schema, model } from 'mongoose'; import * as z from 'zod'; +import logger from '@/infra/logger/logger'; + export enum UserRole { User = 'user', Admin = 'admin', @@ -86,7 +88,7 @@ const UserModelSchema = new Schema( { timestamps: true, toObject: { - transform (_doc, ret) { + transform(_doc, ret) { ret.id = ret._id; delete ret.password; delete ret._id; @@ -95,7 +97,7 @@ const UserModelSchema = new Schema( }, }, toJSON: { - transform (_doc, ret) { + transform(_doc, ret) { ret.id = ret._id; delete ret.password; delete ret._id; @@ -131,7 +133,7 @@ export const GetUserByEmail = async (email: string): Promise => { const parsed = await UserSchema.safeParseAsync(data.toObject()); if (!parsed.success) { - throw new Error(`failed to parse and validate data: ${ parsed.error}`); + throw new Error(`failed to parse and validate data: ${parsed.error}`); } parsed.data.id = data.id; diff --git a/pkg/api/src/services/cron/service.ts b/pkg/api/src/services/cron/service.ts index bb203a9b4..c74e0f942 100644 --- a/pkg/api/src/services/cron/service.ts +++ b/pkg/api/src/services/cron/service.ts @@ -1,4 +1,4 @@ -import { Logger } from '@/loaders/logger'; +import { Logger } from '@/infra/logger/logger'; import { DefaultCronOptions } from './constants'; import { diff --git a/pkg/api/src/services/discovery/build.ts b/pkg/api/src/services/discovery/build.ts index 77c38bffa..5414bf806 100644 --- a/pkg/api/src/services/discovery/build.ts +++ b/pkg/api/src/services/discovery/build.ts @@ -5,8 +5,8 @@ import { isErrorFromPath } from '@zodios/core'; import Bluebird from 'bluebird'; import { config } from '@/config'; +import loggerMain from '@/infra/logger/logger'; import { PlexAPIClient, PlexApiEndpoints } from '@/infra/plex/plex'; -import loggerMain from '@/loaders/logger'; import DiscoveryModel from '@/models/discovery'; import MovieModel, { Movie } from '@/models/movie'; import ShowModel, { Show } from '@/models/show'; diff --git a/pkg/api/src/services/discovery/display.ts b/pkg/api/src/services/discovery/display.ts index f80a6f27a..0e4910169 100644 --- a/pkg/api/src/services/discovery/display.ts +++ b/pkg/api/src/services/discovery/display.ts @@ -6,9 +6,10 @@ import Bluebird from 'bluebird'; import request from 'xhr-request'; -import externalConfig from '@/config/env/external'; import { config } from '@/config/index'; -import loggerMain from '@/loaders/logger'; +// eslint-disable-next-line import/order +import { TMDB_API_KEY } from '@/infra/config/env'; +import loggerMain from '@/infra/logger/logger'; import DiscoveryModel from '@/models/discovery'; import getHistory from '@/services/plex/history'; import onServer from '@/services/plex/server'; @@ -23,7 +24,6 @@ import { } from '@/services/tmdb/show'; import is from '@/utils/is'; -// eslint-disable-next-line import/order import cache from '../cache/cache'; const logger = loggerMain.child({ module: 'discovery.display' }); @@ -626,7 +626,7 @@ function discoverMovie(page = 1, params = {}) { Object.keys(params).forEach((i) => { par += `&${i}=${params[i]}`; }); - const url = `${tmdb}discover/movie?api_key=${externalConfig.tmdbApiKey}${par}&page=${page}&append_to_response=videos`; + const url = `${tmdb}discover/movie?api_key=${TMDB_API_KEY}${par}&page=${page}&append_to_response=videos`; return new Promise((resolve, reject) => { request( url, @@ -651,7 +651,7 @@ function discoverShow(page = 1, params = {}) { Object.keys(params).forEach((i) => { par += `&${i}=${params[i]}`; }); - const url = `${tmdb}discover/tv?api_key=${externalConfig.tmdbApiKey}${par}&page=${page}&append_to_response=videos`; + const url = `${tmdb}discover/tv?api_key=${TMDB_API_KEY}${par}&page=${page}&append_to_response=videos`; return new Promise((resolve, reject) => { request( url, @@ -672,7 +672,7 @@ function discoverShow(page = 1, params = {}) { function searchPeople(term: any) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}search/person?query=${term}&include_adult=false&api_key=${externalConfig.tmdbApiKey}`; + const url = `${tmdb}search/person?query=${term}&include_adult=false&api_key=${TMDB_API_KEY}`; return new Promise((resolve, reject) => { request( url, diff --git a/pkg/api/src/services/discovery/index.ts b/pkg/api/src/services/discovery/index.ts index bc5e11ca2..230c2fad7 100644 --- a/pkg/api/src/services/discovery/index.ts +++ b/pkg/api/src/services/discovery/index.ts @@ -2,7 +2,7 @@ import Bluebird from 'bluebird'; import _ from 'lodash'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import { GetAllUsers } from '@/models/user'; import cache from '../cache/cache'; diff --git a/pkg/api/src/services/downloaders/radarr.ts b/pkg/api/src/services/downloaders/radarr.ts index e43d6b35e..760568238 100644 --- a/pkg/api/src/services/downloaders/radarr.ts +++ b/pkg/api/src/services/downloaders/radarr.ts @@ -1,7 +1,7 @@ /* eslint-disable no-restricted-syntax */ import RadarrAPI from '@/infra/arr/radarr'; import { Movie } from '@/infra/arr/radarr/movie'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import { DownloaderType, GetAllDownloaders, diff --git a/pkg/api/src/services/downloaders/sonarr.ts b/pkg/api/src/services/downloaders/sonarr.ts index 60ed5bd3c..72327ae00 100644 --- a/pkg/api/src/services/downloaders/sonarr.ts +++ b/pkg/api/src/services/downloaders/sonarr.ts @@ -1,7 +1,7 @@ /* eslint-disable no-restricted-syntax */ import SonarrAPI from '@/infra/arr/sonarr'; import { Series } from '@/infra/arr/sonarr/series'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import { DownloaderType, GetAllDownloaders, diff --git a/pkg/api/src/services/fanart/index.ts b/pkg/api/src/services/fanart/index.ts index f22b8bb48..364d75dc8 100644 --- a/pkg/api/src/services/fanart/index.ts +++ b/pkg/api/src/services/fanart/index.ts @@ -1,5 +1,5 @@ import { FanartAPI } from '@/infra/fanart/api'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import cache from '../cache/cache'; diff --git a/pkg/api/src/services/mail/mailer.ts b/pkg/api/src/services/mail/mailer.ts index 92fb673ac..423399e6d 100644 --- a/pkg/api/src/services/mail/mailer.ts +++ b/pkg/api/src/services/mail/mailer.ts @@ -1,7 +1,7 @@ import nodemailer from 'nodemailer'; import { config } from '@/config/index'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; const logger = loggerMain.child({ module: 'mail.mailer' }); diff --git a/pkg/api/src/services/meta/imdb.ts b/pkg/api/src/services/meta/imdb.ts index fb1affd16..1d3551281 100644 --- a/pkg/api/src/services/meta/imdb.ts +++ b/pkg/api/src/services/meta/imdb.ts @@ -4,8 +4,8 @@ import lineReader from 'line-reader'; import path from 'path'; import zlib from 'zlib'; -import pathsConfig from '@/config/env/paths'; -import loggerMain from '@/loaders/logger'; +import { DATA_DIR } from '@/infra/config/env'; +import loggerMain from '@/infra/logger/logger'; import Imdb from '@/models/imdb'; const logger = loggerMain.child({ module: 'meta.imdb' }); @@ -32,7 +32,7 @@ export async function storeCache(firstTime = false) { } } const unzip = zlib.createGunzip(); - const tempFile = path.join(pathsConfig.dataDir, './imdb_dump.txt'); + const tempFile = path.join(DATA_DIR, './imdb_dump.txt'); logger.debug('IMDB: Rebuilding Cache'); try { logger.debug('IMDB: Cache Downloading latest cache', { diff --git a/pkg/api/src/services/notifications/discord.ts b/pkg/api/src/services/notifications/discord.ts index 73a009f30..f521572d6 100644 --- a/pkg/api/src/services/notifications/discord.ts +++ b/pkg/api/src/services/notifications/discord.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { config } from '@/config/index'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; const logger = loggerMain.child({ module: 'notifications.discord' }); diff --git a/pkg/api/src/services/notifications/notification.ts b/pkg/api/src/services/notifications/notification.ts index 597e633d5..2164f8fdd 100644 --- a/pkg/api/src/services/notifications/notification.ts +++ b/pkg/api/src/services/notifications/notification.ts @@ -1,6 +1,7 @@ import { container } from 'tsyringe'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; + import { INotify } from './notify'; import { DiscordProvider, diff --git a/pkg/api/src/services/notifications/telegram.ts b/pkg/api/src/services/notifications/telegram.ts index 7f7f80c86..e41c5fb16 100644 --- a/pkg/api/src/services/notifications/telegram.ts +++ b/pkg/api/src/services/notifications/telegram.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { config } from '@/config/index'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; export default class Telegram { botToken: any; diff --git a/pkg/api/src/services/plex/bandwidth.ts b/pkg/api/src/services/plex/bandwidth.ts index 72d2fd37c..3428a9f93 100644 --- a/pkg/api/src/services/plex/bandwidth.ts +++ b/pkg/api/src/services/plex/bandwidth.ts @@ -1,6 +1,6 @@ import axios from 'axios'; -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import MakePlexURL from '@/services/plex/util'; const logger = loggerMain.child({ module: 'plex.bandwidth' }); diff --git a/pkg/api/src/services/plex/history.ts b/pkg/api/src/services/plex/history.ts index 049f0e3c3..64d2a4484 100644 --- a/pkg/api/src/services/plex/history.ts +++ b/pkg/api/src/services/plex/history.ts @@ -4,8 +4,8 @@ import { isErrorFromPath } from '@zodios/core'; import { config } from '@/config'; +import loggerMain from '@/infra/logger/logger'; import { PlexAPIClient, PlexApiEndpoints } from '@/infra/plex/plex'; -import loggerMain from '@/loaders/logger'; import plexLookup from '@/services/plex/lookup'; import { movieLookup } from '@/services/tmdb/movie'; import { showLookup } from '@/services/tmdb/show'; diff --git a/pkg/api/src/services/plex/library.ts b/pkg/api/src/services/plex/library.ts index 7ac0072d8..4bd6ced40 100644 --- a/pkg/api/src/services/plex/library.ts +++ b/pkg/api/src/services/plex/library.ts @@ -2,9 +2,9 @@ import axios from 'axios'; import Bluebird from 'bluebird'; import xmlParser from 'xml-js'; -import externalConfig from '@/config/env/external'; import { config } from '@/config/index'; -import loggerMain from '@/loaders/logger'; +import { TMDB_API_KEY } from '@/infra/config/env'; +import loggerMain from '@/infra/logger/logger'; import Library from '@/models/library'; import MovieModel from '@/models/movie'; import Profile from '@/models/profile'; @@ -761,19 +761,19 @@ export default class LibraryUpdate { } async externalIdTv(id, type) { - const url = `${this.tmdb}find/${id}?api_key=${externalConfig.tmdbApiKey}&language=en-US&external_source=${type}_id`; + const url = `${this.tmdb}find/${id}?api_key=${TMDB_API_KEY}&language=en-US&external_source=${type}_id`; const res = await axios.get(url); return res.data.tv_results[0].id; } async tmdbExternalIds(id) { - const url = `${this.tmdb}tv/${id}/external_ids?api_key=${externalConfig.tmdbApiKey}`; + const url = `${this.tmdb}tv/${id}/external_ids?api_key=${TMDB_API_KEY}`; const res = await axios.get(url); return res.data; } async externalIdMovie(id, type) { - const url = `${this.tmdb}find/${id}?api_key=${externalConfig.tmdbApiKey}&language=en-US&external_source=${type}_id`; + const url = `${this.tmdb}find/${id}?api_key=${TMDB_API_KEY}&language=en-US&external_source=${type}_id`; const res = await axios.get(url); return res.data.movie_results[0].id; } diff --git a/pkg/api/src/services/plex/top.ts b/pkg/api/src/services/plex/top.ts index 8b6d86266..3faf1a9c2 100644 --- a/pkg/api/src/services/plex/top.ts +++ b/pkg/api/src/services/plex/top.ts @@ -2,9 +2,9 @@ import { fromError, isValidationError } from 'zod-validation-error'; import { config } from '@/config'; +import loggerMain from '@/infra/logger/logger'; import { PlexAPIClient } from '@/infra/plex/plex'; import { MediaContainer } from '@/infra/plex/plex/library'; -import loggerMain from '@/loaders/logger'; import plexLookup from '@/services/plex/lookup'; import { movieLookup } from '@/services/tmdb/movie'; import { showLookup } from '@/services/tmdb/show'; diff --git a/pkg/api/src/services/requests/display.ts b/pkg/api/src/services/requests/display.ts index 69eed1189..2b2d04fff 100644 --- a/pkg/api/src/services/requests/display.ts +++ b/pkg/api/src/services/requests/display.ts @@ -1,19 +1,31 @@ /* eslint-disable no-restricted-syntax */ + /* eslint-disable import/prefer-default-export */ import Bluebird from 'bluebird'; -import { RequestChildren, RequestOutput, RequestState, isDownloaderSeries, isDownloaderMovie } from './types'; -import { calcDate, cinemaWindow } from "./utils"; -import { Movie } from "@/infra/arr/radarr/v4/movie"; -import { Series } from "@/infra/arr/sonarr/v3/series"; -import logger from '@/loaders/logger'; -import { DownloaderType, GetAllDownloaders, IDownloader } from '@/models/downloaders'; +import { Movie } from '@/infra/arr/radarr/v4/movie'; +import { Series } from '@/infra/arr/sonarr/v3/series'; +import logger from '@/infra/logger/logger'; +import { + DownloaderType, + GetAllDownloaders, + IDownloader, +} from '@/models/downloaders'; import Request, { IRequest } from '@/models/request'; import Radarr from '@/services/downloaders/radarr'; import Sonarr from '@/services/downloaders/sonarr'; import { movieLookup } from '@/services/tmdb/movie'; import { showLookup } from '@/services/tmdb/show'; +import { + RequestChildren, + RequestOutput, + RequestState, + isDownloaderMovie, + isDownloaderSeries, +} from './types'; +import { calcDate, cinemaWindow } from './utils'; + function reqState(req: IRequest, children: RequestChildren[]): RequestState { let diff: number; if (!req.approved) { @@ -114,7 +126,7 @@ function reqState(req: IRequest, children: RequestChildren[]): RequestState { if (data.inCinemas) { diff = Math.ceil( new Date(element.info.inCinemas).getTime() - - new Date().getTime(), + new Date().getTime(), ); if (diff > 0) { return { @@ -135,8 +147,7 @@ function reqState(req: IRequest, children: RequestChildren[]): RequestState { } } else if (data.inCinemas) { diff = Math.ceil( - new Date().getTime() - - new Date(data.inCinemas).getTime(), + new Date().getTime() - new Date(data.inCinemas).getTime(), ); if (cinemaWindow(diff)) { return { @@ -225,11 +236,14 @@ function makeOutput(request: IRequest): RequestOutput { process_stage: reqState(request, []), defaults: request.pendingDefault, seasons: request.type === 'tv' ? request.seasons : undefined, - } + }, }; } -async function getRequestsForMovies(radarrs: IDownloader[], requests: IRequest[]) { +async function getRequestsForMovies( + radarrs: IDownloader[], + requests: IRequest[], +) { const results = await Bluebird.map(requests, async (request) => { const output = makeOutput(request); try { @@ -245,8 +259,7 @@ async function getRequestsForMovies(radarrs: IDownloader[], requests: IRequest[] client.GetQueue(), ]); - const status = queue.items - .filter((q) => q.id === movieId); + const status = queue.items.filter((q) => q.id === movieId); output[request.requestId].children.push({ id: movieId, @@ -256,9 +269,15 @@ async function getRequestsForMovies(radarrs: IDownloader[], requests: IRequest[] }, status, }); - output[request.requestId].process_stage = reqState(request, output[request.requestId].children); + output[request.requestId].process_stage = reqState( + request, + output[request.requestId].children, + ); } catch (error) { - logger.error(`failed to get movie ${request.radarrId[0]} from ${instance.name}`, error); + logger.error( + `failed to get movie ${request.radarrId[0]} from ${instance.name}`, + error, + ); output[request.requestId].children.push({ id: -1, info: { @@ -281,7 +300,10 @@ async function getRequestsForMovies(radarrs: IDownloader[], requests: IRequest[] return results; } -async function getRequestsForShows(sonarrs: IDownloader[], requests: IRequest[]) { +async function getRequestsForShows( + sonarrs: IDownloader[], + requests: IRequest[], +) { const results = await Bluebird.map(requests, async (request) => { const output = makeOutput(request); try { @@ -297,8 +319,7 @@ async function getRequestsForShows(sonarrs: IDownloader[], requests: IRequest[]) client.GetQueue(), ]); - const status = queue.items - .filter((q) => q.id === seriesId); + const status = queue.items.filter((q) => q.id === seriesId); output[request.requestId].children.push({ id: seriesId, @@ -308,9 +329,15 @@ async function getRequestsForShows(sonarrs: IDownloader[], requests: IRequest[]) }, status, }); - output[request.requestId].process_stage = reqState(request, output[request.requestId].children); + output[request.requestId].process_stage = reqState( + request, + output[request.requestId].children, + ); } catch (error) { - logger.error(`failed to get show ${request.sonarrId[0]} from ${instance.name}`, error); + logger.error( + `failed to get show ${request.sonarrId[0]} from ${instance.name}`, + error, + ); output[request.requestId].children.push({ id: -1, info: { @@ -344,10 +371,16 @@ export async function getAllRequests() { return {}; } - const sonarrs = instances.filter((instance) => instance.type === DownloaderType.Sonarr); - const radarrs = instances.filter((instance) => instance.type === DownloaderType.Radarr); + const sonarrs = instances.filter( + (instance) => instance.type === DownloaderType.Sonarr, + ); + const radarrs = instances.filter( + (instance) => instance.type === DownloaderType.Radarr, + ); - const movieRequests = requests.filter((request) => request.type === 'movie'); + const movieRequests = requests.filter( + (request) => request.type === 'movie', + ); const tvRequests = requests.filter((request) => request.type === 'tv'); const [movieStatus, tvStatus] = await Promise.all([ @@ -355,7 +388,10 @@ export async function getAllRequests() { getRequestsForShows(sonarrs, tvRequests), ]); - if (Object.keys(movieStatus).length === 0 && Object.keys(tvStatus).length === 0) { + if ( + Object.keys(movieStatus).length === 0 && + Object.keys(tvStatus).length === 0 + ) { return requests.map((request) => makeOutput(request)); } @@ -381,10 +417,16 @@ export async function getAllUserRequests(userId: string) { return {}; } - const sonarrs = instances.filter((instance) => instance.type === DownloaderType.Sonarr); - const radarrs = instances.filter((instance) => instance.type === DownloaderType.Radarr); + const sonarrs = instances.filter( + (instance) => instance.type === DownloaderType.Sonarr, + ); + const radarrs = instances.filter( + (instance) => instance.type === DownloaderType.Radarr, + ); - const movieRequests = requests.filter((request) => request.type === 'movie'); + const movieRequests = requests.filter( + (request) => request.type === 'movie', + ); const tvRequests = requests.filter((request) => request.type === 'tv'); const [movieStatus, tvStatus] = await Promise.all([ @@ -392,7 +434,10 @@ export async function getAllUserRequests(userId: string) { getRequestsForShows(sonarrs, tvRequests), ]); - if (Object.keys(movieStatus).length === 0 && Object.keys(tvStatus).length === 0) { + if ( + Object.keys(movieStatus).length === 0 && + Object.keys(tvStatus).length === 0 + ) { return requests.map((request) => makeOutput(request)); } diff --git a/pkg/api/src/services/requests/filter.ts b/pkg/api/src/services/requests/filter.ts index ded708b63..0c4735913 100644 --- a/pkg/api/src/services/requests/filter.ts +++ b/pkg/api/src/services/requests/filter.ts @@ -1,4 +1,4 @@ -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import Filter from '@/models/filter'; import { movieLookup } from '@/services/tmdb/movie'; import { showLookup } from '@/services/tmdb/show'; diff --git a/pkg/api/src/services/requests/process.ts b/pkg/api/src/services/requests/process.ts index af9fdd9e8..eb5414451 100644 --- a/pkg/api/src/services/requests/process.ts +++ b/pkg/api/src/services/requests/process.ts @@ -1,4 +1,4 @@ -import loggerMain from '@/loaders/logger'; +import loggerMain from '@/infra/logger/logger'; import Archive from '@/models/archive'; import { DownloaderType, GetAllDownloaders } from '@/models/downloaders'; import Profile from '@/models/profile'; diff --git a/pkg/api/src/services/requests/quotas.ts b/pkg/api/src/services/requests/quotas.ts index 59f15aa1a..cb1f8a24a 100644 --- a/pkg/api/src/services/requests/quotas.ts +++ b/pkg/api/src/services/requests/quotas.ts @@ -1,4 +1,4 @@ -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import { UserModel } from '@/models/user'; export default class QuotaSystem { diff --git a/pkg/api/src/services/setup/setup.ts b/pkg/api/src/services/setup/setup.ts index 92583aac0..ae64dd72d 100644 --- a/pkg/api/src/services/setup/setup.ts +++ b/pkg/api/src/services/setup/setup.ts @@ -1,4 +1,4 @@ -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; import Movie from '@/models/movie'; import Show from '@/models/show'; import { UserModel } from '@/models/user'; @@ -15,12 +15,11 @@ export default async () => { ready: true, error: false, }; - } - return { - ready: false, - error: false, - }; - + } + return { + ready: false, + error: false, + }; } catch (e) { logger.error('setup ready check failed to get ready'); logger.error(e); diff --git a/pkg/api/src/services/tmdb/movie.ts b/pkg/api/src/services/tmdb/movie.ts index 4c16cd390..64a68cc00 100644 --- a/pkg/api/src/services/tmdb/movie.ts +++ b/pkg/api/src/services/tmdb/movie.ts @@ -2,8 +2,8 @@ import axios, { AxiosResponse } from 'axios'; import http from 'http'; -import externalConfig from '@/config/env/external'; -import loggerMain from '@/loaders/logger'; +import { TMDB_API_KEY } from '@/infra/config/env'; +import loggerMain from '@/infra/logger/logger'; import fanartLookup from '@/services/fanart'; import { lookup } from '@/services/meta/imdb'; import onServer from '@/services/plex/server'; @@ -341,7 +341,7 @@ export async function discoverMovie(page = 1, params = {}) { Object.keys(params).forEach((i) => { par += `&${i}=${params[i]}`; }); - const url = `${tmdb}discover/movie?api_key=${externalConfig.tmdbApiKey}${par}&page=${page}&append_to_response=videos`; + const url = `${tmdb}discover/movie?api_key=${TMDB_API_KEY}${par}&page=${page}&append_to_response=videos`; const res = await axios.get(url, { httpAgent: agent }); if (res.data && res.data.results.length > 0) { await Promise.all( @@ -361,7 +361,7 @@ interface CompanyData { export async function company(id: number): Promise { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}company/${id}?api_key=${externalConfig.tmdbApiKey}`; + const url = `${tmdb}company/${id}?api_key=${TMDB_API_KEY}`; const res: AxiosResponse = await axios.get(url, { httpAgent: agent, }); diff --git a/pkg/api/src/services/tmdb/person.ts b/pkg/api/src/services/tmdb/person.ts index bcec4de86..1c97e9745 100644 --- a/pkg/api/src/services/tmdb/person.ts +++ b/pkg/api/src/services/tmdb/person.ts @@ -1,8 +1,8 @@ -import http from 'http'; import axios from 'axios'; +import http from 'http'; -import externalConfig from "@/config/env/external"; -import logger from '@/loaders/logger'; +import { TMDB_API_KEY } from '@/infra/config/env'; +import logger from '@/infra/logger/logger'; const agent = new http.Agent({ family: 4 }); @@ -26,21 +26,21 @@ export default personLookup; async function getPersonInfo(id) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}person/${id}?api_key=${externalConfig.tmdbApiKey}&append_to_response=images`; + const url = `${tmdb}person/${id}?api_key=${TMDB_API_KEY}&append_to_response=images`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } async function getPersonMovies(id) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}person/${id}/movie_credits?api_key=${externalConfig.tmdbApiKey}&append_to_response=credits,videos`; + const url = `${tmdb}person/${id}/movie_credits?api_key=${TMDB_API_KEY}&append_to_response=credits,videos`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } async function getPersonShows(id) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}person/${id}/tv_credits?api_key=${externalConfig.tmdbApiKey}&append_to_response=credits,videos`; + const url = `${tmdb}person/${id}/tv_credits?api_key=${TMDB_API_KEY}&append_to_response=credits,videos`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } diff --git a/pkg/api/src/services/tmdb/search.ts b/pkg/api/src/services/tmdb/search.ts index 0d6069802..610721721 100644 --- a/pkg/api/src/services/tmdb/search.ts +++ b/pkg/api/src/services/tmdb/search.ts @@ -3,9 +3,9 @@ import Promise from 'bluebird'; import http from 'http'; import sanitize from 'sanitize-filename'; -import externalConfig from '@/config/env/external'; import { config } from '@/config/index'; -import loggerMain from '@/loaders/logger'; +import { TMDB_API_KEY } from '@/infra/config/env'; +import loggerMain from '@/infra/logger/logger'; import onServer from '@/services/plex/server'; import { movieLookup } from '@/services/tmdb/movie'; import { showLookup } from '@/services/tmdb/show'; @@ -55,7 +55,7 @@ export default search; async function searchMovies(term) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}search/movie?query=${term}&include_adult=false&api_key=${externalConfig.tmdbApiKey}&append_to_response=credits,videos`; + const url = `${tmdb}search/movie?query=${term}&include_adult=false&api_key=${TMDB_API_KEY}&append_to_response=credits,videos`; try { const res = await axios.get(url, { httpAgent: agent }); return res.data; @@ -69,7 +69,7 @@ async function searchMovies(term) { async function searchShows(term) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}search/tv?query=${term}&include_adult=false&api_key=${externalConfig.tmdbApiKey}&append_to_response=credits,videos`; + const url = `${tmdb}search/tv?query=${term}&include_adult=false&api_key=${TMDB_API_KEY}&append_to_response=credits,videos`; try { const res = await axios.get(url, { httpAgent: agent }); return res.data; @@ -83,7 +83,7 @@ async function searchShows(term) { async function searchPeople(term) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}search/person?query=${term}&include_adult=false&api_key=${externalConfig.tmdbApiKey}&append_to_response=credits,videos`; + const url = `${tmdb}search/person?query=${term}&include_adult=false&api_key=${TMDB_API_KEY}&append_to_response=credits,videos`; try { const res = await axios.get(url, { httpAgent: agent }); return res.data; @@ -97,7 +97,7 @@ async function searchPeople(term) { async function searchCompanies(term) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}search/company?query=${term}&api_key=${externalConfig.tmdbApiKey}`; + const url = `${tmdb}search/company?query=${term}&api_key=${TMDB_API_KEY}`; try { const res = await axios.get(url, { httpAgent: agent }); return res.data; diff --git a/pkg/api/src/services/tmdb/show.ts b/pkg/api/src/services/tmdb/show.ts index 42fa3acbb..aa35bf7b2 100644 --- a/pkg/api/src/services/tmdb/show.ts +++ b/pkg/api/src/services/tmdb/show.ts @@ -4,9 +4,9 @@ import axios from 'axios'; import http from 'http'; -import externalConfig from '@/config/env/external'; +import { TMDB_API_KEY } from '@/infra/config/env'; +import loggerMain from '@/infra/logger/logger'; import { TMDBAPI } from '@/infra/tmdb/tmdb'; -import loggerMain from '@/loaders/logger'; import fanartLookup from '@/services/fanart'; import { lookup as imdb } from '@/services/meta/imdb'; import onServer from '@/services/plex/server'; @@ -349,7 +349,7 @@ async function getSeasons(seasons, id) { async function tmdbData(id) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}tv/${id}?api_key=${externalConfig.tmdbApiKey}&append_to_response=aggregate_credits,videos,keywords,content_ratings,credits`; + const url = `${tmdb}tv/${id}?api_key=${TMDB_API_KEY}&append_to_response=aggregate_credits,videos,keywords,content_ratings,credits`; const res = await axios.get(url, { httpAgent: agent }); const { data } = res; if (data.aggregate_credits) { @@ -376,14 +376,14 @@ async function tmdbData(id) { async function recommendationData(id, page = 1) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}tv/${id}/recommendations?api_key=${externalConfig.tmdbApiKey}&page=${page}`; + const url = `${tmdb}tv/${id}/recommendations?api_key=${TMDB_API_KEY}&page=${page}`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } async function similarData(id, page = 1) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}tv/${id}/similar?api_key=${externalConfig.tmdbApiKey}&page=${page}`; + const url = `${tmdb}tv/${id}/similar?api_key=${TMDB_API_KEY}&page=${page}`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } @@ -402,14 +402,14 @@ async function seasonsAsync(seasonList, id) { async function getSeason(id, season) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}tv/${id}/season/${season}?api_key=${externalConfig.tmdbApiKey}`; + const url = `${tmdb}tv/${id}/season/${season}?api_key=${TMDB_API_KEY}`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } async function reviewsData(id) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}tv/${id}/reviews?api_key=${externalConfig.tmdbApiKey}`; + const url = `${tmdb}tv/${id}/reviews?api_key=${TMDB_API_KEY}`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } @@ -446,7 +446,7 @@ function findEnRating(data) { async function idLookup(id) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}tv/${id}/external_ids?api_key=${externalConfig.tmdbApiKey}`; + const url = `${tmdb}tv/${id}/external_ids?api_key=${TMDB_API_KEY}`; try { const res = await axios.get(url, { httpAgent: agent }); return res.data; @@ -462,7 +462,7 @@ export async function discoverSeries(page = 1, params = {}) { Object.keys(params).forEach((i) => { par += `&${i}=${params[i]}`; }); - const url = `${tmdb}discover/tv?api_key=${externalConfig.tmdbApiKey}${par}&page=${page}`; + const url = `${tmdb}discover/tv?api_key=${TMDB_API_KEY}${par}&page=${page}`; const res = await axios.get(url, { httpAgent: agent }); if (res.data && res.data.results.length > 0) { await Promise.all( @@ -477,7 +477,7 @@ export async function discoverSeries(page = 1, params = {}) { export async function network(id) { const tmdb = 'https://api.themoviedb.org/3/'; - const url = `${tmdb}network/${id}?api_key=${externalConfig.tmdbApiKey}`; + const url = `${tmdb}network/${id}?api_key=${TMDB_API_KEY}`; const res = await axios.get(url, { httpAgent: agent }); return res.data; } diff --git a/pkg/api/src/services/tmdb/trending.ts b/pkg/api/src/services/tmdb/trending.ts index 2c6c123bf..d68eb298c 100644 --- a/pkg/api/src/services/tmdb/trending.ts +++ b/pkg/api/src/services/tmdb/trending.ts @@ -1,7 +1,6 @@ import bluebird from 'bluebird'; -import cache from "../cache/cache"; -import { getMovieDetails, getShowDetails } from './show'; +import logger from '@/infra/logger/logger'; import { TMDBAPI } from '@/infra/tmdb/tmdb'; import { MediaType, @@ -10,7 +9,9 @@ import { TrendingPeople, TrendingTv, } from '@/infra/tmdb/trending/trending'; -import logger from '@/loaders/logger'; + +import cache from '../cache/cache'; +import { getMovieDetails, getShowDetails } from './show'; const Companies = [ { diff --git a/pkg/api/src/types/node.d.ts b/pkg/api/src/types/node.d.ts deleted file mode 100644 index f15394795..000000000 --- a/pkg/api/src/types/node.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -interface Process extends NodeJS.Process { - pkg?: any; -} diff --git a/pkg/api/src/utils/startupMessage.ts b/pkg/api/src/utils/startupMessage.ts index 598404ab2..e4590c1a8 100644 --- a/pkg/api/src/utils/startupMessage.ts +++ b/pkg/api/src/utils/startupMessage.ts @@ -1,10 +1,11 @@ -import appConfig from "@/config/env/app"; import config from '@/config/schema'; -import logger from '@/loaders/logger'; +import logger from '@/infra/logger/logger'; + +import appConfig from '../../package.json'; import { removeSlashes } from './urls'; export default () => { - const subpath = `/${ removeSlashes(config.get('petio.subpath'))}`; + const subpath = `/${removeSlashes(config.get('petio.subpath'))}`; logger.info(`Petio v${appConfig.version} [${logger.core().level}]`); logger.info( `Serving Web UI on http://${config.get('petio.host')}:${config.get( diff --git a/yarn.lock b/yarn.lock index aa1040e00..8edf77387 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6258,6 +6258,7 @@ __metadata: xhr-request: ^1.1.0 xml-js: ^1.6.11 yargs-parser: ^21.1.1 + znv: ^0.4.0 zod: ^3.22.4 zod-validation-error: ^3.2.0 languageName: unknown @@ -7535,7 +7536,7 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.14, colorette@npm:^2.0.7": +"colorette@npm:^2.0.14, colorette@npm:^2.0.19, colorette@npm:^2.0.7": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d @@ -20388,6 +20389,17 @@ __metadata: languageName: node linkType: hard +"znv@npm:^0.4.0": + version: 0.4.0 + resolution: "znv@npm:0.4.0" + dependencies: + colorette: ^2.0.19 + peerDependencies: + zod: ^3.13.2 + checksum: 7dac62d9b83de85f46ebdcc3a8563a198129a34f60948e99ba339a824594ace4ca37d328ad358646f321ee42b9506ef71d9e9901d41a668cca843ac272194d71 + languageName: node + linkType: hard + "zod-validation-error@npm:^3.2.0": version: 3.2.0 resolution: "zod-validation-error@npm:3.2.0"