import { Service, ServiceBroker, Context } from "moleculer"; import ApiGateway from "moleculer-web"; import { extractCoverFromFile } from "../utils/uncompression.utils"; import { map } from "lodash"; const IO = require("socket.io")(); export default class ApiService extends Service { public constructor(broker: ServiceBroker) { super(broker); // @ts-ignore this.parseServiceSchema({ name: "api", mixins: [ApiGateway], // More info about settings: https://moleculer.services/docs/0.14/moleculer-web.html settings: { port: process.env.PORT || 3000, routes: [{ path: "/api", whitelist: [ // Access to any actions in all services under "/api" URL "**", ], cors: { origin: "*", methods: [ "GET", "OPTIONS", "POST", "PUT", "DELETE", ], allowedHeaders: ["*"], exposedHeaders: [], credentials: false, maxAge: 3600, }, // Route-level Express middlewares. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Middlewares use: [], // Enable/disable parameter merging method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Disable-merging mergeParams: true, // Enable authentication. Implement the logic into `authenticate` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authentication authentication: false, // Enable authorization. Implement the logic into `authorize` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authorization authorization: false, // The auto-alias feature allows you to declare your route alias directly in your services. // The gateway will dynamically build the full routes from service schema. autoAliases: true, aliases:{}, /** * Before call hook. You can check the request. * @param {Context} ctx * @param {Object} route * @param {IncomingMessage} req * @param {ServerResponse} res * @param {Object} data onBeforeCall(ctx: Context, route: object, req: IncomingMessage, res: ServerResponse) { Set request headers to context meta ctx.meta.userAgent = req.headers["user-agent"]; }, */ /** * After call hook. You can modify the data. * @param {Context} ctx * @param {Object} route * @param {IncomingMessage} req * @param {ServerResponse} res * @param {Object} data * onAfterCall(ctx: Context, route: object, req: IncomingMessage, res: ServerResponse, data: object) { // Async function which return with Promise return doSomething(ctx, res, data); }, */ // Calling options. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Calling-options callingOptions: {}, bodyParsers: { json: { strict: false, limit: "1MB", }, urlencoded: { extended: true, limit: "1MB", }, }, // Mapping policy setting. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Mapping-policy mappingPolicy: "all", // Available values: "all", "restrict" // Enable/disable logging logging: true, }], // Do not log client side errors (does not log an error response when the error.code is 400<=X<500) log4XXResponses: false, // Logging the request parameters. Set to any log level to enable it. E.g. "info" logRequestParams: null, // Logging the response data. Set to any log level to enable it. E.g. "info" logResponseData: null, // Serve assets from "public" folder assets: { folder: "public", // Options to `server-static` module options: {}, }, }, events: { "**"(payload, sender, event) { if (this.io) this.io.emit("event", { sender, event, payload, }); }, }, methods: { /** * Authenticate the request. It checks the `Authorization` token value in the request header. * Check the token value & resolve the user by the token. * The resolved user will be available in `ctx.meta.user` * * PLEASE NOTE, IT'S JUST AN EXAMPLE IMPLEMENTATION. DO NOT USE IN PRODUCTION! * * @param {Context} ctx * @param {any} route * @param {IncomingMessage} req * @returns {Promise} async authenticate (ctx: Context, route: any, req: IncomingMessage): Promise < any > => { // Read the token from header const auth = req.headers.authorization; if (auth && auth.startsWith("Bearer")) { const token = auth.slice(7); // Check the token. Tip: call a service which verify the token. E.g. `accounts.resolveToken` if (token === "123456") { // Returns the resolved user. It will be set to the `ctx.meta.user` return { id: 1, name: "John Doe", }; } else { // Invalid token throw new ApiGateway.Errors.UnAuthorizedError(ApiGateway.Errors.ERR_INVALID_TOKEN, { error: "Invalid Token", }); } } else { // No token. Throw an error or do nothing if anonymous access is allowed. // Throw new E.UnAuthorizedError(E.ERR_NO_TOKEN); return null; } }, */ /** * Authorize the request. Check that the authenticated user has right to access the resource. * * PLEASE NOTE, IT'S JUST AN EXAMPLE IMPLEMENTATION. DO NOT USE IN PRODUCTION! * * @param {Context} ctx * @param {Object} route * @param {IncomingMessage} req * @returns {Promise} async authorize (ctx: Context < any, { user: string; } > , route: Record, req: IncomingMessage): Promise < any > => { // Get the authenticated user. const user = ctx.meta.user; // It check the `auth` property in action schema. // @ts-ignore if (req.$action.auth === "required" && !user) { throw new ApiGateway.Errors.UnAuthorizedError("NO_RIGHTS", { error: "Unauthorized", }); } }, */ }, started(): any { // Create a Socket.IO instance, passing it our server this.io = IO.listen(this.server); // Add a connect listener this.io.on("connection", (client) => { this.logger.info("Client connected via websocket!"); client.on( "importComicsToDB", async ({ action, params, opts }, done) => { this.logger.info( "Received request from client! Action:", action, ", Params:", params ); const { extractionOptions, walkedFolders } = params; map(walkedFolders, async (folder, idx) => { let comicBookCoverMetadata = await extractCoverFromFile( extractionOptions, folder ); const dbImportResult = await this.broker.call( "import.rawImportToDB", { importStatus: { isImported: true, tagged: false, matchedResult: { score: "0", }, }, rawFileDetails: comicBookCoverMetadata, sourcedMetadata: { comicvine: {}, }, }, {} ); client.emit("comicBookCoverMetadata", { comicBookCoverMetadata, dbImportResult, }); }); } ); client.on("disconnect", () => { this.logger.info("Client disconnected"); }); }); }, }); } }