express-mesh
Version:
A Gentics Mesh frontend for Express.
528 lines (483 loc) • 21.3 kB
text/typescript
'use strict';
import Q = require('q');
import express = require('express');
import rest = require('./meshRestClient');
import renderer = require('./meshRenderer');
import filters = require('./meshTemplateFilters');
import lang = require('./meshLanguages');
import u = require('./meshUtil');
import {MeshConfig} from "./config";
import {MeshRenderer} from "./meshRenderer";
import {MeshRestClient} from "./meshRestClient";
import {IMeshSchemaHandler} from "./meshHandlerStore";
import {IMeshViewHandler} from "./meshHandlerStore";
import {IMeshErrorHandler} from "./meshHandlerStore";
import {RenderData} from "./meshRenderer";
import {MeshRestResponse} from "./meshRestClient";
import {IFilterRegisterFunction} from "./meshTemplateFilters";
/**
* Wrapper for the express.Request.
* We can use them to add properties to the request.
*/
export interface IMeshRequest extends express.Request {
meshConfig : MeshConfig;
requestedNode: IMeshNode<any>;
}
/**
* Wrapper for the express.Response.
* We can use them to add properties to the response.
*/
export interface IMeshResponse extends express.Response {
}
/**
* Possible request options for Mesh list requests.
*/
export interface IMeshNodeListQueryParams {
expand?: string;
page?: number;
perPage?: number;
orderBy?: string;
lang?:string
resolveLinks?:string;
maxDepth?:number;
includeAll?: boolean;
}
/**
* Implementation of IMeshNodeListQueryParams.
* Possible request options for Mesh list requests.
*/
export class MeshQueryParams implements IMeshNodeListQueryParams {
public expand : string;
public expandAll : boolean;
public page : number;
public perPage : number;
public orderBy : string;
public lang : string;
public maxDepth : number;
public includeAll : boolean;
}
/**
* Mesh response of a list of Mesh Objects.
*/
export interface IMeshNodeListResponse<T> {
_metainfo : IMeshMetaInfo;
data : Array<T>;
}
/**
* Meta information contained in IMeshNodeListResponse
*/
export interface IMeshMetaInfo {
currentPage: number;
pageCount: number;
perPage: number;
totalCount: number;
}
/**
* Search query structure for the elastic search in Mesh.
*/
export interface IMeshSearchQuery {
sort? : any;
query? : any;
filter? : any;
}
/**
* Mesh object reference.
*/
export interface IMeshRef {
name? : string;
uuid? : string;
}
/**
* Mesh node reference.
*/
export interface IMeshNodeRef {
displayName : string;
uuid : string;
schema : IMeshRef;
}
/**
* Possible mesh permissions.
*/
export enum MeshPermission {
CREATE = <any>"create",
READ = <any>"read",
UPDATE = <any>"update",
DELETE = <any>"delete"
}
/**
* Mesh navigation object.
* The mesh navigation object has a root node, which is a MeshNavElement.
*/
export interface IMeshNav {
root : IMeshNavElement;
}
/**
* MeshNavElement.
* Consists of the uuid of the node, the node itself and its children.
*/
export interface IMeshNavElement {
uuid : string;
node : IMeshNode<any>;
children : Array<IMeshNavElement>;
}
/**
* A Mesh object. This object is the base for more complex objects, such as Mesh Nodes.
*/
export interface IMeshObject {
uuid : string;
creator : IMeshRef;
created : number;
editor : IMeshRef;
edited : number;
permissions : Array<MeshPermission>;
}
/**
* Representation of a tag family in mesh.
*/
export interface IMeshTagFamily extends IMeshObject {
name : string;
}
/**
* Fields node for the MeshTag containing the "value" of the tag as name.
*/
export interface ITagFields {
name : string;
}
/**
* Representation of a MeshTag.
*/
export interface IMeshTag extends IMeshObject {
tagFamily : IMeshRef;
fields : ITagFields;
}
/**
* Representation of a MeshNode.
* This representation is generic, because its fields are defined in the
* Schema of the node.
*/
export interface IMeshNode<T> {
uuid : string;
creator : IMeshRef;
created : number;
editor : IMeshRef;
edited : number;
permissions : Array<MeshPermission>;
language : string;
availableLanguages : Array<string>;
path? : string;
languagePaths? : { [key:string]:string; };
parentNode : IMeshNodeRef;
tags : IMeshTags;
childrenInfo : any;
schema? : IMeshRef;
microschema? : IMeshRef;
published : boolean;
displayField: string;
fields : T;
url? : string;
container: boolean;
}
/**
* Container for a node's tags.
*/
export interface IMeshTags {
[tagFamily: string]: {
uuid: string;
items: Array<IMeshRef>;
};
}
/**
* Representation for a node with binary content.
*/
export interface BinaryNode<T> extends IMeshNode<T> {
binaryProperties: {
sha512sum: string;
fileSize: number;
mimeType: string;
};
fileName: string;
path: string;
}
/**
* Main entry point for the frontend API. Use this class to power your express mesh server.
*
* Usage:
* import * as mesh from 'express-mesh';
* var Mesh = new mesh.Mesh(new mesh.MeshConfig('Demo', 'public', 'languages'));
* Mesh.server(app);
*/
export class Mesh {
private meshClient : MeshRestClient;
private renderer : MeshRenderer;
/**
* Constructor for the main frontend API entry point.
* @param app Express app.
* @param config Configuration for the mesh server.
*/
constructor(private app : express.Express, private config : MeshConfig) {
this.meshClient = new MeshRestClient();
this.renderer = new MeshRenderer(this.app, this.config.viewDirectory);
this.registerMeshMiddleware(app);
}
/**
* Register a custom schema handler.
* A registered schema handler will be executed before a node with the schema it has been
* registered for will be rendered. This can be used to fetch additional information and
* perform operations on the data provided by Mesh.
* @param schema Schema the handler should be registered for
* @param handler The handler that should be registered
*/
public registerSchemaHandler<T>(schema : string, handler : IMeshSchemaHandler<T>) : void {
this.renderer.registerSchemaHandler(schema, handler);
}
/**
* Register a custom view handler.
* A view handler will be executed every time before a view is being rendered by the Mesh
* frontend. This can be used to fetch additional information and perform operations on the
* data provided by Mesh.
* @param handler The handler that should be registered
*/
public registerViewHandler(handler : IMeshViewHandler) : void {
this.renderer.registerViewRenderHandler(handler);
}
/**
* Register a custom error handler.
* A error handler will be executed if an error with the status it has been registered for
* occurs.
* @param status The status the handler should be registered for
* @param handler The handler that should be registered
*/
public registerErrorHandler(status : number, handler : IMeshErrorHandler) : void {
this.renderer.registerErrorHandler(status, handler);
}
/**
* Render a Mesh node.
* This function will render the provided node with the view that is named after the node's schema.
* If the view with this name is not available, the default view will be rendered.
* All registered handlers that apply for this function will be executed.
* @param node Mesh node that should be rendered.
* @param req The mesh request / Express request.
* @param res The mesh response / Express response.
*/
public renderNode(node : IMeshNode<any>, req : IMeshRequest, res : IMeshResponse) : void {
this.renderer.renderMeshNode(node, req, res);
}
/**
* Render a view.
* This function will render the provided view.
* If the view with this name is not available, the default view will be rendered.
* All registered handlers that apply for this function will be executed.
* @param view name of the view that should be rendered.
* @param renderdata Data that should be passed to the view.
* @param req The mesh request / Express request.
* @param res The mesh response / Express response.
*/
public renderView(view : string, renderdata : RenderData, req : IMeshRequest, res : IMeshResponse) : void {
this.renderer.renderView(view, renderdata, req, res);
}
/**
* Login to Gentics Mesh with the provided user. All subsequent requests with the same session
* will use the provided credentials for requests to Mesh.
* @param req The mesh request / Express request.
* @param username Login
* @param password Password
* @returns {Promise<U>} A promise that will be fulfilled as soon es the login completes and will
* be rejected if the login fails.
*/
public login(req : IMeshRequest, username : string, password : string) : Q.Promise<boolean> {
return this.meshClient.login(req, username, password).then((loggedin) => {
if (loggedin === true) {
req.session[rest.MeshAuth.MESH_USER_SESSION_KEY] = username;
req.session[rest.MeshAuth.MESH_PASSWORD_SESSION_KEY] = password;
}
return loggedin;
});
}
/**
* Logout from Gentics Mesh. All subsequent requests with the same session will use the default
* user that has been configured in the MeshConfig for requests to Mesh.
* @param req The mesh request / Express request.
* @returns {Promise<U>} A promise that will be fulfilled as soon as the logout completes and will
* be rejected if the logout fails.
*/
public logout(req : IMeshRequest) : Q.Promise<boolean> {
return this.meshClient.logout(req).then((loggedout) => {
req.session[rest.MeshAuth.MESH_USER_SESSION_KEY] = undefined;
req.session[rest.MeshAuth.MESH_PASSWORD_SESSION_KEY] = undefined;
return loggedout;
})
}
/**
* Perform a search for Mesh Nodes.
* @param req The mesh request / Express request.
* @param query The elastic search query object.
* @param params NodeListParams to implement pagination.
* @returns {Promise<U>} A promise that will be fulfilled once the search request complets and will be rejected
* if the search fails.
*/
public searchNodes<T>(req : IMeshRequest, query : IMeshSearchQuery, params? : IMeshNodeListQueryParams) : Q.Promise<IMeshNodeListResponse<IMeshNode<T>>> {
return this.meshClient.meshSearch<IMeshNode<T>>(req, query, params).then((response)=> {
return response.data;
});
}
/**
* Load all child nodes of a specified node.
* @param req The mesh request / Express request.
* @param uuid The specified node, which children should be loaded.
* @param params QueryParams to specify the language and other options.
* @returns {Promise<U>} A promise that will be fulfilled once the children have been loaded and will be rejected
* if loading the children fails.
*/
public getChildren<T>(req : IMeshRequest, uuid : string, params? : MeshQueryParams) : Q.Promise<IMeshNodeListResponse<IMeshNode<T>>> {
return this.meshClient.getChildren<T>(req, uuid, params).then((response) => {
return response.data;
});
}
/**
* Load a Mesh node with the specified uuid.
* @param req The mesh request / Express request.
* @param uuid The uuid of the node that should be loaded.
* @param params QueryParams to specify the language and other options.
* @returns {Promise<U>} A promise that will be fulfilled once the Mesh node is loaded and will be rejected
* if loading of the Mesh node fails.
*/
public getNode<T>(req : IMeshRequest, uuid : string, params? : MeshQueryParams) : Q.Promise<IMeshNode<T>> {
return this.meshClient.getMeshNode<T>(req, uuid, params).then((response)=> {
return response.data;
});
}
/**
* Load a navigation object by its path. e.g. / for the root navigation.
* You can only load navigation objects for container nodes.
* @param req The mesh request / Express request.
* @param path The path for which the navigation object should be loaded.
* @param params QueryParams to specify the language, navigation depth, includeAll and other options.
* @returns {Q.Promise<MeshRestResponse<IMeshNav>>} A promise that will be fulfilled once the navigation object
* has been loaded and will be rejected if loading of the navigation object fails.
*/
public getNavigationByPath(req : IMeshRequest, path : string, params: MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNav>> {
return this.meshClient.getNavigationByPath(req, path, params);
}
/**
* Load a navigation object by its uuid.
* You can only load navigation objects for container nodes.
* @param req The mesh request / Express request.
* @param uuid The uuid of the root node of the navigation tree you want to load.
* @param params QueryParams to specify the language, navigation depth, includeAll and other options.
* @returns {Q.Promise<MeshRestResponse<IMeshNav>>} A promise that will be fulfilled once the navigation object
* has been loaded and will be rejected if loading of the navigation object fails.
*/
public getNavigationByUUID(req : IMeshRequest, uuid : string, params: MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNav>> {
return this.meshClient.getNavigationByUUID(req, uuid, params);
}
/**
* Load the tag families of the current project.
* @param req The mesh request / Express request.
* @param params Query params to specify pagination.
* @returns {Q.Promise<MeshRestResponse<IMeshNodeListResponse<IMeshTagFamily>>>} A promise that will be fulfilled once
* the tag families have been loaded and will be rejected if loading of the tag families fails.
*/
public getTagFamilies(req : IMeshRequest, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNodeListResponse<IMeshTagFamily>>> {
return this.meshClient.getTagFamilies(req, params);
}
/**
* Load the tags that are contained in a tag family.
* @param req The mesh request / Express request.
* @param uuid The uuid of the tag family.
* @param params Query params to specify pagination.
* @returns {Q.Promise<MeshRestResponse<IMeshNodeListResponse<IMeshTag>>>} A promise that will be fulfilled once
* the tags have been loaded and will fail if loading of the tags fail.
*/
public getTagsOfTagFamily(req : IMeshRequest, uuid : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<IMeshNodeListResponse<IMeshTag>>> {
return this.meshClient.getTagsOfTagFamily(req, uuid, params);
}
/**
* Make a GET request to the mesh backend.
* @param req The Mesh Request
* @param url The url you want to GET
* @param params Query params for your request.
* @returns {Q.Promise<MeshRestResponse<T>>} A promise that will be fulfilled once the request has been completed
* and will fail if the request fails.
*/
public get<T>(req : IMeshRequest, url : string, params? : MeshQueryParams) : Q.Promise<MeshRestResponse<T>> {
return this.meshClient.meshSimpleGET<T>(req, url, params);
}
/**
* Make a request to the mesh backend.
* @param method The request method (GET/POST/PUT/DELETE/...)
* @param req The Mesh Request
* @param url The url you want to GET
* @param params Query params for your request.
* @param data Data you want to send with the request (PUT/POST)
* @returns {Q.Promise<MeshRestResponse<T>>} A promise that will be fulfilled once the request has been completed
* and will fail if the request fails.
*/
public request<T>(method : string, req : IMeshRequest, url : string, params? : MeshQueryParams, data? : any) : Q.Promise<MeshRestResponse<T>> {
return this.meshClient.meshSimpleRequest<T>(method, req, url, params, data);
}
/**
* Private method that constructs the quest handler, that will serve the Mesh content from webroot.
* @returns {function(IMeshRequest, express.Response): undefined}
*/
private getRequestHandler() : (req : express.Request, res : express.Response)=>void {
return (req : IMeshRequest, res : express.Response) => {
this.meshClient.getWebrootNode<IMeshNode<any>>(req).then((response : MeshRestResponse<IMeshNode<any>>) => {
var status = Number(response.status);
if (status < 400) {
res.status(response.status);
if (response.isBinary) {
response.stream.pipe(res);
} else {
req.requestedNode = response.data;
this.renderer.renderMeshNode(response.data, req, res);
}
} else {
this.renderer.renderError(status, req, res, response.data);
}
}).catch((err) => {
this.renderer.renderError(500, req, res, err);
});
};
}
/**
* Private method that will register the mesh middleware in the Express app.
* This middleware will enrich the mesh request with the configuration and the
* active language.
* @param app The Express app.
*/
private registerMeshMiddleware(app : express.Express) : void {
app.use('*', (req : IMeshRequest, res : IMeshResponse, next : Function) => {
if (!u.isDefined(req.meshConfig)) {
req.meshConfig = this.config;
}
if (u.isDefined(req.query.lang)) {
lang.setActiveLanguage(req, req.query.lang);
}
// polifill for express-session
if (!u.isDefined(req.session)) {
req.session = <Express.Session>{};
}
lang.readLanguageFiles(this.config);
next();
});
}
/**
* Register default template filters.
* Out of the box we support registering filters with swig and handlebars. If you have a different template engine
* please pass a register function to register the filters with your template engine. This function will then be called
* for each of the mesh filters.
* @param engine Your template engine.
* @param registerfunction [optional] register function that will be called for each of the mesh filters.
**/
public registerTemplateFilters(engine : any, registerfunction? : IFilterRegisterFunction) : void {
filters.registerFilters(engine, registerfunction);
}
/**
* Initialize the Mesh server. Call this method after you added your own request handlers to the Express app,
* as this method will attach a * handler to catch all requests that have not been handled by another handler.
* @param app The Express app.
*/
public server(app : express.Express) : void {
app.get('*', this.getRequestHandler());
}
}