Browse Source

Convert Request service to use the Microservice package

dev
David Ludwig 4 years ago
parent
commit
67250e4a30
13 changed files with 72 additions and 195 deletions
  1. +1
    -0
      services/request/package.json
  2. +7
    -83
      services/request/src/server/Application.ts
  3. +1
    -1
      services/request/src/server/index.ts
  4. +5
    -7
      services/request/src/server/services/Database.ts
  5. +9
    -4
      services/request/src/server/services/DiscordBot.ts
  6. +15
    -4
      services/request/src/server/services/Ipc/IpcClient.ts
  7. +1
    -1
      services/request/src/server/services/Ipc/SeekerIpcClient.ts
  8. +1
    -1
      services/request/src/server/services/Ipc/TorrentIpcClient.ts
  9. +10
    -11
      services/request/src/server/services/MovieSearch.ts
  10. +12
    -14
      services/request/src/server/services/PlexLibrary.ts
  11. +0
    -58
      services/request/src/server/services/Service.ts
  12. +5
    -8
      services/request/src/server/services/TvDb.ts
  13. +5
    -3
      services/request/src/server/services/WebServer/index.ts

+ 1
- 0
services/request/package.json View File

@ -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",


+ 7
- 83
services/request/src/server/Application.ts View File

@ -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 <Application>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<T extends Service>(serviceName: string) {
assert(serviceName in this.services);
return <T>this.services[serviceName];
}
}

+ 1
- 1
services/request/src/server/index.ts View File

@ -14,4 +14,4 @@ let app = new Application(appKey);
/**
* Start the application
*/
app.start();
app.exec();

+ 5
- 7
services/request/src/server/services/Database.ts View File

@ -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<Application>
{
/**
* 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


+ 9
- 4
services/request/src/server/services/DiscordBot.ts View File

@ -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<void>
}
export default class DiscordBot extends Service
export default class DiscordBot extends InternalService<Application>
{
/**
* 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
*/


+ 15
- 4
services/request/src/server/services/Ipc/IpcClient.ts View File

@ -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<Application>
{
/**
* 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
*/


+ 1
- 1
services/request/src/server/services/Ipc/SeekerIpcClient.ts View File

@ -1,5 +1,5 @@
import IpcClient from "./IpcClient";
import Application from "../../Application";
import Application from "@server/Application";
export default class SeekerIpcClient extends IpcClient
{


+ 1
- 1
services/request/src/server/services/Ipc/TorrentIpcClient.ts View File

@ -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
{


+ 10
- 11
services/request/src/server/services/MovieSearch.ts View File

@ -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<Application>
{
/**
* 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


+ 12
- 14
services/request/src/server/services/PlexLibrary.ts View File

@ -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<Application>
{
/**
* 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


+ 0
- 58
services/request/src/server/services/Service.ts View File

@ -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<void>;
/**
* Shut the application down
*/
public abstract shutdown(): Promise<void>;
// 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);
}
}

+ 5
- 8
services/request/src/server/services/TvDb.ts View File

@ -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<Application>
{
/**
* 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


+ 5
- 3
services/request/src/server/services/WebServer/index.ts View File

@ -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<Application>
{
/**
* 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(<string>process.env["WEBSERVER_PORT"]);
this.fastify = fastify();
}
public get name() { return "Web Server" }
/**
* Register required Fastify plugins
*/


Loading…
Cancel
Save