From 767a248e44db49ee3dee0b5131c2af00d264a93b Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 23 Oct 2019 12:09:27 -0400 Subject: [PATCH] RatingService: Convert to TypeScript This was an adventure, and was also used as a peer programming exercise to introduce TypeScript to other programmers in the office. This class has far too many dependencies, which made this difficult. The approach was to create .d.ts files for dependencies and wait on moving those over for now, otherwise the task will never get done. The RatingServicePublic trait was left as such for the time being; I was able to work around a bug that was making it difficult to mix it into a prototype. There were no logic changes; this was just type refactoring. --- src/client/action/ClientAction.ts | 39 +++ src/program/Program.d.ts | 27 ++ src/quote/BaseQuote.d.ts | 93 ++++++ src/quote/Quote.d.ts | 33 +++ src/server/Server.d.ts | 66 +++++ src/server/daemon/controller.js | 6 +- src/server/db/MongoServerDao.d.ts | 146 +++++++++ src/server/log/PriorityLog.d.ts | 45 +++ src/server/quote/ServerSideQuote.d.ts | 46 +++ src/server/rater/ProcessManager.d.ts | 35 +++ src/server/rater/ProcessManager.js | 3 + src/server/rater/Rater.d.ts | 121 ++++++++ src/server/request/UserRequest.d.ts | 36 +++ src/server/request/UserResponse.d.ts | 28 ++ src/server/request/UserSession.d.ts | 34 +++ .../{RatingService.js => RatingService.ts} | 280 ++++++++++++------ src/server/service/RatingServicePublish.js | 25 +- ...ingServiceTest.js => RatingServiceTest.ts} | 19 +- 18 files changed, 959 insertions(+), 123 deletions(-) create mode 100644 src/client/action/ClientAction.ts create mode 100644 src/program/Program.d.ts create mode 100644 src/quote/BaseQuote.d.ts create mode 100644 src/quote/Quote.d.ts create mode 100644 src/server/Server.d.ts create mode 100644 src/server/db/MongoServerDao.d.ts create mode 100644 src/server/log/PriorityLog.d.ts create mode 100644 src/server/quote/ServerSideQuote.d.ts create mode 100644 src/server/rater/ProcessManager.d.ts create mode 100644 src/server/rater/Rater.d.ts create mode 100644 src/server/request/UserRequest.d.ts create mode 100644 src/server/request/UserResponse.d.ts create mode 100644 src/server/request/UserSession.d.ts rename src/server/service/{RatingService.js => RatingService.ts} (57%) rename test/server/service/{RatingServiceTest.js => RatingServiceTest.ts} (82%) 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, "", () => {} ); } ); } );