diff --git a/src/client/action/ClientAction.ts b/src/client/action/ClientAction.ts new file mode 100644 index 0000000..da57ebb --- /dev/null +++ b/src/client/action/ClientAction.ts @@ -0,0 +1,39 @@ +/** + * Representation of actions to be performed by the client + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +/** + * Action to be performed by the client + * + * TODO: More specific types + */ +export interface ClientAction +{ + /** Action to be performed */ + action: string; + + /** Action arguments */ + [P: string]: any; +} + + +/** Set of actions */ +export type ClientActions = ClientAction[]; diff --git a/src/program/Program.d.ts b/src/program/Program.d.ts new file mode 100644 index 0000000..de3f45a --- /dev/null +++ b/src/program/Program.d.ts @@ -0,0 +1,27 @@ +/** + * Contains Program base class + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export declare abstract class Program +{ + readonly ineligibleLockCount: number; + + getId(): string; +} diff --git a/src/quote/BaseQuote.d.ts b/src/quote/BaseQuote.d.ts new file mode 100644 index 0000000..9af47ee --- /dev/null +++ b/src/quote/BaseQuote.d.ts @@ -0,0 +1,93 @@ +/** + * Contains program Quote class + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @todo Use ``document'' terminology in place of ``quote'' + */ + +import { Program } from "../program/Program"; +import { Quote, QuoteId } from "./Quote"; + + +export declare class BaseQuote implements Quote +{ + /** + * Retrieve Program associated with quote + * + * @return quote program + */ + getProgram(): Program; + + + /** + * Returns the program id associated with the quote + * + * @return program id + */ + getProgramId(): string; + + + /** + * Returns the quote id + * + * The quote id is immutable. A different quote id would represent a + * different quote, therefore a new object should be created with the + * desired quote id. + * + * @return quote id + */ + getId(): QuoteId; + + + /** + * Returns the id of the current step + * + * @return id of current step + */ + getCurrentStepId(): number; + + + /** + * Sets an explicit lock, providing a reason for doing so + * + * @param reason - lock reason + * @param step - step that user may not navigate prior + * + * @return self + */ + setExplicitLock( reason: string, step: number ): this; + + + /** + * Set the date that the premium was calculated as a Unix timestamp + * + * @param timestamp - Unix timestamp representing premium date + * + * @return self + */ + setLastPremiumDate( timestamp: UnixTimestamp ): this; + + + /** + * Retrieve the last time the premium was calculated + * + * @return last calculated time or 0 + */ + getLastPremiumDate(): UnixTimestamp; +} diff --git a/src/quote/Quote.d.ts b/src/quote/Quote.d.ts new file mode 100644 index 0000000..9324d0f --- /dev/null +++ b/src/quote/Quote.d.ts @@ -0,0 +1,33 @@ +/** + * Generic interface to represent quotes + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @todo Use ``document'' terminology in place of ``quote'' + */ + +import { Program } from "../program/Program"; +import { QuoteId } from "../document/Document"; + + +export declare interface Quote +{ + // TODO: the easejs interface is empty; is this actually needed? +} + +export { QuoteId }; diff --git a/src/server/Server.d.ts b/src/server/Server.d.ts new file mode 100644 index 0000000..e165d69 --- /dev/null +++ b/src/server/Server.d.ts @@ -0,0 +1,66 @@ +/** + * General server actions + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { ClientActions } from "../client/action/ClientAction"; +import { ServerSideQuote } from "./quote/ServerSideQuote"; +import { UserRequest } from "./request/UserRequest"; + + +/** + * General server actions + */ +export declare class Server +{ + /** + * Send response to user + * + * @param request - request to respond to + * @param quote - quote associated with request + * @param data - data with which to reply + * @param actions - optional client actions + * + * @return self + */ + sendResponse( + request: UserRequest, + quote: ServerSideQuote, + data: Record, + actions?: ClientActions, + ): this; + + + /** + * Send response to user + * + * @param request - request to respond to + * @param message - message to display to user + * @param actions - optional client actions + * @param btn_caption - optional caption for acknowledgement button + * + * @return self + */ + sendError( + request: UserRequest, + message: string, + actions?: ClientActions, + btn_caption?: string, + ): this; +} diff --git a/src/server/daemon/controller.js b/src/server/daemon/controller.js index 5b1b6b0..c027e4e 100644 --- a/src/server/daemon/controller.js +++ b/src/server/daemon/controller.js @@ -28,6 +28,8 @@ const { ReplSetServers: ReplSetServers, } = require( 'mongodb/lib/mongodb' ); +const easejs = require( 'easejs' ); + const regex_base = /^\/quote\/([a-z0-9-]+)\/?(?:\/(\d+)\/?(?:\/(.*))?|\/(program.js))?$/; const regex_step = /^step\/(\d+)\/?(?:\/(post|visit))?$/; @@ -83,7 +85,7 @@ const { ExportService, }, - RatingService, + RatingService: { RatingService }, RatingServicePublish, TokenedService, }, @@ -136,7 +138,7 @@ exports.init = function( logger, enc_service, conf ) server.init( server_cache, exports.rater ); // TODO: temporary proof-of-concept - rating_service = RatingService.use( + rating_service = easejs( RatingService ).use( RatingServicePublish( amqplib, exports.post_rate_publish, logger ) )( logger, dao, server, exports.rater diff --git a/src/server/db/MongoServerDao.d.ts b/src/server/db/MongoServerDao.d.ts new file mode 100644 index 0000000..fbf62c0 --- /dev/null +++ b/src/server/db/MongoServerDao.d.ts @@ -0,0 +1,146 @@ +/** + * Mongo DB DAO for program server + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { ClassificationData, WorksheetData } from "../rater/Rater"; +import { PositiveInteger } from "../../numeric"; +import { QuoteId } from "../../document/Document"; +import { ServerSideQuote } from "../quote/ServerSideQuote"; + +/** Success or failure callback */ +type Callback = ( quote: ServerSideQuote ) => void; + + +/** + * MongoDB-backed data store + */ +export declare class MongoServerDao +{ + /** + * Saves a quote to the database + * + * A full save will include all metadata. + * + * @param quote - the quote to save + * @param success - function to call on success + * @param failure - function to call if save fails + * @param save_data - quote data to save (optional) + */ + saveQuote( + quote: ServerSideQuote, + success: Callback, + failure: Callback, + save_data: Record, + ): this; + + + /** + * Merges bucket data with the existing bucket (rather than overwriting the + * entire bucket) + * + * @param quote - quote to save + * @param data - bucket data + * @param success - successful callback + * @param failure - failure callback + */ + mergeBucket( + quote: ServerSideQuote, + data: Record, + success: Callback, + failure: Callback, + ): this; + + + /** + * Save quote classification data + * + * @param quote - quote to save + * @param classes - classification data + * @param success - successful callback + * @param failure - failure callback + */ + saveQuoteClasses( + quote: ServerSideQuote, + classes: ClassificationData, + success: Callback, + failure: Callback, + ): this; + + + /** + * Saves the quote state to the database + * + * The quote state includes the current step, the top visited step and the + * explicit lock message. + * + * @param quote - the quote to save + * @param success - function to call on success + * @param failure - function to call if save fails + */ + saveQuoteState( + quote: ServerSideQuote, + success: Callback, + failure: Callback, + ): this; + + + /** + * Saves the quote lock state to the database + * + * @param quote - the quote to save + * @param success - function to call on success + * @param failure - function to call if save fails + */ + saveQuoteLockState( + quote: ServerSideQuote, + success: Callback, + failure: Callback, + ): this + + + /** + * Save worksheet data + * + * @param qid - quote identifier + * @param data - worksheet data + * @param callback - callback + */ + setWorksheets( + qid: QuoteId, + data: WorksheetData, + callback: NodeCallback, + ): this; + + + /** + * Retrieve worksheet data + * + * @param qid - quote identifier + * @param supplier - supplier id + * @param index - worksheet index + * @param callback - callback + */ + getWorksheet( + qid: QuoteId, + supplier: string, + index: PositiveInteger, + callback: ( data: WorksheetData | null ) => void, + ): this; +} diff --git a/src/server/log/PriorityLog.d.ts b/src/server/log/PriorityLog.d.ts new file mode 100644 index 0000000..60819a4 --- /dev/null +++ b/src/server/log/PriorityLog.d.ts @@ -0,0 +1,45 @@ +/** + * Priority log typescript type definitions + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export declare interface PriorityLog +{ + readonly PRIORITY_ERROR: number; + readonly PRIORITY_IMPORTANT: number; + readonly PRIORITY_DB: number; + readonly PRIORITY_INFO: number; + readonly PRIORITY_SOCKET: number; + + /** + * Write to the log at the given priority + * + * If the priority is less than or equal to the set priority for this + * object, it will be logged. Otherwise, the message will be ignored. + * + * The first argument should be the priority. The remaining arguments should + * be provided in a sprintf()-style fashion + * + * @param priority - logging priority + * @param ...args - sprintf-style aruments + * + * @return self + */ + log( priority: number, ...args: Array ): this; +} diff --git a/src/server/quote/ServerSideQuote.d.ts b/src/server/quote/ServerSideQuote.d.ts new file mode 100644 index 0000000..98eff98 --- /dev/null +++ b/src/server/quote/ServerSideQuote.d.ts @@ -0,0 +1,46 @@ +/** + * Augments a quote with additional data for use by the quote server + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @todo Use ``document'' terminology in place of ``quote'' + */ + +import { Program } from "../../program/Program"; +import { BaseQuote } from "../../quote/BaseQuote"; + + +export declare class ServerSideQuote extends BaseQuote +{ + /** + * Last rated date, if any + * + * @return last rated date + */ + getRatedDate(): UnixTimestamp; + + + /** + * Set the timestamp of the first time quote was rated + * + * @param timestamp - Unix timestamp representing first rated date + * + * @return self + */ + setRatedDate( timestamp: UnixTimestamp ): this; +} diff --git a/src/server/rater/ProcessManager.d.ts b/src/server/rater/ProcessManager.d.ts new file mode 100644 index 0000000..23d6705 --- /dev/null +++ b/src/server/rater/ProcessManager.d.ts @@ -0,0 +1,35 @@ +/** + * Rating process manager + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Rater } from "./Rater"; + + +export declare class ProcessManager +{ + /** + * Returns the rater associated with the given id + * + * @param id - rater id + * + * @return requested rater + */ + byId( id: string ): Rater; +} diff --git a/src/server/rater/ProcessManager.js b/src/server/rater/ProcessManager.js index a5a67b1..13c7fe2 100644 --- a/src/server/rater/ProcessManager.js +++ b/src/server/rater/ProcessManager.js @@ -69,6 +69,9 @@ const _signum = { * * Handles formatting and sending requests to the rating process; and * processing replies. + * + * TODO: Rename this class and provide a more generic interface. The caller + * does not care that this sends data to another process for rating. */ module.exports = Class( 'ProcessManager', { diff --git a/src/server/rater/Rater.d.ts b/src/server/rater/Rater.d.ts new file mode 100644 index 0000000..d9dcb1b --- /dev/null +++ b/src/server/rater/Rater.d.ts @@ -0,0 +1,121 @@ +/** + * Contains Rater interface + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { ClientActions } from "../../client/action/ClientAction"; +import { ServerSideQuote } from "../quote/ServerSideQuote"; +import { UserSession } from "../request/UserSession"; + + + +/** Result of rating */ +export interface RateResult +{ + /** Whether all suppliers were not able to provide rates */ + _unavailable_all: '0' | '1'; + + /** Result data */ + [P: string]: any; +} + + +/** + * Result of rating with an individual supplier + * + * This gets combined into a single RateResult prefixed with each supplier + * id and an underscore. + */ +export interface SupplierRateResult +{ + /** Rating worksheet data */ + __worksheet?: WorksheetData, + + /** Classification system results */ + __classes?: ClassificationData, + + /** Basic profiling data */ + __perf: PerformanceData, + + /** Ineligible message, if any */ + ineligible: string, + + /** Submit message, if any */ + submit: string, + + /** Final premium */ + premium: number, + + /** Rating data */ + [P: string]: any; +} + + +/** Basic profiling data */ +export interface PerformanceData +{ + /** Timestamp of beginning of rating */ + start: UnixTimestampMillis; + + /** Timestamp of end of rating */ + end: UnixTimestampMillis; + + /** Total rating time */ + total: Milliseconds; +} + + +/** + * Worksheet data from rater + * + * These data come from the compiled raters. + * + * TODO: Fill in a schema here + */ +export type WorksheetData = Record; + + +/** Classification results */ +export type ClassificationData = Record; + + +/** + * Represents a rater that will generate a quote from a given set of values + */ +export interface Rater +{ + /** + * Asynchronously performs rating + * + * @param quote - quote to perform rating on + * @param session - user session + * @param indv - individual supplier to rate (otherwise empty) + * @param success - continuation when rating is successful + * @param failure - continuation when rating fails + * + * @return self + */ + rate( + quote: ServerSideQuote, + session: UserSession, + indv: string, + success: ( rate_data: RateResult, actions: ClientActions ) => void, + failure: ( message: string ) => void, + ): this; +} diff --git a/src/server/request/UserRequest.d.ts b/src/server/request/UserRequest.d.ts new file mode 100644 index 0000000..3449963 --- /dev/null +++ b/src/server/request/UserRequest.d.ts @@ -0,0 +1,36 @@ +/** + * User request abstraction + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { UserSession } from "./UserSession"; + + +/** + * Representation of request from user + */ +export declare class UserRequest +{ + /** + * Retrieve the current session + * + * @return current session + */ + getSession(): UserSession; +} diff --git a/src/server/request/UserResponse.d.ts b/src/server/request/UserResponse.d.ts new file mode 100644 index 0000000..7ba776f --- /dev/null +++ b/src/server/request/UserResponse.d.ts @@ -0,0 +1,28 @@ +/** + * UserResponse class + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +/** + * Manipulates response to user request + */ +export declare class UserResponse +{ +} diff --git a/src/server/request/UserSession.d.ts b/src/server/request/UserSession.d.ts new file mode 100644 index 0000000..01937d7 --- /dev/null +++ b/src/server/request/UserSession.d.ts @@ -0,0 +1,34 @@ +/** + * UserSession class + * + * Copyright (C) 2010-2019 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +/** + * Session management + */ +export declare class UserSession +{ + /** + * Whether the user is logged in as an internal user + * + * @return true if internal user, otherwise false + */ + isInternal(): boolean; +} diff --git a/src/server/service/RatingService.js b/src/server/service/RatingService.ts similarity index 57% rename from src/server/service/RatingService.js rename to src/server/service/RatingService.ts index aa9e53f..d0b33dc 100644 --- a/src/server/service/RatingService.js +++ b/src/server/service/RatingService.ts @@ -19,33 +19,60 @@ * along with this program. If not, see . */ -var Class = require( 'easejs' ).Class; +import { ClientActions } from "../../client/action/ClientAction"; +import { MongoServerDao } from "../db/MongoServerDao"; +import { PositiveInteger } from "../../numeric"; +import { PriorityLog } from "../log/PriorityLog"; +import { ProcessManager } from "../rater/ProcessManager"; +import { Program } from "../../program/Program"; +import { QuoteId } from "../../quote/Quote"; +import { ClassificationData, RateResult, WorksheetData } from "../rater/Rater"; +import { Server } from "../Server"; +import { ServerSideQuote } from "../quote/ServerSideQuote"; +import { UserRequest } from "../request/UserRequest"; +import { UserResponse } from "../request/UserResponse"; + +type RequestCallback = () => void; /** - * XXX: Half-assed, quick refactoring to extract from Server class; this is not - * yet complete! + * Handle rating requests + * + * XXX: This class was extracted from Server and needs additional + * refactoring, testing, and cleanup. * * TODO: Logging should be implemented by observers */ -module.exports = Class( 'RatingService', +export class RatingService { - logger: null, - - dao: null, - - _server: null, - - _raters: null, + /** + * Initialize rating service + * + * @param _logger - logging system + * @param _dao - database connection + * @param _server - server actions + * @param _rater_manager - rating manager + */ + constructor( + private readonly _logger: PriorityLog, + private readonly _dao: MongoServerDao, + private readonly _server: Server, + private readonly _rater_manager: ProcessManager, + ) {} - __construct: function( logger, dao, server, raters ) + /** + * TODO: Remove once traits subtypes are converted to TS + * + * This works around an easejs bug where prototype constructors are not + * properly invoked. Note that technically the constructor above is + * invoked twice by easejs: once with no arguments, and again when + * calling this method with the proper arguments. + */ + __construct() { - this._logger = logger; - this._dao = dao; - this._server = server; - this._raters = raters; - }, + (RatingService ).apply( this, arguments ); + } /** @@ -54,15 +81,21 @@ module.exports = Class( 'RatingService', * Note that the continuation will be called after all data saving is * complete; the request will be sent back to the client before then. * - * @param {UserRequest} request user request to satisfy - * @param {UserResponse} response pending response - * @param {Quote} quote quote to export - * @param {string} cmd applicable of command request - * @param {Function} callback continuation after saving is complete + * @param request - user request to satisfy + * @param _response - pending response + * @param quote - quote to export + * @param cmd - applicable of command request + * @param callback - continuation after saving is complete * * @return Server self to allow for method chaining */ - 'public request': function( request, response, quote, cmd, callback ) + request( + request: UserRequest, + _response: UserResponse, + quote: ServerSideQuote, + cmd: string, + callback: RequestCallback + ) { // cmd represents a request for a single rater if ( !cmd && this._isQuoteValid( quote ) ) @@ -89,12 +122,12 @@ module.exports = Class( 'RatingService', } return this; - }, + } - _getProgramRater: function( program, quote ) + private _getProgramRater( program: Program, quote: ServerSideQuote ) { - var rater = this._raters.byId( program.getId() ); + var rater = this._rater_manager.byId( program.getId() ); // if a rater could not be found, we can't do any rating if ( rater === null ) @@ -107,10 +140,20 @@ module.exports = Class( 'RatingService', } return rater; - }, + } - _isQuoteValid: function( quote ) + /** + * Whether quote is still valid + * + * TODO: This class shouldn't be making this determination, and this + * method is nondeterministic. + * + * @param quote - quote to check + * + * @return whether quote is still valid + */ + private _isQuoteValid( quote: ServerSideQuote ): boolean { // quotes are valid for 30 days var re_date = Math.round( ( ( new Date() ).getTime() / 1000 ) - @@ -129,14 +172,19 @@ module.exports = Class( 'RatingService', } return false; - }, + } - _performRating: function( request, program, quote, indv, c ) + private _performRating( + request: UserRequest, + program: Program, + quote: ServerSideQuote, + indv: string, + c: RequestCallback, + ) { - var _self = this; + var rater = this._getProgramRater( program, quote ); - var rater = this._getProgramRater( program ); if ( !rater ) { this._server.sendError( request, 'Unable to perform rating.' ); @@ -150,49 +198,51 @@ module.exports = Class( 'RatingService', ); rater.rate( quote, request.getSession(), indv, - function( rate_data, actions ) + ( rate_data: RateResult, actions: ClientActions ) => { actions = actions || []; - _self.postProcessRaterData( + this.postProcessRaterData( request, rate_data, actions, program, quote ); const class_dest = {}; - const cleaned = _self._cleanRateData( + const cleaned = this._cleanRateData( rate_data, class_dest ); // TODO: move me during refactoring - _self._dao.saveQuoteClasses( quote, class_dest ); + this._dao.saveQuoteClasses( + quote, class_dest, () => {}, () => {} + ); // save all data server-side (important: do after // post-processing); async - _self._saveRatingData( quote, rate_data, indv, function() + this._saveRatingData( quote, rate_data, indv, function() { // we're done c(); } ); // no need to wait for the save; send the response - _self._server.sendResponse( request, quote, { + this._server.sendResponse( request, quote, { data: cleaned, initialRatedDate: quote.getRatedDate(), lastRatedDate: quote.getLastPremiumDate() }, actions ); }, - function( message ) + ( message: string ) => { - _self._sendRatingError( request, quote, program, + this._sendRatingError( request, quote, program, Error( message ) ); c(); } ); - }, + } /** @@ -202,32 +252,32 @@ module.exports = Class( 'RatingService', * this is to allow us to reference the data (e.g. for reporting) even if * the client does not save it. * - * @param {Quote} quote quote to save data to - * @param {Object} data rating data - * - * @return {undefined} + * @param quote - quote to save data to + * @param data - rating data + * @param indv - individual supplier, or empty + * @param c - callback */ - _saveRatingData: function( quote, data, indv, c ) + private _saveRatingData( + quote: ServerSideQuote, + data: RateResult, + indv: string, + c: RequestCallback + ): void { // only update the last premium calc date on the initial request if ( !indv ) { - var cur_date = Math.round( + var cur_date = Math.round( ( new Date() ).getTime() / 1000 ); quote.setLastPremiumDate( cur_date ); quote.setRatedDate( cur_date ); - function done() - { - c(); - } - // save the last prem status (we pass an empty object as the save // data argument to ensure that we do not save the actual bucket // data, which may cause a race condition with the below merge call) - this._dao.saveQuote( quote, done, done, {} ); + this._dao.saveQuote( quote, c, c, {} ); } else { @@ -239,24 +289,26 @@ module.exports = Class( 'RatingService', // user a rate (if this save fails, it's likely we have bigger problems // anyway); this can also be done concurrently with the above request // since it only modifies a portion of the bucket - this._dao.mergeBucket( quote, data ); - }, + this._dao.mergeBucket( quote, data, () => {}, () => {} ); + } /** * Process rater data returned from a rater * - * @param {UserRequest} request user request to satisfy - * @param {Object} data rating data returned - * @param {Array} actions actions to send to client - * @param {Program} program program used to perform rating - * @param {Quote} quote quote used for rating - * - * @return {undefined} + * @param _request - user request to satisfy + * @param data - rating data returned + * @param actions - actions to send to client + * @param program - program used to perform rating + * @param quote - quote used for rating */ - 'virtual protected postProcessRaterData': function( - request, data, actions, program, quote - ) + protected postProcessRaterData( + _request: UserRequest, + data: RateResult, + actions: ClientActions, + program: Program, + quote: ServerSideQuote, + ): void { var meta = data._cmpdata || {}; @@ -284,18 +336,18 @@ module.exports = Class( 'RatingService', // have a race condition with async. rating (the /visit request may // be made while we're rating, and when we come back we would then // update the step id with a prior, incorrect step) - this._dao.saveQuoteLockState( quote ); + this._dao.saveQuoteLockState( quote, () => {}, () => {} ); } // if any have been deferred, instruct the client to request them // individually if ( Array.isArray( meta.deferred ) && ( meta.deferred.length > 0 ) ) { - var torate = []; + var torate: string[] = []; - meta.deferred.forEach( function( alias ) + meta.deferred.forEach( ( alias: string ) => { - actions.push( { action: 'indvRate', id: alias } ); + actions.push( { action: 'indvRate', after: alias } ); torate.push( alias ); } ); @@ -308,17 +360,29 @@ module.exports = Class( 'RatingService', torate.join( ',' ) ); } - }, + } - _sendRatingError: function( request, quote, program, err ) + /** + * Send rating error to user and log + * + * @param request - user request to satisfy + * @param quote - problem quote + * @param err - error + */ + private _sendRatingError( + request: UserRequest, + quote: ServerSideQuote, + program: Program, + err: Error, + ): void { // well that's no good this._logger.log( this._logger.PRIORITY_ERROR, "Rating for quote %d (program %s) failed: %s", quote.getId(), program.getId(), - err.message + '\n-!' + err.stack.replace( /\n/g, '\n-!' ) + err.message + '\n-!' + ( err.stack || "" ).replace( /\n/g, '\n-!' ) ); this._server.sendError( request, @@ -328,18 +392,25 @@ module.exports = Class( 'RatingService', // show details for internal users ( ( request.getSession().isInternal() ) ? '

[Internal] ' + err.message + '

' + - '
' + err.stack.replace( /\n/g, '
' ) + '
' + ( err.stack || "" ).replace( /\n/g, '
' ) : '' ) ); - }, + } - _processWorksheetData: function( qid, data ) + /** + * Process and save worksheet data from rating results + * + * @param qid - quote id + * @param data - rating result + */ + private _processWorksheetData( qid: QuoteId, data: RateResult ): void { // TODO: this should be done earlier on, so that this is not necessary - var wre = /^(.+)___worksheet$/, - worksheets = {}; + const wre = /^(.+)___worksheet$/; + + const worksheets: Record = {}; // extract worksheets for each supplier for ( var field in data ) @@ -354,33 +425,44 @@ module.exports = Class( 'RatingService', } } - var _self = this; - this._dao.setWorksheets( qid, worksheets, function( err ) + this._dao.setWorksheets( qid, worksheets, ( err: Error | null ) => { if ( err ) { - _self._logger.log( this._logger.PRIORITY_ERROR, + this._logger.log( this._logger.PRIORITY_ERROR, "Failed to save rating worksheets for quote %d", - quote.getId(), - err.message + '\n-!' + err.stack.replace( /\n/g, '\n-!' ) + qid, + err.message + '\n-!' + ( err.stack || "" ).replace( /\n/g, '\n-!' ) ); } } ); - }, + } - serveWorksheet: function( request, quote, supplier, index ) + /** + * Serve worksheet data to user + * + * @param request - user request to satisfy + * @param quote - quote from which to look up worksheet data + * @param supplier - supplier name + * @param index - worksheet index + */ + serveWorksheet( + request: UserRequest, + quote: ServerSideQuote, + supplier: string, + index: PositiveInteger, + ): void { - var qid = quote.getId(), - _self = this; + var qid = quote.getId(); - this._dao.getWorksheet( qid, supplier, index, function( data ) + this._dao.getWorksheet( qid, supplier, index, data => { - _self._server.sendResponse( request, quote, { + this._server.sendResponse( request, quote, { data: data } ); } ); - }, + } /** @@ -389,15 +471,18 @@ module.exports = Class( 'RatingService', * There are certain data saved server-side that there is no use serving to * the client. * - * @param {Object} data rate data + * @param data - rate data + * @param classes - classification data * - * @return {Object} modified rate data + * @return modified rate data */ - 'private _cleanRateData': function( data, classes ) + private _cleanRateData( + data: RateResult, + classes: ClassificationData + ): RateResult { - classes = classes || {}; - - var result = {}; + // forceful cast because the below loop will copy everything + const result = {}; // clear class data for ( var key in data ) @@ -415,6 +500,5 @@ module.exports = Class( 'RatingService', } return result; - }, -} ); - + } +} diff --git a/src/server/service/RatingServicePublish.js b/src/server/service/RatingServicePublish.js index fd5a539..ce843f0 100644 --- a/src/server/service/RatingServicePublish.js +++ b/src/server/service/RatingServicePublish.js @@ -21,8 +21,8 @@ 'use strict'; -const { Trait } = require( 'easejs' ); -const RatingService = require( './RatingService' ); +const { Interface, Trait } = require( 'easejs' ); +const { RatingService } = require( './RatingService' ); /** @@ -51,7 +51,8 @@ const RatingService = require( './RatingService' ); * See the body of `#_sendMessage' for their values. */ module.exports = Trait( 'RatingServicePublish' ) - .extend( RatingService, + .implement( Interface( { 'postProcessRaterData': [] } ) ) + .extend( { /** * AMQP library (amqplib API) @@ -75,7 +76,7 @@ module.exports = Trait( 'RatingServicePublish' ) * * @type {DebugLog} */ - 'private _logger': null, + 'private _log': null, /** @@ -87,9 +88,9 @@ module.exports = Trait( 'RatingServicePublish' ) */ __mixin( amqp, conf, logger ) { - this._amqp = amqp; - this._conf = conf; - this._logger = logger; + this._amqp = amqp; + this._conf = conf; + this._log = logger; }, @@ -104,7 +105,7 @@ module.exports = Trait( 'RatingServicePublish' ) * * @return {undefined} */ - 'override protected postProcessRaterData'( + 'abstract override postProcessRaterData'( request, data, actions, program, quote ) { @@ -127,13 +128,13 @@ module.exports = Trait( 'RatingServicePublish' ) quote ); } ) - .then( () => this._logger.log( - this._logger.PRIORITY_INFO, + .then( () => this._log.log( + this._log.PRIORITY_INFO, "Published quote " + quote.getId() + " to post-rate exchange '" + exchange + "'" ) ) - .catch( e => this._logger.log( - this._logger.PRIORITY_ERROR, + .catch( e => this._log.log( + this._log.PRIORITY_ERROR, "Post-rate exchange publish failure for quote " + quote.getId() + ": " + e.message ) ); diff --git a/test/server/service/RatingServiceTest.js b/test/server/service/RatingServiceTest.ts similarity index 82% rename from test/server/service/RatingServiceTest.js rename to test/server/service/RatingServiceTest.ts index b04c354..8330bea 100644 --- a/test/server/service/RatingServiceTest.js +++ b/test/server/service/RatingServiceTest.ts @@ -19,10 +19,9 @@ * along with this program. If not, see . */ -'use strict' +import { expect } from 'chai'; +import { RatingService as Sut } from "../../../src/server/service/RatingService"; -const { expect } = require( 'chai' ); -const Sut = require( '../../../' ).server.service.RatingService; const RatingServiceStub = require( '../../../' ).test.server.service.RatingServiceStub; describe( 'RatingService', () => @@ -49,15 +48,13 @@ describe( 'RatingService', () => done(); }; - const sut = Sut.extend( + const sut = new class extends Sut { - 'override postProcessRaterData'( - request, data, actions, program, quote - ) + postProcessRaterData() { processed = true; } - } )( logger, dao, server, raters ); + }( logger, dao, server, raters ); sut.request( request, response, quote, 'something', () => {} ); } ); @@ -87,9 +84,9 @@ describe( 'RatingService', () => quote.getRatedDate = () => initial_date; - const sut = Sut( logger, dao, server, raters ); + const sut = new Sut( logger, dao, server, raters ); - server.sendResponse = ( request, quote, resp, actions ) => + server.sendResponse = ( _request: any, _quote: any, resp: any, _actions: any ) => { expect( getLastPremiumDateCallCount ).to.equal( 2 ); expect( resp.initialRatedDate ).to.equal( initial_date ); @@ -98,7 +95,7 @@ describe( 'RatingService', () => done(); }; - sut.request( request, response, quote, null, () => {} ); + sut.request( request, response, quote, "", () => {} ); } ); } );