diff --git a/services/request/package.json b/services/request/package.json index 1916915..083cf37 100644 --- a/services/request/package.json +++ b/services/request/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@autoplex/database": "^0.0.0", + "@autoplex/microservice": "^0.0.0", "@autoplex/utils": "^0.0.0", "@fortawesome/fontawesome-free": "^5.15.3", "bcrypt": "^5.0.1", diff --git a/services/request/src/server/Application.ts b/services/request/src/server/Application.ts index 779aedf..bd2bab2 100644 --- a/services/request/src/server/Application.ts +++ b/services/request/src/server/Application.ts @@ -1,67 +1,35 @@ +import { Microservice } from "@autoplex/microservice"; import services from "./services"; -import Service from "./services/Service"; import { User, RegisterToken } from "@autoplex/database"; -import assert from "assert"; - -interface ServiceMap { - [name: string]: Service -} /** * The main application class */ -export default class Application +export default class Application extends Microservice { - private static __instance: Application; - /** * The application key used for signing stuff */ public readonly APP_KEY: string; /** - * All available services - */ - protected services: ServiceMap = {}; - - /** - * Return the current application instance + * Get the singleton application instance */ - public static instance() { - return this.__instance; - } + public static instance() { return super.instance() } /** * Create a new application instance */ public constructor(appKey: string) { - Application.__instance = this; + super(); this.APP_KEY = appKey; - for (let ServiceClass of Object.values(services)) { - this.installService(ServiceClass); - } - } - - /** - * Install a service into the application - */ - protected installService(ServiceClass: new (app: Application) => Service) { - let service = new ServiceClass(this); - this.services[service.name] = service; - } - - /** - * Boot the application and all of the services - */ - protected async boot() { - let services = Object.values(this.services); - return Promise.all(services.map(service => service.boot())); + this.installServices(Object.values(services)); } /** * Initialize the application if necessary */ - protected async initialize() { + protected async onStart() { let numUsers = await User.count(); if (numUsers == 0) { console.log("Found 0 users"); @@ -72,48 +40,4 @@ export default class Application console.log("First time register with: ", token.token); } } - - /** - * Shutdown the application - */ - protected shutdown() { - let services = Object.values(this.services); - return Promise.all(services.map(service => service.shutdown())); - } - - /** - * Start the application - */ - public async start() { - await this.boot(); - await this.initialize(); - for (let service of Object.values(this.services)) { - service.start(); - } - } - - /** - * Quit the application - */ - public async quit(code: number = 0) { - await this.shutdown(); - process.exit(code); - } - - // Access -------------------------------------------------------------------------------------- - - /** - * Get all available services - */ - public serviceList() { - return Object.keys(this.services); - } - - /** - * Get an application service instance - */ - public service(serviceName: string) { - assert(serviceName in this.services); - return this.services[serviceName]; - } } diff --git a/services/request/src/server/index.ts b/services/request/src/server/index.ts index 17cab67..fd27716 100644 --- a/services/request/src/server/index.ts +++ b/services/request/src/server/index.ts @@ -14,4 +14,4 @@ let app = new Application(appKey); /** * Start the application */ -app.start(); +app.exec(); diff --git a/services/request/src/server/services/Database.ts b/services/request/src/server/services/Database.ts index 0d3d892..8e75fb2 100644 --- a/services/request/src/server/services/Database.ts +++ b/services/request/src/server/services/Database.ts @@ -1,10 +1,10 @@ import { Connection } from "typeorm"; -import Service from "./Service"; -import Application from "../Application"; +import { InternalService } from "@autoplex/microservice"; import connectToDatabase from "@autoplex/database"; import { env, secret } from "@autoplex/utils"; +import Application from "@server/Application"; -export default class Database extends Service +export default class Database extends InternalService { /** * The active database connection @@ -12,11 +12,9 @@ export default class Database extends Service protected connection!: Connection; /** - * Create a new database instance + * The name of the service */ - public constructor(app: Application) { - super("Database", app); - } + public get name() { return "Database" } /** * Boot the database service diff --git a/services/request/src/server/services/DiscordBot.ts b/services/request/src/server/services/DiscordBot.ts index 2a79052..73fc8a8 100644 --- a/services/request/src/server/services/DiscordBot.ts +++ b/services/request/src/server/services/DiscordBot.ts @@ -1,9 +1,9 @@ -import Application from "../Application"; -import Service from "./Service"; +import { InternalService } from "@autoplex/microservice"; import { Client, Collection, Message, TextChannel, User as DiscordUser } from "discord.js"; import { env, formatImdbId, generateToken, secret } from "@autoplex/utils"; import { DiscordAccount, DiscordChannel, DiscordLinkRequest, MovieTicket } from "@autoplex/database"; import MovieSearch from "./MovieSearch"; +import Application from "@server/Application"; /** * The required role to perfrom administrative commands on the bot @@ -32,7 +32,7 @@ interface IAuthCommandmap { [key: string]: (account: DiscordAccount, message: Message) => void|Promise } -export default class DiscordBot extends Service +export default class DiscordBot extends InternalService { /** * The internal Discord bot instance @@ -78,7 +78,7 @@ export default class DiscordBot extends Service * Create a new Discord bot instance */ public constructor(app: Application) { - super("Discord Bot", app); + super(app); this.bot = new Client(); this.adminCommands = { "install": this.cmdInstallChannel.bind(this) @@ -89,6 +89,11 @@ export default class DiscordBot extends Service this.authCommands = {}; } + /** + * The service name + */ + public get name() { return "Discord Bot" } + /** * Boot the discord bot */ diff --git a/services/request/src/server/services/Ipc/IpcClient.ts b/services/request/src/server/services/Ipc/IpcClient.ts index b4738c4..f258578 100644 --- a/services/request/src/server/services/Ipc/IpcClient.ts +++ b/services/request/src/server/services/Ipc/IpcClient.ts @@ -1,7 +1,7 @@ import { Socket } from "net"; -import Service from "../Service"; -import Application from "../../Application"; +import { InternalService } from "@autoplex/microservice"; import RawIPC = require("node-ipc"); +import Application from "@server/Application"; export interface IIpcResponse { response?: any, @@ -18,7 +18,7 @@ export class IpcConnectionError extends Error { } } -export default class IpcClient extends Service +export default class IpcClient extends InternalService { /** * Indicate if there is an active connection to the IPC @@ -30,6 +30,11 @@ export default class IpcClient extends Service */ private __targetIpc: string; + /** + * The IPC client name + */ + protected readonly IPC_NAME: string; + /** * HOLY @#$@% WHOEVER MADE THE TYPES FOR node-ipc SHOULDB BE HANGED */ @@ -44,7 +49,8 @@ export default class IpcClient extends Service * Create a new IPC client for the Seeker */ constructor(name: string, app: Application, ipcId: string, targetIpc: string) { - super(name, app); + super(app); + this.IPC_NAME = name; this.ipc = new RawIPC.IPC(); this.ipc.config.id = ipcId; this.ipc.config.retry = 1500; @@ -53,6 +59,11 @@ export default class IpcClient extends Service this.__isConnected = false; } + /** + * The name of the service + */ + public get name() { return this.IPC_NAME } + /** * Boot the seeker client IPC service */ diff --git a/services/request/src/server/services/Ipc/SeekerIpcClient.ts b/services/request/src/server/services/Ipc/SeekerIpcClient.ts index db68e47..fe96aad 100644 --- a/services/request/src/server/services/Ipc/SeekerIpcClient.ts +++ b/services/request/src/server/services/Ipc/SeekerIpcClient.ts @@ -1,5 +1,5 @@ import IpcClient from "./IpcClient"; -import Application from "../../Application"; +import Application from "@server/Application"; export default class SeekerIpcClient extends IpcClient { diff --git a/services/request/src/server/services/Ipc/TorrentIpcClient.ts b/services/request/src/server/services/Ipc/TorrentIpcClient.ts index af94936..a7cd6b5 100644 --- a/services/request/src/server/services/Ipc/TorrentIpcClient.ts +++ b/services/request/src/server/services/Ipc/TorrentIpcClient.ts @@ -1,6 +1,6 @@ import { ISerializedTorrent, ITorrent } from "../../common"; -import Application from "../../Application"; import IpcClient from "./IpcClient"; +import Application from "@server/Application"; export default class TorrentIpcClient extends IpcClient { diff --git a/services/request/src/server/services/MovieSearch.ts b/services/request/src/server/services/MovieSearch.ts index 1e824ef..815c8e7 100644 --- a/services/request/src/server/services/MovieSearch.ts +++ b/services/request/src/server/services/MovieSearch.ts @@ -1,17 +1,20 @@ -import Application from "@server/Application"; import TheMovieDb, { ExternalSource } from "@lib/tmdb"; import { env, plexMediaUrl, secret } from "@autoplex/utils"; import { request } from "https"; -import Service from "./Service"; +import { InternalService } from "@autoplex/microservice"; import TvDb from "./TvDb"; import { IApiMovie, IApiMovieDetails, IApiPaginatedResponse } from "@common/api_schema"; import { MovieTicket, PlexMovie } from "@autoplex/database"; import { IMovieSearchResult } from "@lib/tmdb/schema"; +import Application from "@server/Application"; const CACHE_CLEAR_INTERVAL = 1000*60; // 60 seconds -export default class MovieSearch extends Service +export default class MovieSearch extends InternalService { + /** + * A reference to The Movie DB API + */ protected tmdb!: TheMovieDb; /** @@ -22,21 +25,17 @@ export default class MovieSearch extends Service /** * Hold a cache of recently fetched movies to speed up request times */ - protected movieCache: { [tmdbId: number]: { timestamp: number, movie: IApiMovieDetails } }; + protected movieCache: { [tmdbId: number]: { timestamp: number, movie: IApiMovieDetails } } = {}; /** * Hold the clear cache interval reference */ - private __clearCacheInterval: NodeJS.Timeout | null; + private __clearCacheInterval: NodeJS.Timeout|null = null; /** - * Create a new Movie Search service instance + * The name of the service */ - public constructor(app: Application) { - super("Movie Search", app); - this.movieCache = {}; - this.__clearCacheInterval = null; - } + public get name() { return "Movie Search" } /** * Start the service diff --git a/services/request/src/server/services/PlexLibrary.ts b/services/request/src/server/services/PlexLibrary.ts index e9ad258..0adda37 100644 --- a/services/request/src/server/services/PlexLibrary.ts +++ b/services/request/src/server/services/PlexLibrary.ts @@ -1,9 +1,9 @@ import Plex from "@lib/plex"; -import Application from "@server/Application"; import { PlexMovie } from "@autoplex/database"; import { env, secret, sleep } from "@autoplex/utils"; import MovieSearch from "./MovieSearch"; -import Service from "./Service"; +import { InternalService } from "@autoplex/microservice"; +import Application from "@server/Application"; /** * Throttle requests when updating the movie database @@ -13,7 +13,7 @@ const API_DATABASE_THROTTLE = 500; /** * A service for maintaining an internal representation of the Plex library */ -export default class PlexLibrary extends Service +export default class PlexLibrary extends InternalService { /** * A reference to the Plex library @@ -23,36 +23,34 @@ export default class PlexLibrary extends Service /** * The key for the movies library */ - protected moviesKey!: string; + protected readonly KEY_MOVIES: string = env("PLEX_LIBRARY_MOVIES_KEY"); /** * The key for the TV shows library */ - protected tvKey!: string; + protected readonly KEY_TV: string = env("PLEX_LIBRARY_TV_KEY"); /** * Indicate if the plex library is currently being updated */ - protected isUpdating: boolean; + protected isUpdating: boolean = false; /** - * Create a new Plex library service instance + * The service name */ - public constructor(app: Application) { - super("Plex Library", app); - this.isUpdating = false; - } + public get name() { return "Plex Library" } /** * Boot the Plex library service */ public async boot() { let token = await secret(env("PLEX_TOKEN_FILE")); - this.moviesKey = env("PLEX_LIBRARY_MOVIES_KEY"); - this.tvKey = env("PLEX_LIBRARY_TV_KEY"); this.plex = new Plex(token); } + /** + * Update the movies after boot + */ public start() { this.updateMovies(); } @@ -76,7 +74,7 @@ export default class PlexLibrary extends Service // Fetch the current and new sets of IMDb IDs let [currentIds, newIds] = await Promise.all([ PlexMovie.imdbSet(), - this.plex.movies(this.moviesKey), + this.plex.movies(this.KEY_MOVIES), ]); // Calculate the updates diff --git a/services/request/src/server/services/Service.ts b/services/request/src/server/services/Service.ts deleted file mode 100644 index 61c0d2a..0000000 --- a/services/request/src/server/services/Service.ts +++ /dev/null @@ -1,58 +0,0 @@ -import Application from "../Application"; - -export default abstract class Service -{ - /** - * The name of the service - */ - public readonly name: string; - - /** - * The application instance - */ - protected readonly app: Application; - - /** - * Enable/disable logging for this service - */ - public logging: boolean = true; - - /** - * Create a new service - */ - public constructor(name: string, app: Application) { - this.app = app; - this.name = name; - } - - // Required Service Implementation ------------------------------------------------------------- - - /** - * Boot the service - */ - public abstract boot(): Promise; - - /** - * Shut the application down - */ - public abstract shutdown(): Promise; - - // Miscellaneous ------------------------------------------------------------------------------ - - /** - * Indicate the application is ready - */ - public start() { - // no-op - }; - - /** - * Service-specific logging - */ - public log(...args: any[]) { - if (!this.logging) { - return; - } - console.log(`[${this.name}]:`, ...args); - } -} diff --git a/services/request/src/server/services/TvDb.ts b/services/request/src/server/services/TvDb.ts index db8a950..352b800 100644 --- a/services/request/src/server/services/TvDb.ts +++ b/services/request/src/server/services/TvDb.ts @@ -1,15 +1,14 @@ -import { readFile } from "fs/promises"; -import Application from "@server/Application"; import TVDB from "tvdb-v4"; -import Service from "./Service"; +import { InternalService } from "@autoplex/microservice"; import { env, secret } from "@autoplex/utils"; +import Application from "@server/Application"; /** * The token refresh period in milliseconds */ const TOKEN_REFRESH_PERIOD = 1000*60*parseInt(env("TVDB_REFRESH_PERIOD")); -export default class TvDb extends Service +export default class TvDb extends InternalService { /** * The active TVDB instance @@ -22,11 +21,9 @@ export default class TvDb extends Service protected nextTokenRefreshTimestamp: number = 0; /** - * Create a new TvDb service instance + * The service name */ - public constructor(app: Application) { - super("TVDB", app); - } + public get name() { return "TVDB" } /** * Boot the service diff --git a/services/request/src/server/services/WebServer/index.ts b/services/request/src/server/services/WebServer/index.ts index 1960ef4..4b8dd7f 100644 --- a/services/request/src/server/services/WebServer/index.ts +++ b/services/request/src/server/services/WebServer/index.ts @@ -5,13 +5,13 @@ import fastifyHttpProxy from "fastify-http-proxy"; import fastifyMultipart from "fastify-multipart"; import fastifyStatic from "fastify-static"; import Application from "@server/Application"; -import Service from "../Service"; +import { InternalService } from "@autoplex/microservice"; import { join } from "path"; import routes from "./routes"; import "./validators"; import RouteRegisterFactory, { RouteFactory } from "./routes/RouteRegisterFactory"; -export default class WebServer extends Service +export default class WebServer extends InternalService { /** * The port to host the webserver on @@ -27,11 +27,13 @@ export default class WebServer extends Service * Create a new webserver instance */ public constructor(app: Application) { - super("Web Server", app); + super(app); this.port = parseInt(process.env["WEBSERVER_PORT"]); this.fastify = fastify(); } + public get name() { return "Web Server" } + /** * Register required Fastify plugins */