Santhosh has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/377739 )

Change subject: Refactor the API router to ES6 class
......................................................................

Refactor the API router to ES6 class

In follow ups, we are trying to make the router a simple path to handler
mapper and handlers in seperate files. This refactoring will help us
to provide v2 of apis in future without much code duplication.

Change-Id: I10009261b311c80fdea4c300199b73a44880cbe4
---
M lib/routes/v1.js
1 file changed, 281 insertions(+), 223 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/cxserver 
refs/changes/39/377739/1

diff --git a/lib/routes/v1.js b/lib/routes/v1.js
index 77fb8d1..3f29352 100644
--- a/lib/routes/v1.js
+++ b/lib/routes/v1.js
@@ -1,274 +1,332 @@
 'use strict';
 
-var app, router, registry;
+let registry;
 const sUtil = require( '../util' ),
        jwt = require( 'jsonwebtoken' ),
        CXConfig = require( '../Config.js' );
 
-/**
- * The main router object
- */
-router = sUtil.router();
+class Routes {
+       constructor( app, registry ) {
+               this.app = app;
+               this.registry = registry;
+               this.router = sUtil.router();
+               if ( !this.app ) { throw new Error( 'Missing app property' ); }
+               this.registerRoutes();
+       }
 
-router.get( '/page/:language/:title/:revision?', function ( req, res ) {
-       var sourceLanguage = req.params.language,
-               title = req.params.title,
-               revision = req.params.revision,
-               CXSegmenter = require( __dirname + 
'/../segmentation/CXSegmenter.js' ),
-               PageLoader = require( __dirname + 
'/../pageloader/PageLoader.js' ),
-               pageloader = new PageLoader( app );
+       /**
+        * routes definitions
+        */
+       get routes() {
+               return {
+                       '/page/:language/:title/:revision?': 
this.fetchPage.bind( this ),
+                       'POST /mt/:from/:to/:provider?': 
this.machineTranslate.bind( this ),
+                       '/dictionary/:word/:from/:to/:provider?': 
this.dictionary.bind( this ),
+                       '/list/tool/:tool': this.listTool.bind( this ),
+                       '/list/pair/:from/:to': 
this.listToolForLanguagePair.bind( this ),
+                       '/list/languagepairs': this.listLanguagePairs.bind( 
this ),
+                       '/list/:tool/:from?/:to?': 
this.listToolForLanguagePairsAndTool.bind( this ),
+                       'POST /translate/:from/:to/:provider?': 
this.translate.bind( this )
+               };
+       }
 
-       return pageloader.load( title, sourceLanguage, revision ).then(
-               function ( response ) {
-                       var segmenter, segmentedContent;
+       /**
+        * It goes through each route defition, figures out the verb/action to 
use
+        * (default: get), the function to call to handle the request and 
registers
+        * the whole for the given path.
+        */
+       registerRoutes() {
+               var routes = this.routes;
+
+               Object.keys( routes ).forEach( ( path ) => {
+                       let parts = path.split( ' ' );
+                       let verb = parts[ 1 ] ? parts[ 0 ] : 'get';
+                       path = parts[ 1 ] || parts[ 0 ];
+                       verb = verb.toLowerCase();
+                       this.router[ verb ]( path, routes[ path ] );
+               } );
+       }
+
+       fetchPage( req, res ) {
+               var sourceLanguage = req.params.language,
+                       title = req.params.title,
+                       revision = req.params.revision,
+                       CXSegmenter = require( __dirname + 
'/../segmentation/CXSegmenter.js' ),
+                       PageLoader = require( __dirname + 
'/../pageloader/PageLoader.js' ),
+                       pageloader = new PageLoader( this.app );
+
+               return pageloader.load( title, sourceLanguage, revision ).then(
+                       function ( response ) {
+                               var segmenter, segmentedContent;
+
+                               try {
+                                       this.app.logger.log( 'debug', 'Fetch 
page', {
+                                               title: title,
+                                               sourceLanguage: sourceLanguage
+                                       } );
+                                       segmenter = new CXSegmenter( 
response.body, sourceLanguage );
+                                       segmenter.segment();
+                                       segmentedContent = 
segmenter.getSegmentedContent();
+                                       this.app.logger.log( 'debug', 'Segment 
page', {
+                                               title: title,
+                                               sourceLanguage: sourceLanguage
+                                       } );
+                               } catch ( error ) {
+                                       res.status( 500 ).end( `Page 
${sourceLanguage} : ${title} could not be fetched or segmented: ` + 
error.toString() );
+                               }
+                               res.send( {
+                                       sourceLanguage: sourceLanguage,
+                                       title: title,
+                                       revision: response.revision,
+                                       segmentedContent: segmentedContent
+                               } );
+                               this.app.logger.log( 'debug', 'Page sent' );
+                       },
+                       function ( error ) {
+                               res.status( 404 ).end( 'Page ' + sourceLanguage 
+ ':' + title + ' could not be found. ' + error.toString() );
+                       }
+               );
+       }
+
+       /**
+        * Get the appropriate machine translation service client
+        * @param {Request} req request object
+        * @param {Response} res response object
+        * @return {mtClient}
+        */
+       getMTClient( req, res ) {
+               var mtClients, mtClient, provider,
+                       authzToken, jwtConfig,
+                       from = req.params.from,
+                       to = req.params.to;
+
+               provider = this.registry.getValidProvider( from, to, 'mt', 
req.params.provider );
+
+               if ( !provider ) {
+                       res.status( 404 ).end( 'Provider not supported' );
+                       return;
+               }
+
+               mtClients = require( __dirname + '/../mt/' );
+               if ( mtClients[ provider ] === undefined ) {
+                       res.status( 500 ).end( 'Provider not found' );
+                       return;
+               }
+
+               mtClient = new mtClients[ provider ]( this.app );
+
+               if ( mtClient.requiresAuthorization() ) {
+                       if ( !req.headers || !req.headers.authorization ) {
+                               res.status( 403 ).end( 'Authorization header is 
missing' );
+                               return;
+                       }
+
+                       authzToken = req.headers.authorization;
+                       jwtConfig = this.app.conf.jwt;
 
                        try {
-                               app.logger.log( 'debug', 'Fetch page', {
-                                       title: title,
-                                       sourceLanguage: sourceLanguage
+                               jwt.verify( authzToken, jwtConfig.secret, {
+                                       algorithms: jwtConfig.algorithms
                                } );
-                               segmenter = new CXSegmenter( response.body, 
sourceLanguage );
-                               segmenter.segment();
-                               segmentedContent = 
segmenter.getSegmentedContent();
-                               app.logger.log( 'debug', 'Segment page', {
-                                       title: title,
-                                       sourceLanguage: sourceLanguage
-                               } );
-                       } catch ( error ) {
-                               res.status( 500 ).end( 'Page ' + sourceLanguage 
+ ':' +
-                                       title + ' could not be fetched or 
segmented: ' + error.toString() );
+                       } catch ( err ) {
+                               res.status( 403 ).end( 'Authorization header is 
not valid: ' + err );
+                               return;
                        }
-                       res.send( {
-                               sourceLanguage: sourceLanguage,
-                               title: title,
-                               revision: response.revision,
-                               segmentedContent: segmentedContent
-                       } );
-                       app.logger.log( 'debug', 'Page sent' );
-               },
-               function ( error ) {
-                       res.status( 404 ).end( 'Page ' + sourceLanguage + ':' + 
title + ' could not be found. ' + error.toString() );
                }
-       );
-} );
-
-router.get( '/mt/:from/:to/:provider?', function ( req, res ) {
-       res.status( 405 ).end( 'Request must be posted' );
-} );
-
-function getMTClient( req, res ) {
-       var mtClients, mtClient, provider,
-               authzToken, jwtConfig,
-               from = req.params.from,
-               to = req.params.to;
-
-       provider = registry.getValidProvider( from, to, 'mt', 
req.params.provider );
-
-       if ( !provider ) {
-               res.status( 404 ).end( 'Provider not supported' );
-               return;
+               return mtClient;
        }
 
-       mtClients = require( __dirname + '/../mt/' );
-       if ( mtClients[ provider ] === undefined ) {
-               res.status( 500 ).end( 'Provider not found' );
-               return;
-       }
+       /**
+        * Machine translation api handler
+        * @param {Request} req request object
+        * @param {Response} res response object
+        * @return {Promise}
+        */
+       machineTranslate( req, res ) {
+               var mtClient, sourceHtml,
+                       from = req.params.from,
+                       to = req.params.to;
 
-       mtClient = new mtClients[ provider ]( app );
+               mtClient = this.getMTClient( req, res );
 
-       if ( mtClient.requiresAuthorization() ) {
-               if ( !req.headers || !req.headers.authorization ) {
-                       res.status( 403 ).end( 'Authorization header is 
missing' );
+               if ( !mtClient ) {
                        return;
                }
 
-               authzToken = req.headers.authorization;
-               jwtConfig = app.conf.jwt;
+               // We support setting html as body or as body.html. But 
body.html is the recommended way.
+               // The other way will be removed soon.
+               sourceHtml = [ '<div>', req.body.html || req.rawBody, '</div>' 
].join( '' );
+               return mtClient.translate( from, to, sourceHtml ).then(
+                       ( data ) => {
+                               res.json( {
+                                       contents: data
+                               } );
+                       },
+                       ( error ) => {
+                               res.status( 500 ).end( error.toString() );
+                               this.app.logger.log( 'error', 'MT processing 
error: ' + error.stack );
+                       }
+               );
+       }
+       /**
+        * Dictionary api handler
+        * @param {Request} req request object
+        * @param {Response} res response object
+        * @return {Promise}
+        */
+       dictionary( req, res ) {
+               var dictClients, provider, dictClient,
+                       word = req.params.word,
+                       from = req.params.from,
+                       to = req.params.to;
 
-               try {
-                       jwt.verify( authzToken, jwtConfig.secret, {
-                               algorithms: jwtConfig.algorithms
-                       } );
-               } catch ( err ) {
-                       res.status( 403 ).end( 'Authorization header is not 
valid: ' + err );
+               provider = this.registry.getValidProvider( from, to, 
'dictionary', req.params.provider );
+
+               if ( !provider ) {
+                       res.status( 404 ).end( 'Dictionary provider invalid or 
missing' );
                        return;
                }
-       }
-       return mtClient;
-}
 
-router.post( '/mt/:from/:to/:provider?', function ( req, res ) {
-       var mtClient, sourceHtml,
-               from = req.params.from,
-               to = req.params.to;
+               dictClients = require( __dirname + '/../dictionary/' );
+               dictClient = new dictClients[ provider ]( this.app );
 
-       mtClient = getMTClient( req, res );
-
-       if ( !mtClient ) {
-               return;
+               return dictClient.getTranslations( word, from, to ).then(
+                       ( data ) => {
+                               res.send( data );
+                       },
+                       ( error ) => {
+                               res.status( 500 ).end( error.toString() );
+                               this.app.logger.log( 'error', 'Dictionary 
lookup error: (%s)', error.toString() );
+                       }
+               );
        }
 
-       // We support setting html as body or as body.html. But body.html is 
the recommended way.
-       // The other way will be removed soon.
-       sourceHtml = [ '<div>', req.body.html || req.rawBody, '</div>' ].join( 
'' );
-       return mtClient.translate( from, to, sourceHtml ).then(
-               function ( data ) {
-                       res.json( {
-                               contents: data
-                       } );
-               },
-               function ( error ) {
-                       res.status( 500 ).end( error.toString() );
-                       app.logger.log( 'error', 'MT processing error: ' + 
error.stack );
-               }
-       );
-} );
+       /**
+        * Get a list of all language pairs that tool supports.
+        * @param {Request} req request object
+        * @param {Response} res response object
+        */
+       listTool( req, res ) {
+               var result,
+                       tool = req.params.tool;
 
-router.get( '/dictionary/:word/:from/:to/:provider?', function ( req, res ) {
-       var dictClients, provider, dictClient,
-               word = req.params.word,
-               from = req.params.from,
-               to = req.params.to;
-
-       provider = registry.getValidProvider( from, to, 'dictionary', 
req.params.provider );
-
-       if ( !provider ) {
-               res.status( 404 ).end( 'Dictionary provider invalid or missing' 
);
-               return;
-       }
-
-       dictClients = require( __dirname + '/../dictionary/' );
-       dictClient = new dictClients[ provider ]( app );
-
-       return dictClient.getTranslations( word, from, to ).then(
-               function ( data ) {
-                       res.send( data );
-               },
-               function ( error ) {
-                       res.status( 500 ).end( error.toString() );
-                       app.logger.log( 'error', 'Dictionary lookup error: 
(%s)', error.toString() );
-               }
-       );
-} );
-
-/**
- * Get a list of all language pairs that tool supports.
- */
-router.get( '/list/tool/:tool', function ( req, res ) {
-       var result,
-               tool = req.params.tool;
-
-       if ( tool === 'mt' ) {
-               result = registry.MTPairs;
-       }
-       if ( tool === 'dictionary' ) {
-               result = registry.DictionaryPairs;
-       }
-
-       if ( !result ) {
-               res.status( 404 ).end( 'Unknown tool' );
-               return;
-       }
-
-       res.json( result );
-} );
-
-/**
- * Lists the available tools for a language pair.
- */
-router.get( '/list/pair/:from/:to', function ( req, res ) {
-       var result,
-               from = req.params.from,
-               to = req.params.to;
-
-       result = registry.getToolSet( from, to );
-
-       res.json( result );
-} );
-
-/**
- * Get a list of all language pairs.
- */
-router.get( '/languagepairs', function ( req, res ) {
-       res.json( registry.LanguagePairs );
-} );
-
-/**
- * Get a list of all language pairs.
- */
-router.get( '/list/languagepairs', function ( req, res ) {
-       res.json( registry.LanguagePairs );
-} );
-
-router.get( '/list/:tool/:from?/:to?', function ( req, res ) {
-       var toolset, result = {},
-               tool = req.params.tool,
-               from = req.params.from,
-               to = req.params.to;
-
-       if ( from && to ) {
-               toolset = registry.getToolSet( from, to );
-               result[ tool ] = toolset[ tool ];
-               result[ 'default' ] = toolset.default;
-       } else if ( tool ) {
                if ( tool === 'mt' ) {
-                       result = registry.MTPairs;
+                       result = this.registry.MTPairs;
                }
                if ( tool === 'dictionary' ) {
-                       result = registry.DictionaryPairs;
+                       result = this.registry.DictionaryPairs;
                }
-       }
-       res.json( result );
-} );
 
-router.post( '/translate/:from/:to/:provider?', function ( req, res ) {
-       var mtClient, sourceHtml, machineTranslationRequest,
-               from = req.params.from,
-               to = req.params.to;
-
-       if ( req.params.provider ) {
-               mtClient = getMTClient( req, res );
-               if ( !mtClient ) {
-                       // With explicit provider, if not MT Client found, it 
is an error.
+               if ( !result ) {
+                       res.status( 404 ).end( 'Unknown tool' );
                        return;
                }
+
+               res.json( result );
        }
 
-       sourceHtml = req.body.html;
+       /**
+        * Lists the available tools for a language pair.
+        * @param {Request} req request object
+        * @param {Response} res response object
+        */
+       listToolForLanguagePair( req, res ) {
+               var result,
+                       from = req.params.from,
+                       to = req.params.to;
 
-       if ( !mtClient ) {
-               machineTranslationRequest = Promise.resolve( sourceHtml );
-       } else {
-               machineTranslationRequest = mtClient.translate( from, to, 
sourceHtml );
+               result = this.registry.getToolSet( from, to );
+
+               res.json( result );
        }
 
-       return machineTranslationRequest.then( ( translatedHTML ) => {
-               var CXAdapter = require( __dirname + '/../Adapter' );
+       /**
+        * Get a list of all language pairs.
+        * @param {Request} req request object
+        * @param {Response} res response object
+        */
+       listLanguagePairs( req, res ) {
+               res.json( this.registry.LanguagePairs );
+       }
 
-               app.conf.mtClient = mtClient;
+       /**
+        * @param {Request} req request object
+        * @param {Response} res response object
+        */
+       listToolForLanguagePairsAndTool( req, res ) {
+               var toolset, result = {},
+                       tool = req.params.tool,
+                       from = req.params.from,
+                       to = req.params.to;
 
-               return new CXAdapter( from, to, app )
-                       .adapt( translatedHTML )
-                       .then( ( adaptedDoc ) => {
-                               res.json( {
-                                       contents: adaptedDoc.getHtml()
+               if ( from && to ) {
+                       toolset = this.registry.getToolSet( from, to );
+                       result[ tool ] = toolset[ tool ];
+                       result[ 'default' ] = toolset.default;
+               } else if ( tool ) {
+                       if ( tool === 'mt' ) {
+                               result = this.registry.MTPairs;
+                       }
+                       if ( tool === 'dictionary' ) {
+                               result = this.registry.DictionaryPairs;
+                       }
+               }
+               res.json( result );
+       }
+
+       /**
+        * @param {Request} req request object
+        * @param {Response} res response object
+        * @return {Promise}
+        */
+       translate( req, res ) {
+               var mtClient, sourceHtml, machineTranslationRequest,
+                       from = req.params.from,
+                       to = req.params.to;
+
+               if ( req.params.provider ) {
+                       mtClient = this.getMTClient( req, res );
+                       if ( !mtClient ) {
+                       // With explicit provider, if not MT Client found, it 
is an error.
+                               return;
+                       }
+               }
+
+               sourceHtml = req.body.html;
+
+               if ( !mtClient ) {
+                       machineTranslationRequest = Promise.resolve( sourceHtml 
);
+               } else {
+                       machineTranslationRequest = mtClient.translate( from, 
to, sourceHtml );
+               }
+
+               return machineTranslationRequest.then( ( translatedHTML ) => {
+                       var CXAdapter = require( __dirname + '/../Adapter' );
+
+                       this.app.conf.mtClient = mtClient;
+
+                       return new CXAdapter( from, to, this.app )
+                               .adapt( translatedHTML )
+                               .then( ( adaptedDoc ) => {
+                                       res.json( {
+                                               contents: adaptedDoc.getHtml()
+                                       } );
+                               }, ( error ) => {
+                                       res.status( 500 ).end( error.stack );
+                                       this.app.logger.log( 'error', 'MT 
processing error: ' + error.stack );
                                } );
-                       }, ( error ) => {
-                               res.status( 500 ).end( error.stack );
-                               app.logger.log( 'error', 'MT processing error: 
' + error.stack );
-                       } );
-       } );
-} );
+               } );
+       }
+}
 
-module.exports = function ( appObj ) {
-       app = appObj;
-       registry = registry || new CXConfig( app );
+module.exports = ( appObj ) => {
+       registry = registry || new CXConfig( appObj );
+       const routes = new Routes( appObj, registry );
        return {
                path: '/v1/',
                /* eslint camelcase:off */
                api_version: 1,
-               router: router,
+               router: routes.router,
                skip_domain: true
        };
 };

-- 
To view, visit https://gerrit.wikimedia.org/r/377739
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I10009261b311c80fdea4c300199b73a44880cbe4
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/services/cxserver
Gerrit-Branch: master
Gerrit-Owner: Santhosh <santhosh.thottin...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to