lynx-framework
Version:
lynx is a NodeJS framework for Web Development, based on decorators and the async/await support.
326 lines (301 loc) • 11.1 kB
text/typescript
import * as express from 'express';
import App from './app';
import { FileOptions } from './file.response';
import RenderResponse from './render.response';
import RedirectResponse from './redirect.response';
import SkipResponse from './skip.response';
import UnauthorizedResponse from './unauthorized.response';
import FileResponse from './file.response';
import Request from './request';
import { LynxControllerMetadata } from './decorators';
import Media from './entities/media.entity';
import StatusError from './status-error';
import { logger } from './logger';
import Logger from './logger';
import XmlResponse from './xml.response';
export enum FlashType {
primary,
secondary,
success,
danger,
warning,
info,
light,
dark,
}
function mapFlashTypeToString(type: FlashType): string {
switch (type) {
case FlashType.primary:
return 'primary';
case FlashType.secondary:
return 'secondary';
case FlashType.success:
return 'success';
case FlashType.danger:
return 'danger';
case FlashType.warning:
return 'warning';
case FlashType.info:
return 'info';
case FlashType.light:
return 'light';
case FlashType.dark:
return 'dark';
}
}
export interface FlashMessage {
type: FlashType;
message: string;
}
/**
* This class defines the basic class for any controllers. It implements a lot
* of utility methods in order to correctly generate any response.
*/
export class BaseController {
public app: App;
private _metadata: LynxControllerMetadata;
public logger: Logger = logger;
get metadata(): LynxControllerMetadata {
return this._metadata;
}
constructor(app: App) {
this.app = app;
}
/**
* This method is called only when the constructed has been completed.
* Since this method is async, it can be used to perform some initialization
* that needed the use of the await keyword. */
async postConstructor() {}
/**
* Add a value to the current request context.
* Any variable added with this method will available in the template context
* thought the @method render method.
* @param req the current Request
* @param key the key of the value to add
* @param value the value to add
*/
public addToContext(req: Request, key: string, value: any) {
if (!req.lynxContext) {
req.lynxContext = {};
}
req.lynxContext[key] = value;
}
/**
* Utility method to generate an error with a status code.
* This method should be used instead of the usual throw new Error(msg).
* In this way, a proper HTTP status code can be used (for example, 404 or 500),
* instead of the default 400.
* @param status the http status code to return
* @param message the error message
* @return a new @type StatusError object
*/
public error(status: number, message: string): StatusError {
let err = new StatusError(message);
err.statusCode = status;
return err;
}
/**
* This method generates a url to a route starting from the route name and
* optionally its parameters.
* If a parameter is not used to generate the route url, it will be appended
* as a query parameter.
* @param name the name of the route
* @param parameters a plain object containing the parameters for the route.
*/
public route(name: string, parameters?: any): string {
return this.app.route(name, parameters);
}
/**
* Generate a web page starting from a template and using a generated context.
* @param view the name of the view
* @param req the request object
* @param context a plain object containing any necessary data needed by the view
*/
public render(view: string, req: Request, context?: any): RenderResponse {
if (!view.endsWith('.njk')) {
view = view + '.njk';
}
if (!context) {
context = {};
}
context.req = req;
context.flash = (req.session as any).sessionFlash;
for (let key in req.lynxContext) {
context[key] = req.lynxContext[key];
}
delete (req.session as any).sessionFlash;
return new RenderResponse(view, context);
}
/**
* Redirect the current route to another
* @param routeName the new of the target route
* @param routeParams a plain object containing the parameters for the route.
*/
public redirect(routeName: string, routeParams?: any): RedirectResponse {
return new RedirectResponse(this.route(routeName, routeParams));
}
/**
* Add a flash message in the current request.
* @param msg the FlashMessage to be included
* @param req the request
*/
public addFlashMessage(msg: FlashMessage, req: Request) {
let session = req.session as any;
if (!session.sessionFlash) {
session.sessionFlash = [];
}
session.sessionFlash.push({
type: mapFlashTypeToString(msg.type),
message: this.tr(msg.message, req),
});
}
/**
* Add a success flash message in the current request.
* @param msg the string (can be localized) of the message
* @param req the request
*/
public addSuccessMessage(msg: string, req: Request) {
this.addFlashMessage({ type: FlashType.success, message: msg }, req);
}
/**
* Add an error flash message in the current request.
* @param msg the string (can be localized) of the message
* @param req the request
*/
public addErrorMessage(msg: string, req: Request) {
this.addFlashMessage({ type: FlashType.danger, message: msg }, req);
}
/**
* Generate a response suitable to file download. This method can also be
* used to generate images of specific dimensions.
* @param path the string path of the file, or a Media object to be downloaded
* @param options options to correctly generate the output file
*/
public download(path: string | Media, options?: FileOptions): FileResponse {
let f: FileResponse;
if (path instanceof Media) {
if (path.isDirectory) {
throw new Error('unable to download a directory');
}
f = new FileResponse(path.fileName);
f.contentType = path.mimetype;
if (path.originalName) {
f.fileName = path.originalName;
}
} else {
f = new FileResponse(path);
}
if (options) {
f.options = options;
}
return f;
}
/**
* @deprecated use the `throw this.error(401, 'unauthorized') instead.
* Generate an unauthorized response.
*/
public unauthorized(): UnauthorizedResponse {
return new UnauthorizedResponse();
}
/**
* Generate a skip response. In this particular case, the original Express `next()`
* will be executed, causing the controller chain to continue its execution.
*/
public next(): SkipResponse {
return new SkipResponse();
}
/**
* Generate a response as an Xml file, but starting from a standard Nunjucks template.
* This response is very similar to the standard render response. The main difference is
* the `contentType`, set to `application/xml`.
* Moreover, the flash messages are ignored.
* @param view the name of the view
* @param req the request object
* @param context a plain object containing any necessary data needed by the view
*/
public xml(view: string, req: Request, context?: any): XmlResponse {
if (!view.endsWith('.njk')) {
view = view + '.njk';
}
if (!context) {
context = {};
}
context.req = req;
for (let key in req.lynxContext) {
context[key] = req.lynxContext[key];
}
return new XmlResponse(view, context);
}
/**
* Utility method to send emails from a controller.
* This method is similar to the `sendMail` method, but define a lower level API.
* Indeed, it directly accepts the text and the html of the email, and not the templates urls.
* @param dest the email destination (can also be an array of addresses)
* @param subject the subject of the email
* @param text the text version of the email
* @param html the html version of the email
*/
public async sendRawMail(
dest: string | string[],
subject: string,
text: string,
html: string
) {
return this.app.mailClient.sendRawMail(dest, subject, text, html);
}
/**
* Utility method to send an email from a controller. This method is async,
* so use the await keyword (or eventually a promise) to correctly read the
* return value.
* This method uses the template engine to compile the email.
* NOTE: internally, this method uses the `sendRawMail` method.
* @param req the current request
* @param dest the email destination (can also be an array of addresses)
* @param subjectTemplateString the subject of the email, that can also be a string template
* @param textTemplate the text version of the email, referencing a path in the view folders
* @param htmlTemplate the html version of the email, referencing a path in the view folders
* @param context a plain object containing any necessary data needed by the view
*/
public async sendMail(
req: express.Request,
dest: string | string[],
subjectTemplateString: string,
textTemplate: string,
htmlTemplate: string,
context: any
): Promise<boolean> {
return this.app.mailClient.sendMail(
req,
dest,
subjectTemplateString,
textTemplate,
htmlTemplate,
context
);
}
/**
* Utility method to obtain a translated string.
* @param str the string key to be translated
* @param req the original request
* @param language optionally, the language can be forced using this variable
*/
public tr(str: string, req: Request, language?: string): string {
return this.app.translate(str, req, language);
}
/**
* Utility method to obtain a translated string, formatted with parameters.
* Each parameter should be encoded as {0}, {1}, etc...
* @param str the string key to be translated
* @param req the original request
* @param language optionally, the language can be forced using this variable
* @param args the arguments to format the string
*/
public trFormat(
str: string,
req: Request,
language: string | undefined,
...args: any
): string {
return this.app.translateFormat(str, req, language, args);
}
}