Browse Source

Request uses request API package. Common modules are gone, therefore no need for server folder anymore. Request module uses the microservices properly

dev
David Ludwig 4 years ago
parent
commit
ebc2b89cb5
37 changed files with 124 additions and 947 deletions
  1. +2
    -2
      services/request/nodemon.json
  2. +4
    -1
      services/request/package.json
  3. +0
    -0
      services/request/src/Application.ts
  4. +0
    -0
      services/request/src/common.ts
  5. +0
    -90
      services/request/src/common/api_schema.ts
  6. +0
    -90
      services/request/src/common/validation.ts
  7. +0
    -0
      services/request/src/index.ts
  8. +0
    -48
      services/request/src/lib/plex/index.ts
  9. +0
    -112
      services/request/src/lib/plex/request.ts
  10. +0
    -12
      services/request/src/lib/plex/schema.ts
  11. +0
    -38
      services/request/src/lib/tmdb/index.ts
  12. +0
    -126
      services/request/src/lib/tmdb/request.ts
  13. +0
    -89
      services/request/src/lib/tmdb/schema.ts
  14. +0
    -176
      services/request/src/server/services/MovieSearch.ts
  15. +0
    -64
      services/request/src/server/services/PlexLibrary.ts
  16. +0
    -58
      services/request/src/server/services/TvDb.ts
  17. +0
    -16
      services/request/src/server/services/index.ts
  18. +2
    -2
      services/request/src/services/DiscordBot.ts
  19. +86
    -0
      services/request/src/services/MovieSearch.ts
  20. +3
    -3
      services/request/src/services/WebServer/WebServer.ts
  21. +4
    -5
      services/request/src/services/WebServer/middleware/auth.ts
  22. +0
    -0
      services/request/src/services/WebServer/middleware/index.ts
  23. +0
    -0
      services/request/src/services/WebServer/requests/LinkDiscordRequest.ts
  24. +2
    -2
      services/request/src/services/WebServer/requests/LoginRequest.ts
  25. +2
    -2
      services/request/src/services/WebServer/requests/MovieSearchRequest.ts
  26. +2
    -2
      services/request/src/services/WebServer/requests/RegisterRequest.ts
  27. +0
    -0
      services/request/src/services/WebServer/requests/RequestImdbMovieRequest.ts
  28. +0
    -0
      services/request/src/services/WebServer/requests/RequestMovieRequest.ts
  29. +0
    -0
      services/request/src/services/WebServer/requests/RequestTmdbMovieRequest.ts
  30. +0
    -0
      services/request/src/services/WebServer/requests/index.ts
  31. +2
    -2
      services/request/src/services/WebServer/routes/api.ts
  32. +1
    -1
      services/request/src/services/WebServer/routes/auth.ts
  33. +0
    -0
      services/request/src/services/WebServer/routes/index.ts
  34. +1
    -1
      services/request/src/services/WebServer/routes/web.ts
  35. +0
    -0
      services/request/src/services/WebServer/validators.ts
  36. +13
    -0
      services/request/src/services/index.ts
  37. +0
    -5
      services/request/tsconfig.json

+ 2
- 2
services/request/nodemon.json View File

@ -1,8 +1,8 @@
{
"watch": ["src/common", "src/server"],
"watch": ["src"],
"ext": "ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "node --inspect=0.0.0.0:9229 -r tsconfig-paths/register -r ts-node/register src/server/index.ts",
"exec": "node --inspect=0.0.0.0:9229 -r tsconfig-paths/register -r ts-node/register src/index.ts",
"events": {
"start": "clear"
}


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

@ -4,7 +4,7 @@
"keywords": [],
"author": "David Ludwig",
"license": "ISC",
"main": "./dist/server/index.js",
"main": "./dist/index.js",
"scripts": {
"clean": "rimraf ./dist",
"build": "ttsc",
@ -12,6 +12,9 @@
"start:dev": "nodemon"
},
"dependencies": {
"@autoplex-api/plex": "^0.0.0",
"@autoplex-api/request": "^0.0.0",
"@autoplex-api/search": "^0.0.0",
"@autoplex-api/seeker": "^0.0.0",
"@autoplex/database": "^0.0.0",
"@autoplex/ipc": "^0.0.0",


services/request/src/server/Application.ts → services/request/src/Application.ts View File


services/request/src/server/common.ts → services/request/src/common.ts View File


+ 0
- 90
services/request/src/common/api_schema.ts View File

@ -1,90 +0,0 @@
import { IMovieDetails, IPaginatedResponse } from "@lib/tmdb/schema";
/**
* Basic user information schema
*/
export interface IUser {
id : number,
name : string,
isAdmin: boolean
}
/**
* The JWT auth token structure
*/
export interface ITokenSchema extends IUser {
iat : number,
exp : number
}
/**
* The general API response structure
*/
export interface IApiResponse {
status: string
}
/**
* A generic data response from the API
*/
export interface IApiDataResponse<T> extends IApiResponse {
data: T
}
export interface IApiPaginatedResponse<T> {
page : number,
results : T[],
totalPages : number,
totalResults: number
};
/**
* A movie listing returned from the API
*/
export interface IApiMovie {
plexLink : string | null,
posterPath : string | null,
releaseDate: string | null,
ticketId : number | null,
title : string,
tmdbId : number
}
/**
* Movie details returned from the API
*/
export interface IApiMovieDetails extends IApiMovie {
backdropPath: string | null,
imdbId : string | null,
overview : string | null,
runtime : number | null,
requestedBy : IUser | null
}
/**
* Movie detail data
*/
// export interface IApiMovieDetails extends Pick<IMovieDetails, "title" | "overview" | "runtime" |
// "release_date" | "imdb_id" | "backdrop_path" | "poster_path">
// {
// is_requested: boolean
// }
/**
*
*/
// export type IApiMovieDetailsResponse = IApiDataResponse<IApiMovieDetails>;
// export interface IApiMovieTicket {
// id : number,
// tmdbId : number,
// imdbId : number,
// title : string,
// overview : string,
// posterPath : string,
// backdropPath: string,
// runtime : number,
// releaseDate : string,
// }
// export type IApiMovieTicketResponse = IApiDataResponse<IApiMovieTicket[]>;

+ 0
- 90
services/request/src/common/validation.ts View File

@ -1,90 +0,0 @@
export const constraints = {
api: {
movie: {
search: {
query: {
presence: {
allowEmpty: false,
message: "The query cannot be blank"
}
},
year: {
numericality: {
onlyInteger: true,
greaterThan: 0,
notGreaterThan: "Invalid year",
notValid: "Invalid year",
notInteger: "Invalid year"
}
}
}
}
},
login: {
email: {
presence: {
allowEmpty: false,
message: "An email address is required"
}
},
password: {
presence: {
allowEmpty: false,
message: "A password is required"
}
}
},
register: {
token: {
presence: {
message: "A valid token is required to register"
},
token: {
message: "A valid token is required to register"
}
},
name: {
presence: {
allowEmpty: false,
message: "Your name is required"
},
length: {
maximum: 50,
tooLong: "Your name cannot exceed 50 characters"
}
},
email: {
presence: {
allowEmpty: false,
message: "Your email is required"
},
length: {
maximum: 255,
tooLong: "An email address cannot exceed 255 characters"
},
email: {
message: "A valid email address is required"
}
},
password: {
presence: {
allowEmpty: false,
message: "A password is required"
},
length: {
minimum: 8,
tooShort: "Password should be at least 8 characters"
}
},
retypePassword: {
presence: {
allowEmpty: false,
message: "Re-type your password to confirm it"
},
equality: {
attribute: "password",
message: "Passwords must match"
}
}
}
};

services/request/src/server/index.ts → services/request/src/index.ts View File


+ 0
- 48
services/request/src/lib/plex/index.ts View File

@ -1,48 +0,0 @@
import { parseStringPromise } from "xml2js";
import { readFileSync } from "fs";
import ApiRequestManager from "./request";
import { ILibraryContents } from "./schema";
/**
* Map a media ID such as an IMDb ID to a Plex movie key
*/
interface IMediaMap {
[id: string]: number
}
/**
* An interface to the a Plex server
*/
export default class Plex
{
/**
* The API request manager instance
*/
protected requestManager: ApiRequestManager;
/**
* Create a new instance of the Plex interface
*/
public constructor(plexToken: string) {
this.requestManager = new ApiRequestManager(plexToken);
}
/**
* Fetch the IMDb ID set from the Plex movies library
*/
public async movies(key: string|number) {
let response = await this.requestManager.get(`/library/sections/${key}/all`);
let library = <ILibraryContents>await parseStringPromise(response);
let movies: IMediaMap = {};
for (let video of library.MediaContainer.Video) {
let imdbMatch = video.$.guid.match(/(?<=imdb\:\/\/)tt\d+/i);
if (imdbMatch === null) {
continue;
}
let imdbId = imdbMatch[0];
let plexKey = parseInt(video.$.ratingKey);
movies[imdbId] = plexKey;
}
return movies;
}
}

+ 0
- 112
services/request/src/lib/plex/request.ts View File

@ -1,112 +0,0 @@
import https, { RequestOptions } from "https";
/**
* The URL to the Plex server
*/
const API_URL = <string>process.env["PLEX_URL"];
/**
* A status error is used to indicate responses with non-200 status codes
*/
export class StatusError<T = any> extends Error
{
/**
* The resulting body of a response
*/
public readonly response: T;
/**
* The resulting status code of a response
*/
public readonly statusCode?: number;
/**
* Create a new error indicating non-200 status
*/
public constructor(response: T, statusCode: number) {
super();
Object.setPrototypeOf(this, StatusError.prototype);
this.response = response;
this.statusCode = statusCode;
}
}
/**
* A request manager with atomic/persistent request options
*/
export default class ApiRequestManager
{
/**
* The authorization token
*/
private __plexToken: string;
/**
* Store additional request options
*/
protected options: RequestOptions;
/**
* Create a new API request manager
*
* @param options Additional request options
*/
public constructor(plexToken: string, options: RequestOptions = {}) {
this.__plexToken = plexToken;
this.options = options;
}
/**
* Perform a generic HTTPS request
*
* @param method The HTTP method
* @param url The URL to request
* @param apiKey An optional bearer token
* @param params Optional parameters
* @param body Optional body
*/
protected request(method: string, url: string, params?: any)
{
return new Promise<string>((resolve, reject) => {
// Create request options
let options = Object.assign({ method, headers: {} }, this.options);
// Add search parameters if necessary
let requestUrl = new URL(url);
requestUrl.searchParams.set("X-Plex-Token", this.__plexToken);
if (params) {
Object.keys(params).forEach((key) => {
if (params[key] !== undefined) {
requestUrl.searchParams.set(key, params[key]);
}
});
}
// Create the request
let request = https.request(<any>requestUrl, options, (res) => {
let rawData: string = "";
res.setEncoding("utf8");
res.on("data", chunk => {rawData += chunk});
res.on("error", reject);
res.on("end", async () => {
rawData;
if (res.statusCode == 200) {
resolve(rawData)
} else {
reject(new StatusError(rawData, <number>res.statusCode));
}
});
})
.on("error", reject)
.on("timeout", () => reject("timeout"));
request.end();
});
}
/**
* Perform a generic GET request
*/
public async get(path: string, params?: any) {
return await this.request("GET", `${API_URL}${path}`, params);
}
}

+ 0
- 12
services/request/src/lib/plex/schema.ts View File

@ -1,12 +0,0 @@
export interface IVideo {
$: {
ratingKey: string,
guid: string
}
}
export interface ILibraryContents {
MediaContainer: {
Video: IVideo[]
}
}

+ 0
- 38
services/request/src/lib/tmdb/index.ts View File

@ -1,38 +0,0 @@
import ApiRequestManager from "./request"
import * as Schema from "./schema";
export enum ExternalSource {
Facebook = "facebook_id",
Freebase = "freebase_id",
FreebaseM = "freebase_mid",
Imdb = "imdb_id",
Instagram = "instagram_id",
Tvdb = "tvdb_id",
TvRage = "tvrage_id",
Twitter = "twitter_id"
}
export default class TheMovieDb
{
protected requestManager!: ApiRequestManager;
public constructor(apiKey: string) {
this.requestManager = new ApiRequestManager(apiKey);
}
public async configuration() {
return await this.requestManager.get("/configuration");
}
public async searchMovie(query: string, year?: number, page?: number) {
return await this.requestManager.get<Schema.IPaginatedResponse<Schema.IMovieSearchResult>>("/search/movie", { query, year });
}
public async movie(id: number) {
return await this.requestManager.get<Schema.IMovieDetails>(`/movie/${id}`);
}
public async findMovie(id: string, externalSource: ExternalSource) {
return await this.requestManager.get<Schema.IFindResult>(`/find/${id}`, { external_source: externalSource });
}
}

+ 0
- 126
services/request/src/lib/tmdb/request.ts View File

@ -1,126 +0,0 @@
import https, { RequestOptions } from "https";
/**
* The API URL
*/
const API_URL = "https://api.themoviedb.org/3";
/**
* A status error is used to indicate responses with non-200 status codes
*/
export class StatusError<T = any> extends Error
{
/**
* The resulting body of a response
*/
public readonly response: T;
/**
* The resulting status code of a response
*/
public readonly statusCode?: number;
/**
* Create a new error indicating non-200 status
*/
public constructor(response: T, statusCode: number) {
super();
Object.setPrototypeOf(this, StatusError.prototype);
this.response = response;
this.statusCode = statusCode;
}
}
/**
* A request manager with atomic/persistent request options
*/
export default class ApiRequestManager
{
private __api_key: string;
/**
* Store additional request options
*/
protected options: RequestOptions;
/**
* Create a new API request manager
*
* @param options Additional request options
*/
public constructor(apiKey: string, options: RequestOptions = {}) {
this.__api_key = apiKey;
this.options = options;
}
/**
* Perform a generic HTTPS request
*
* @param method The HTTP method
* @param url The URL to request
* @param apiKey An optional bearer token
* @param params Optional parameters
* @param body Optional body
*/
protected request<T>(method: string, url: string, params?: any, body?: string)
{
return new Promise<T>((resolve, reject) => {
// Create request options
let options = Object.assign({ method, headers: {} }, this.options);
if (body) {
options.headers["Content-Type"] = "application/json";
options.headers["Content-Length"] = body.length;
}
// Add search parameters if necessary
let requestUrl = new URL(url);
requestUrl.searchParams.set("api_key", this.__api_key);
if (params) {
Object.keys(params).forEach((key) => {
if (params[key] !== undefined) {
requestUrl.searchParams.set(key, params[key]);
}
});
}
// Create the request
let request = https.request(<any>requestUrl, options, (res) => {
let rawData: string = "";
res.setEncoding("utf8");
res.on("data", chunk => {rawData += chunk});
res.on("error", reject);
res.on("end", () => {
let response: T = JSON.parse(rawData);
if (res.statusCode == 200) {
resolve(response)
} else {
reject(new StatusError(response, <number>res.statusCode));
}
});
})
.on("error", reject)
.on("timeout", () => reject("timeout"));
if (body) {
request.write(body);
}
request.end();
});
}
/**
* Perform a generic GET request
*/
public async get<T = any>(path: string, params?: any) {
return await this.request<T>("GET", `${API_URL}${path}`, params);
}
/**
* Perform a generic POST request
*/
public async post<T = any>(path: string, params?: any, body?: any) {
if (body !== undefined) {
body = JSON.stringify(body);
}
return await this.request<T>("POST", `${API_URL}${path}`, params, body);
}
}

+ 0
- 89
services/request/src/lib/tmdb/schema.ts View File

@ -1,89 +0,0 @@
export enum Status {
Rumored = "Rumored",
Planned = "Planned",
InProduction = "InProduction",
PostProduction = "PostProduction",
Released = "Released",
Canceled = "Canceled"
}
export interface IGenre {
id : number,
name: string
}
export interface ILanguage {
iso_639_1: string,
name: string
}
export interface IFindResult {
movie_results : IMovieSearchResult[],
person_results : unknown,
tv_results : unknown,
tv_episode_results: unknown,
tv_season_results : unknown
}
export interface IMovieSearchResult {
adult : boolean,
backdrop_path : string | null,
genre_ids : number[],
id : number,
original_string: string,
original_title : string,
overview : string,
popularity : number,
poster_path : string | null,
release_date : string,
title : string,
video : boolean,
vote_average : number
vote_count : number,
}
export interface IMovieDetails {
adult : boolean,
backdrop_path : string | null,
belongs_to_collection: any,
budget : number,
genres : IGenre[]
imdb_id : string | null,
original_language : string,
original_title : string,
overview : string | null,
popularity : number,
poster_path : string | null,
production_companies : IProductionCompany[],
production_countries : IProductionCountry[],
release_date : string,
revenue : number,
runtime : number | null,
spoken_languages : ILanguage[],
status : Status,
tagline : string | null,
title : string,
video : boolean,
vote_average : number,
vote_count : number
}
export interface IProductionCompany {
name : string,
id : number,
logo_path : string | null,
origin_country: string
}
export interface IProductionCountry {
release_date: string,
revenue : number,
runtime : number | null
}
export interface IPaginatedResponse<T> {
page : number,
results : T[],
total_results: number,
total_pages : number
}

+ 0
- 176
services/request/src/server/services/MovieSearch.ts View File

@ -1,176 +0,0 @@
import TheMovieDb, { ExternalSource } from "@lib/tmdb";
import { env, plexMediaUrl, secret } from "@autoplex/utils";
import { request } from "https";
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 InternalService<Application>
{
/**
* A reference to The Movie DB API
*/
protected tmdb!: TheMovieDb;
/**
* The instance of TVDB
*/
protected tvdb!: TvDb;
/**
* Hold a cache of recently fetched movies to speed up request times
*/
protected movieCache: { [tmdbId: number]: { timestamp: number, movie: IApiMovieDetails } } = {};
/**
* Hold the clear cache interval reference
*/
private __clearCacheInterval: NodeJS.Timeout|null = null;
/**
* The name of the service
*/
public readonly NAME = "Movie Search";
/**
* Start the service
*/
public start() {
this.tvdb = this.app.service<TvDb>("TVDB");
}
/**
* Boot the service
*/
public async boot() {
let apiKey = await secret(env("TMDB_KEY_FILE"));
this.tmdb = new TheMovieDb(apiKey);
}
/**
* Store an IMDb ID in cache
*/
protected cacheMovie(tmdbId: number, movie: IApiMovieDetails) {
this.movieCache[tmdbId] = { movie, timestamp: Date.now() + CACHE_CLEAR_INTERVAL };
if (this.__clearCacheInterval === null) {
this.__clearCacheInterval = setInterval(() => this.cleanMovieCache(), CACHE_CLEAR_INTERVAL);
}
return this.movieCache[tmdbId];
}
/**
* Clean the IMDb cache
*/
protected cleanMovieCache() {
let now = Date.now();
let remaining = 0;
for (let key in this.movieCache) {
if (now > this.movieCache[key].timestamp) {
delete this.movieCache[key];
} else {
remaining++;
}
}
if (remaining == 0) {
clearInterval(<NodeJS.Timeout>this.__clearCacheInterval);
this.__clearCacheInterval = null;
}
}
// Interface -----------------------------------------------------------------------------------
/**
* Verify the IMDb ID exists
*/
public verifyImdbId(id: string) {
return new Promise<boolean>((resolve, reject) => {
let req = request({ method: "HEAD", host: "www.imdb.com", path: `/title/${id}/` }, (response) => {
response.resume();
if (response.statusCode == undefined) {
reject();
return;
}
resolve(response.statusCode === 200);
response.destroy();
});
req.end();
});
}
/**
* Get the details of a movie
*/
public async details(id: number) {
if (id in this.movieCache) {
if (this.movieCache[id].movie.ticketId == null) {
let ticket = await MovieTicket.findOne({ where: { tmdbId: id, isCanceled: false }, relations: ["user"] });
this.movieCache[id].movie.ticketId = ticket?.id ?? null;
this.movieCache[id].movie.requestedBy = ticket ? {
id : ticket.user.id,
isAdmin: ticket.user.isAdmin,
name : ticket.user.name
} : null;
}
return this.movieCache[id].movie;
}
let fetchMovieRequest = this.tmdb.movie(id);
let ticket = await MovieTicket.findOne({ where: { tmdbId: id, isCanceled: false }, relations: ["user"] });
let movie = await fetchMovieRequest;
let plexKey = await PlexMovie.findPlexKey(id);
let result: IApiMovieDetails = {
tmdbId : id,
backdropPath: movie.backdrop_path,
imdbId : movie.imdb_id,
overview : movie.overview,
plexLink : plexKey !== null ? plexMediaUrl(plexKey) : null,
posterPath : movie.poster_path,
releaseDate : movie.release_date,
runtime : movie.runtime,
title : movie.title,
ticketId : ticket?.id ?? null,
requestedBy : (ticket ? {
id : ticket.user.id,
isAdmin: ticket.user.isAdmin,
name : ticket.user.name
} : null)
};
return this.cacheMovie(id, result).movie;
}
/**
* Find a movie by its IMDb ID
*/
public async findImdb(imdbId: string): Promise<IMovieSearchResult|null> {
return (await this.tmdb.findMovie(imdbId, ExternalSource.Imdb)).movie_results[0] ?? null;
}
/**
* Search for a movie
*/
public async search(query: string, year?: number) {
let movieFetchRequest = this.tmdb.searchMovie(query, year);
let activeTickets = await MovieTicket.activeTicketMap();
let results = await movieFetchRequest;
let plexKeys = await Promise.all(results.results.map(
movie => PlexMovie.findPlexKey(movie.id)
));
return <IApiPaginatedResponse<IApiMovie>>{
page: results.page,
results: results.results.map((movie, index) => <IApiMovie>{
plexLink : plexKeys[index] !== null ? plexMediaUrl(<number>plexKeys[index]) : null,
posterPath : movie.poster_path,
releaseDate: movie.release_date,
ticketId : activeTickets[movie.id] ?? null,
title : movie.title,
tmdbId : movie.id
}),
totalPages: results.total_pages,
totalResults: results.total_results
};
}
}

+ 0
- 64
services/request/src/server/services/PlexLibrary.ts View File

@ -1,64 +0,0 @@
import Plex from "@lib/plex";
import { PlexMovie } from "@autoplex/database";
import { env, secret, sleep } from "@autoplex/utils";
import MovieSearch from "./MovieSearch";
import { InternalService } from "@autoplex/microservice";
import Application from "@server/Application";
/**
* Throttle requests when updating the movie database
*/
const API_DATABASE_THROTTLE = 500;
/**
* A service for maintaining an internal representation of the Plex library
*/
export default class PlexLibrary extends InternalService<Application>
{
/**
* A reference to the Plex library
*/
protected plex!: Plex;
/**
* The key for the movies library
*/
protected readonly KEY_MOVIES: string = env("PLEX_LIBRARY_MOVIES_KEY");
/**
* The key for the TV shows library
*/
protected readonly KEY_TV: string = env("PLEX_LIBRARY_TV_KEY");
/**
* Indicate if the plex library is currently being updated
*/
protected isUpdating: boolean = false;
/**
* The service name
*/
public readonly NAME = "Plex Library";
/**
* Boot the Plex library service
*/
public async boot() {
let token = await secret(env("PLEX_TOKEN_FILE"));
this.plex = new Plex(token);
}
/**
* Update the movies after boot
*/
public start() {
this.updateMovies();
}
/**
* Update the movie catalog
*/
public async updateMovies() {
}
}

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

@ -1,58 +0,0 @@
import TVDB from "tvdb-v4";
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 InternalService<Application>
{
/**
* The active TVDB instance
*/
protected tvdb!: TVDB;
/**
* Store the next timestamp when a token refresh is needed
*/
protected nextTokenRefreshTimestamp: number = 0;
/**
* The service name
*/
public readonly NAME = "TVDB";
/**
* Boot the service
*/
public async boot() {
let apiKey = await secret(env("TVDB_KEY_FILE"));
this.tvdb = new TVDB(apiKey);
await this.refreshLogin();
}
/**
* Refresh the login token if necessary
*/
protected async refreshLogin() {
if (Date.now() < this.nextTokenRefreshTimestamp) {
return;
}
this.log("Refreshing login token...");
let pin = await secret(env("TVDB_PIN_FILE"));
let timestamp = Date.now() + TOKEN_REFRESH_PERIOD; // Save the time before the request
await this.tvdb.login(pin);
this.nextTokenRefreshTimestamp = timestamp
}
/**
* Search for a movie
*/
public async searchMovie(query: string, year?: number) {
await this.refreshLogin();
return await this.tvdb.search(query, "movie", year);
}
}

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

@ -1,16 +0,0 @@
export { IpcClient } from "@autoplex-api/seeker";
export { DatabaseService } from "@autoplex/database";
import DiscordBot from "./DiscordBot";
import MovieSearch from "./MovieSearch";
import PlexLibrary from "./PlexLibrary";
import TvDb from "./TvDb";
import WebServer from "./WebServer/WebServer";
export {
// DiscordBot,
MovieSearch,
PlexLibrary,
// TorrentIpcClient,
TvDb,
WebServer
}

services/request/src/server/services/DiscordBot.ts → services/request/src/services/DiscordBot.ts View File


+ 86
- 0
services/request/src/services/MovieSearch.ts View File

@ -0,0 +1,86 @@
import { MovieTicket } from "@autoplex/database";
import { IpcClient as PlexIpc } from "@autoplex-api/plex";
import { IMovie, IMovieDetails, IPaginatedResponse } from "@autoplex-api/request";
import { IpcClient as SearchIpc } from "@autoplex-api/search";
import { InternalService } from "@autoplex/microservice";
import Application from "../Application";
export default class MovieSearch extends InternalService<Application>
{
/**
* The name of the service
*/
public readonly NAME = "Movie Search";
/**
* The Plex IPC interface
*/
protected plex!: PlexIpc;
/**
* A reference to The Movie DB API
*/
protected searchIpc!: SearchIpc;
/**
* Start the service
*/
public start() {
this.plex = this.app.service<PlexIpc>("Plex");
this.searchIpc = this.app.service<SearchIpc>("Search");
}
// Interface -----------------------------------------------------------------------------------
/**
* Get the details of a movie
*/
public async details(tmdbId: number) {
let [ movie, plexLinks, ticket ] = await Promise.all([
this.searchIpc.movieDetails(tmdbId),
this.plex.movieLinks([tmdbId]),
MovieTicket.findOne({
where: { tmdbId: tmdbId, isCanceled: false },
relations: ["user"]
})
]);
return <IMovieDetails>Object.assign(movie, {
plexLink: plexLinks[tmdbId],
ticketId : ticket?.id ?? null,
requestedBy : (ticket ? {
id : ticket.user.id,
isAdmin: ticket.user.isAdmin,
name : ticket.user.name
} : null)
});
}
/**
* Find a movie by its IMDb ID
*/
public async findImdb(imdbId: string) {
return await this.searchIpc.findMovieFromImdb(imdbId);
}
/**
* Search for a movie
*/
public async search(query: string, year?: number) {
this.log("Searching for John Wick...");
let [ movies, ticketMap ] = await Promise.all([
this.searchIpc.searchMovie(query, year),
MovieTicket.activeTicketMap()
]);
let plexLinks = await this.plex.movieLinks(movies.results.map(movie => movie.tmdbId));
return <IPaginatedResponse<IMovie>>{
page: movies.page,
results: movies.results.map(movie => <IMovie>{
...movie,
plexLink: plexLinks[movie.tmdbId],
ticketId : ticketMap[movie.tmdbId] ?? null
}),
totalPages: movies.totalPages,
totalResults: movies.totalPages
};
}
}

services/request/src/server/services/WebServer/WebServer.ts → services/request/src/services/WebServer/WebServer.ts View File


services/request/src/server/services/WebServer/middleware/auth.ts → services/request/src/services/WebServer/middleware/auth.ts View File


services/request/src/server/services/WebServer/middleware/index.ts → services/request/src/services/WebServer/middleware/index.ts View File


services/request/src/server/services/WebServer/requests/LinkDiscordRequest.ts → services/request/src/services/WebServer/requests/LinkDiscordRequest.ts View File


services/request/src/server/services/WebServer/requests/LoginRequest.ts → services/request/src/services/WebServer/requests/LoginRequest.ts View File


services/request/src/server/services/WebServer/requests/MovieSearchRequest.ts → services/request/src/services/WebServer/requests/MovieSearchRequest.ts View File


services/request/src/server/services/WebServer/requests/RegisterRequest.ts → services/request/src/services/WebServer/requests/RegisterRequest.ts View File


services/request/src/server/services/WebServer/requests/RequestImdbMovieRequest.ts → services/request/src/services/WebServer/requests/RequestImdbMovieRequest.ts View File


services/request/src/server/services/WebServer/requests/RequestMovieRequest.ts → services/request/src/services/WebServer/requests/RequestMovieRequest.ts View File


services/request/src/server/services/WebServer/requests/RequestTmdbMovieRequest.ts → services/request/src/services/WebServer/requests/RequestTmdbMovieRequest.ts View File


services/request/src/server/services/WebServer/requests/index.ts → services/request/src/services/WebServer/requests/index.ts View File


services/request/src/server/services/WebServer/routes/api.ts → services/request/src/services/WebServer/routes/api.ts View File


services/request/src/server/services/WebServer/routes/auth.ts → services/request/src/services/WebServer/routes/auth.ts View File


services/request/src/server/services/WebServer/routes/index.ts → services/request/src/services/WebServer/routes/index.ts View File


services/request/src/server/services/WebServer/routes/web.ts → services/request/src/services/WebServer/routes/web.ts View File


services/request/src/server/services/WebServer/validators.ts → services/request/src/services/WebServer/validators.ts View File


+ 13
- 0
services/request/src/services/index.ts View File

@ -0,0 +1,13 @@
export { IpcClient as PlexIpc } from "@autoplex-api/plex";
export { IpcClient as SeekerIpc } from "@autoplex-api/seeker";
export { IpcClient as SearchIpc } from "@autoplex-api/search";
export { DatabaseService } from "@autoplex/database";
import DiscordBot from "./DiscordBot";
import MovieSearch from "./MovieSearch";
import WebServer from "./WebServer/WebServer";
export {
// DiscordBot,
MovieSearch,
WebServer
}

+ 0
- 5
services/request/tsconfig.json View File

@ -2,11 +2,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"@common/*": ["common/*"],
"@lib/*" : ["lib/*"],
"@server/*": ["server/*"]
},
"sourceMap": true, /* Generates corresponding '.map' file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"typeRoots": ["./src/typings"], /* List of folders to include type definitions from. */


Loading…
Cancel
Save