import { IncomingMessage, Server, ServerResponse } from 'http' import { Service, ServiceBroker, Context } from 'moleculer' import ApiGateway from 'moleculer-web' import express, { Application } from 'express' /* Middleware */ import shrinkRay from 'shrink-ray-current' import AdminBro from 'admin-bro' import AdminBroExpress from '@admin-bro/express' import AdminBroMongoose from '@admin-bro/mongoose' import { theme } from '../util/admin.theme' AdminBro.registerAdapter(AdminBroMongoose) import mongoose from 'mongoose' import { mongooseOptions } from '../util/mongoose.options' /* Models */ import '../models/literature-list' /* TODO: ts-env */ const PORT = process.env.PORT ?? 3000 export default class ApiService extends Service { private app: Application private server: Server 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: { server: false, /* Disable integral web server */ routes: [{ whitelist: [ /* Access to all yt actions */ 'youtube.*', /* Read only access to anything that exposes DB operations wwwwwww*/ '*.list', '*.find', '*.get' ], use: [ /* Only use this for things specific to moleculer api gateway */ ], mergeParams: true, authentication: false, 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:{}, // 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 }, methods: { /* NOTE: Leave these in place for easing any future expansion */ /* You can safely fold methods */ /** * Authenticate the request. It check 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', }); } }, */ }, /* Mount lifecycle-events et al here */ created: this.created, started: this.started, stopped: this.stopped }) } async setupAdminBro () { const db = await mongoose.connect(process.env.MONGO_URI, mongooseOptions) const adminBro = new AdminBro({ databases: [db], branding: { companyName: 'Red Plateaus', theme, logo: '/RP_pote_tb.svg', softwareBrothers: false }, rootPath: '/admin' }) return AdminBroExpress.buildRouter(adminBro) } created () { this.app = express() /* Remove things we don't need from here */ this.app.use(express.static('public')) /* Compression */ this.app.use(shrinkRay()) /* Mount Moleculer service gateway*/ this.app.use('/api', this.express()) } async started () { if (process.env.MONGO_URI) { /* Adminbro specifies we have to await Db connection, so */ this.app.use('/admin', await this.setupAdminBro()) } /* Hello my friend, stay a while and */ this.server = this.app.listen(PORT, () => { this.logger.info(`API Gateway listening on ${PORT}`) }) } async stopped () { if (!this.server) this.logger.debug('Server was dead in the water') this.server?.close() /* Server instance might not exist, prevent crashes */ } }