diff --git a/CHANGELOG.md b/CHANGELOG.md index 7991079a1734a0f60bec4dc1989ad4cbd90d67b3..1f8ae80baef6b94dcecf2355bcc2a2e0c6fee14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.0.21 - 01-07-2019 +### Added +- Add InputUpdate and a FormUpdate Class to store updates from inputs and forms #26 (Gianfranco) +- Create writeFormUpdate method to insert a FormUpdate into database +- Create writeInputUpdate method to insert a InputUpdate into database +- Create updateForm method to unify writeFormUpdate and writeInputUpdate methods +- Create inputUpdate method in OptHandler +- Create formUpdate method in OptHandler + + ## 0.0.20 - 28-06-2019 ## Changed -- Class Input to receive a Enabled atribute +- Class Input to receive a Enabled atribute (Gianfranco) ## 0.0.19 - 12-06-2019 diff --git a/package.json b/package.json index e3daa91910e72762779abd4043a184c9a58b0ed1..4b71ed4bb3a789574c573a6e09680369626e97df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "form-creator-api", - "version": "0.0.20", + "version": "0.0.21", "description": "RESTful API used to manage and answer forms.", "main": "index.js", "scripts": { diff --git a/src/core/formUpdate.spec.ts b/src/core/formUpdate.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e70e86c32c425444b514a039a87452919a093d21 --- /dev/null +++ b/src/core/formUpdate.spec.ts @@ -0,0 +1,20 @@ +/* + * form-creator-api. RESTful API to manage and answer forms. + * Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR + * + * This file is part of form-creator-api. + * + * This program 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 Affero 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 <https://www.gnu.org/licenses/>. + */ diff --git a/src/core/formUpdate.ts b/src/core/formUpdate.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e00057f9de9dade07a103a98cf35d1aaf0cdbf3 --- /dev/null +++ b/src/core/formUpdate.ts @@ -0,0 +1,53 @@ +/* + * form-creator-api. RESTful API to manage and answer forms. + * Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR + * + * This file is part of form-creator-api. + * + * This program 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 Affero 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 <https://www.gnu.org/licenses/>. + */ + +/** Parameters used to update a form object. */ +export interface FormUpdateOptions { + /** Identifier of updated form. */ + id?: number; + /** Form changed. */ + idForm: number; + /** Data of form update. */ + updateDate: Date; +} + +/** + * FormUpdate Class to manage project's forms + */ +export class FormUpdate { + /** Unique identifier of a FormUpdate instance. */ + public readonly id?: number; + /** Form changed. */ + public readonly idForm: number; + /** Data of form update. */ + public readonly updateDate: Date; + + /** + * Creates a new instance of FormUpdate Class + * @param options - FormUpdateOptions instance to update a form. + */ + constructor(options: FormUpdateOptions) { + this.id = options.id ? options.id : null; + this.idForm = options.idForm; + this.updateDate = options.updateDate; + } + +} diff --git a/src/core/inputUpdate.spec.ts b/src/core/inputUpdate.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e70e86c32c425444b514a039a87452919a093d21 --- /dev/null +++ b/src/core/inputUpdate.spec.ts @@ -0,0 +1,20 @@ +/* + * form-creator-api. RESTful API to manage and answer forms. + * Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR + * + * This file is part of form-creator-api. + * + * This program 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 Affero 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 <https://www.gnu.org/licenses/>. + */ diff --git a/src/core/inputUpdate.ts b/src/core/inputUpdate.ts new file mode 100644 index 0000000000000000000000000000000000000000..bec38b112d94e5b767f6c4f33a5cea5f25a30227 --- /dev/null +++ b/src/core/inputUpdate.ts @@ -0,0 +1,64 @@ +/* + * form-creator-api. RESTful API to manage and answer forms. + * Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR + * + * This file is part of form-creator-api. + * + * This program 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 Affero 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 <https://www.gnu.org/licenses/>. + */ + +import { UpdateType } from "../utils/enumHandler"; + +/** Parameters used to update a input object. */ +export interface InputUpdateOptions { + /** Identifier of updated input. */ + id?: number; + /** Form changed. */ + idFormUpdates: number; + /** Input changed. */ + idInput: number; + /** Update operation. */ + inputOperation: UpdateType; + /** Update description. */ + value: string; +} + +/** + * InputUpdate Class to change project's inputs forms + */ +export class InputUpdate { + /** Unique identifier of a InputUpdate instance. */ + public readonly id?: number; + /** Form changed. */ + public readonly idFormUpdates: number; + /** Input changed. */ + public readonly idInput: number; + /** Update operation. */ + public readonly inputOperation: UpdateType; + /** Update description. */ + public readonly value: string; + /** + * Creates a new instance of InputUpdate Class + * @param options - InputUpdateOptions instance to change a input. + */ + constructor(options: InputUpdateOptions) { + this.id = options.id ? options.id : null; + this.idFormUpdates = options.idFormUpdates; + this.idInput = options.idInput; + this.inputOperation = options.inputOperation; + this.value = options.value; + } + +} diff --git a/src/utils/dbHandler.spec.ts b/src/utils/dbHandler.spec.ts index 934be6fbc38875c6d20da947f336ecd3605d3c26..0355330bc59eb2daa85eb16bf3bdffe2b0766d19 100644 --- a/src/utils/dbHandler.spec.ts +++ b/src/utils/dbHandler.spec.ts @@ -23,11 +23,13 @@ import { expect } from "chai"; import { QueryResult } from "pg"; import { Form, FormOptions } from "../core/form"; import { FormAnswer, FormAnswerOptions } from "../core/formAnswer"; +import { FormUpdate, FormUpdateOptions } from "../core/formUpdate"; import { Input, InputOptions, Validation } from "../core/input"; import { InputAnswer, InputAnswerDict, InputAnswerOptions, InputAnswerOptionsDict } from "../core/inputAnswer"; +import { InputUpdate, InputUpdateOptions } from "../core/inputUpdate"; import { configs } from "./config"; import { DbHandler, QueryOptions } from "./dbHandler"; -import { InputType, ValidationType } from "./enumHandler"; +import { InputType, UpdateType, ValidationType } from "./enumHandler"; import { OptHandler } from "./optHandler"; import { TestHandler } from "./testHandler"; @@ -588,4 +590,43 @@ describe("Read and Write on Database", () => { }); + it("should insert a formUpdate", (done) => { + const dateT = new Date(1996, 2, 29); + const formUpdates: FormUpdate = { + idForm: 1 + , updateDate: dateT + }; + + const inputUpdates: InputUpdateOptions = { + idFormUpdates: 1 + , idInput: 1 + , inputOperation: UpdateType.REMOVE + , value: "remove input 1" + }; + dbhandler.updateForm(formUpdates, inputUpdates, (err: Error) => { + expect(err).to.equal(null); + done(); + }); + }); + + it("should not insert an InputUpdate", (done) => { + const dateT = new Date(1996, 2, 29); + const formUpdateObj: FormUpdateOptions = { + idForm: 1 + , updateDate: dateT + }; + const formUpdates = new FormUpdate (formUpdateObj); + const inputUpdates: InputUpdateOptions = { + idFormUpdates: undefined + , idInput: undefined + , inputOperation: undefined + , value: undefined + }; + + dbhandler.updateForm(formUpdates, inputUpdates, (err: Error) => { + expect(err).to.not.equal(null); + done(); + }); + }); + }); diff --git a/src/utils/dbHandler.ts b/src/utils/dbHandler.ts index c6fc75c096cb6919c92775bc16fe810fe4e3246f..75944a48fa711a16cb985d1b4ab1bb462bdb38d1 100644 --- a/src/utils/dbHandler.ts +++ b/src/utils/dbHandler.ts @@ -23,8 +23,10 @@ import { eachOfSeries, eachSeries , map, waterfall } from "async"; import { Pool, PoolConfig, QueryResult } from "pg"; import { Form, FormOptions } from "../core/form"; import { FormAnswer, FormAnswerOptions } from "../core/formAnswer"; +import { FormUpdate, FormUpdateOptions } from "../core/formUpdate"; import { Input, InputOptions, Validation } from "../core/input"; import { InputAnswer, InputAnswerDict, InputAnswerOptions, InputAnswerOptionsDict } from "../core/inputAnswer"; +import { InputUpdate, InputUpdateOptions } from "../core/inputUpdate"; import { EnumHandler, InputType, ValidationType } from "./enumHandler"; import { ErrorHandler} from "./errorHandler"; import { OptHandler } from "./optHandler"; @@ -108,17 +110,17 @@ export class DbHandler { * @param cb.err - Error information when the method fails. * @param cb.form - list of form or a empty list if there is no form on database. */ - public listForms(cb: (err: Error, forms?: Form[]) => void){ - const query: QueryOptions = {query: "SELECT id, title, description FROM form;", parameters: []}; - const forms: Form[] = []; + public listForms(cb: (err: Error, forms?: Form[]) => void){ + const query: QueryOptions = {query: "SELECT id, title, description FROM form;", parameters: []}; + const forms: Form[] = []; - this.executeQuery(query, (err: Error, result?: QueryResult) => { - if (err) { - cb(err); - return; - } + this.executeQuery(query, (err: Error, result?: QueryResult) => { + if (err) { + cb(err); + return; + } - for (const row of result.rows){ + for (const row of result.rows){ const formObj: FormOptions = { id: row["id"] , title: row["title"] @@ -127,7 +129,7 @@ export class DbHandler { }; let formTmp: Form; try{ - formTmp = new Form ( OptHandler.form(formObj)); + formTmp = new Form ( OptHandler.form(formObj)); } catch (e){ cb(e); @@ -135,10 +137,10 @@ export class DbHandler { } forms.push(formTmp); - } - cb(err, forms); - }); - } + } + cb(err, forms); + }); + } /** * Asynchronously executes a query and get a Form. @@ -153,19 +155,19 @@ export class DbHandler { const query: QueryOptions = {query: queryString, parameters: [id]}; waterfall([ - (callback: (err: Error, result?: QueryResult) => void) => { - this.begin(callback); - }, - (result: QueryResult, callback: (err: Error, result?: QueryResult) => void) => { + (callback: (err: Error, result?: QueryResult) => void) => { + this.begin(callback); + }, + (result: QueryResult, callback: (err: Error, result?: QueryResult) => void) => { this.executeQuery(query, callback); - }, - (result: QueryResult, callback: (err: Error, form?: Form) => void) => { - - if (result.rowCount !== 1){ - callback(ErrorHandler.badIdAmount(result.rowCount)); - return; - } - this.readInputWithFormId(id, (error: Error, inputsResult: Input[]) => { + }, + (result: QueryResult, callback: (err: Error, form?: Form) => void) => { + + if (result.rowCount !== 1){ + callback(ErrorHandler.badIdAmount(result.rowCount)); + return; + } + this.readInputWithFormId(id, (error: Error, inputsResult: Input[]) => { const formObj: FormOptions = { id: result.rows[0]["id"] , title: result.rows[0]["title"] @@ -183,26 +185,26 @@ export class DbHandler { } callback(error, formTmp); - }); - }, - (form: Form, callback: (err: Error, form?: Form) => void) => { - - this.commit((error: Error, result?: QueryResult) => { - callback(error, form); - }); - } - ], (err, result: Form) => { - - if (err){ - this.rollback( (error: Error, results?: QueryResult) => { - cb(err); - return; - }); - return; - } - cb(err, result); - - }); + }); + }, + (form: Form, callback: (err: Error, form?: Form) => void) => { + + this.commit((error: Error, result?: QueryResult) => { + callback(error, form); + }); + } + ], (err, result: Form) => { + + if (err){ + this.rollback( (error: Error, results?: QueryResult) => { + cb(err); + return; + }); + return; + } + cb(err, result); + + }); } @@ -295,8 +297,8 @@ export class DbHandler { */ private readInputValidationArgumentWithInputValidationId(id: number, cb: (err: Error, argumentsArray?: any[]) => void){ const queryString: string = "SELECT id, argument, placement FROM \ - input_validation_argument WHERE \ - id_input_validation=$1;"; +input_validation_argument WHERE \ +id_input_validation=$1;"; const query: QueryOptions = { query: queryString , parameters: [id] @@ -363,7 +365,7 @@ export class DbHandler { this.begin(callback); }, (result: QueryResult, callback: (err: Error, result?: QueryResult) => void) => { - this.executeQuery(query, callback); + this.executeQuery(query, callback); }, (result: QueryResult, callback: (err: Error, formId?: number) => void) => { if (result.rowCount !== 1){ @@ -416,8 +418,8 @@ export class DbHandler { */ private writeInputWithFormId(formId: number, input: Input, cb: (err: Error) => void){ const queryString: string = "INSERT INTO input (\ - id_form, placement, input_type, question, description)\ - VALUES ( $1, $2, $3, $4, $5) RETURNING id;"; +id_form, placement, input_type, question, description)\ +VALUES ( $1, $2, $3, $4, $5) RETURNING id;"; const query: QueryOptions = { query: queryString @@ -431,26 +433,26 @@ export class DbHandler { }; this.executeQuery(query, (err: Error, result?: QueryResult) => { - if (err){ - cb(err); - return; - } + if (err){ + cb(err); + return; + } - if (result.rowCount !== 1){ - cb(ErrorHandler.notInserted("Input")); - return; - } + if (result.rowCount !== 1){ + cb(ErrorHandler.notInserted("Input")); + return; + } - eachSeries(input.validation, (validation: Validation, callback) => { + eachSeries(input.validation, (validation: Validation, callback) => { - this.writeValidationWithInputId(result.rows[0]["id"], validation, callback); + this.writeValidationWithInputId(result.rows[0]["id"], validation, callback); - }, (error) => { + }, (error) => { - cb(error); + cb(error); - }); - }); + }); + }); } /** @@ -463,8 +465,8 @@ export class DbHandler { private writeValidationWithInputId(inputId: number, validation: Validation, cb: (err: Error) => void){ const queryString: string = "INSERT INTO input_validation\ - ( id_input, validation_type) VALUES\ - ( $1, $2 ) RETURNING id;"; +( id_input, validation_type) VALUES\ +( $1, $2 ) RETURNING id;"; const query: QueryOptions = { query: queryString @@ -505,57 +507,57 @@ export class DbHandler { * @param cb - Callback function. * @param cb.err - Error information when the method fails. */ - private writeValidationArgumentWithInputIdAndPlacement(validationId: number, argument: string, placement: number, cb: (err: Error) => void){ - - const queryString: string = "INSERT INTO input_validation_argument \ - ( id_input_validation, argument, placement ) VALUES\ - ( $1, $2, $3 ) RETURNING id;"; - - const query: QueryOptions = { - query: queryString - , parameters: [ - validationId - , argument - , placement - ] - }; - - this.executeQuery(query, (err: Error, result?: QueryResult) => { - - if (err){ - cb(err); - return; - } - - if (result.rowCount !== 1){ - cb(ErrorHandler.notInserted("Validation Argument")); - return; - } - - cb(err); - - }); - - } - - /** - * Asynchronously insert a form answer on Database and return it. - * @param formAnswer - FormAnswer to be inserted. - * @param cb - Callback function which contains the inserted data. - * @param cb.err - Error information when the method fails. - * @param cb.formAnswerResult - Form or null if form any error occurs. - */ - public writeFormAnswer(formAnswer: FormAnswer, cb: (err: Error, formAnswerResult?: FormAnswer) => void){ - const queryString: string = "INSERT INTO form_answer (id_form, answered_at) VALUES( $1, $2 ) RETURNING id;"; - const query: QueryOptions = { - query: queryString - , parameters: [ - formAnswer.form.id - , formAnswer.timestamp - ] - }; - - waterfall([ + private writeValidationArgumentWithInputIdAndPlacement(validationId: number, argument: string, placement: number, cb: (err: Error) => void){ + + const queryString: string = "INSERT INTO input_validation_argument \ +( id_input_validation, argument, placement ) VALUES\ +( $1, $2, $3 ) RETURNING id;"; + + const query: QueryOptions = { + query: queryString + , parameters: [ + validationId + , argument + , placement + ] + }; + + this.executeQuery(query, (err: Error, result?: QueryResult) => { + + if (err){ + cb(err); + return; + } + + if (result.rowCount !== 1){ + cb(ErrorHandler.notInserted("Validation Argument")); + return; + } + + cb(err); + + }); + + } + + /** + * Asynchronously insert a form answer on Database and return it. + * @param formAnswer - FormAnswer to be inserted. + * @param cb - Callback function which contains the inserted data. + * @param cb.err - Error information when the method fails. + * @param cb.formAnswerResult - Form or null if form any error occurs. + */ + public writeFormAnswer(formAnswer: FormAnswer, cb: (err: Error, formAnswerResult?: FormAnswer) => void){ + const queryString: string = "INSERT INTO form_answer (id_form, answered_at) VALUES( $1, $2 ) RETURNING id;"; + const query: QueryOptions = { + query: queryString + , parameters: [ + formAnswer.form.id + , formAnswer.timestamp + ] + }; + + waterfall([ (callback: (err: Error, result?: QueryResult) => void) => { this.begin(callback); }, @@ -564,8 +566,8 @@ export class DbHandler { }, (result: QueryResult, callback: (err: Error, formAnswerId?: number) => void) => { if (result.rowCount !== 1){ - callback(ErrorHandler.notInserted("FormAnswer")); - return; + callback(ErrorHandler.notInserted("FormAnswer")); + return; } // NOTE: Although this two "FOR"s the complexity is O(n) // This first eachSeries iterates over the keys of the dictonary @@ -573,7 +575,7 @@ export class DbHandler { // this second one iterates over the array of the objects within the current key eachSeries(formAnswer.inputAnswers[parseInt(key, 10)], (inputsAnswer: InputAnswer, innerCallback) => { - this.writeInputAnswerWithFormId(result.rows[0]["id"], inputsAnswer, innerCallback); + this.writeInputAnswerWithFormId(result.rows[0]["id"], inputsAnswer, innerCallback); }, (error) => { if (error){ outerCallback(error); @@ -581,7 +583,7 @@ export class DbHandler { } outerCallback(error); }); - }, (error) => { + }, (error) => { if (error){ callback(error); return; @@ -592,172 +594,286 @@ export class DbHandler { }, (formAnswerId: number, callback: (err: Error, formId?: number) => void) => { - this.commit((error: Error, result?: QueryResult) => { - callback(error, formAnswerId); - }); - }, - (formAnswerId: number, callback: (err: Error, formResult?: FormAnswer) => void) => { - - this.readFormAnswer(formAnswerId, callback); - - } - ], (err, result: FormAnswer) => { - - if (err){ - this.rollback( (error: Error, results?: QueryResult) => { - cb(err); - return; - }); - return; - } - cb(err, result); - - }); - } - - /** - * Asynchronously insert a Input Answer on Database and return it. - * @param formAnswerId - Form Answer identifier to relate with Input Answer. - * @param inputsAnswer - InputsAnswer to be inserted. - * @param cb - Callback function. - * @param cb.err - Error information when the method fails. - */ - private writeInputAnswerWithFormId(formAnswerId: number, inputAnswer: InputAnswer, cb: (err: Error) => void){ - const queryString: string = "INSERT INTO input_answer (\ - id_form_answer, id_input, value, placement)\ - VALUES ( $1, $2, $3, $4) RETURNING id;"; - - const query: QueryOptions = { - query: queryString - , parameters: [ - formAnswerId - , inputAnswer.idInput - , inputAnswer.value - , inputAnswer.placement - ] - }; - - this.executeQuery(query, (err: Error, result?: QueryResult) => { - if (err){ - cb(err); - return; - } - - if (result.rowCount !== 1){ - cb(ErrorHandler.notInserted("InputsAnswer")); - return; - } - cb(err); - }); - - } - - /** - * Asynchronously executes a query and get a Form. - * @param id - Form identifier to be founded. - * @param cb - Callback function which contains the data read. - * @param cb.err - Error information when the method fails. - * @param cb.form - Form or null if form not exists. - */ - public readFormAnswer(id: number, cb: (err: Error, formAnswer?: FormAnswer) => void){ - - const queryString: string = "SELECT id, id_form, answered_at FROM form_answer WHERE id=$1;"; - const query: QueryOptions = {query: queryString, parameters: [id]}; - - waterfall([ - (callback: (err: Error, result?: QueryResult) => void) => { - this.begin(callback); - }, - (result: QueryResult, callback: (err: Error, result?: QueryResult) => void) => { - this.executeQuery(query, callback); - }, - (result: QueryResult, callback: (err: Error, formAnswer?: FormAnswer) => void) => { - if (result.rowCount !== 1){ - callback(ErrorHandler.badIdAmount(result.rowCount)); - return; - } - - this.readForm(result.rows[0]["id_form"], (err: Error, formResult: Form) => { - this.readInputAnswerWithFormAnswerId(result.rows[0]["id"], (error: Error, inputsAnswerOptionsResult: InputAnswerOptionsDict) => { - const formAnswerOpt: FormAnswerOptions = { - id: result.rows[0]["id"] - , form: formResult - , inputsAnswerOptions: inputsAnswerOptionsResult - , timestamp : result.rows[0]["answered_at"] - }; - let formAnswerTmp: FormAnswer; - - try{ - formAnswerTmp = new FormAnswer ( OptHandler.formAnswer(formAnswerOpt)); - } - catch (e){ - callback(e); - return; - } - - callback(error, formAnswerTmp); - }); - }); - }, - (formAnswer: FormAnswer, callback: (err: Error, formAnswer?: FormAnswer) => void) => { - - this.commit((error: Error, result?: QueryResult) => { - callback(error, formAnswer); - }); - } - ], (err, result: FormAnswer) => { - - if (err){ - this.rollback( (error: Error, results?: QueryResult) => { - cb(err); - return; - }); - return; - } - cb(err, result); - - }); - - } - - /** - * A private method to asynchronously executes a query and get a list of Inputs. - * @param id - Form identifier which inputs are linked to. - * @param cb - Callback function which contains the data read. - * @param cb.err - Error information when the method fails. - * @param cb.inputs - Input array or an empty list if there is no input linked to form. - */ - private readInputAnswerWithFormAnswerId(id: number, cb: (err: Error, inputsAnswerResult?: InputAnswerOptionsDict) => void){ - const queryString: string = "SELECT id, id_form_answer, id_input, value,\ - placement FROM input_answer WHERE id_form_answer=$1;"; - const query: QueryOptions = {query: queryString, parameters: [id]}; - - this.executeQuery(query, (err: Error, result?: QueryResult) => { - - const inputAnswersOpts: InputAnswerOptions[] = result.rows.map( (inputsAnswerResult) => { - const inputAnswersOpt: InputAnswerOptions = { - id: inputsAnswerResult["id"] - , idInput: inputsAnswerResult["id_input"] - , value: inputsAnswerResult["value"] - , placement: inputsAnswerResult["placement"] - }; - return OptHandler.inputAnswer(inputAnswersOpt); - }); - - const inputsAnswerResults: InputAnswerOptionsDict = {}; - for (const i of inputAnswersOpts){ - // FIXME: There is no coverage teste for this "IF" - // it happens because, until this date (04/Jun/2019) I did not implement multivalored inputs - // I hope someday someone implements it - if ( inputsAnswerResults[i["idInput"]] ) { - inputsAnswerResults[i["idInput"]].push(i); - inputsAnswerResults[i["idInput"]] = DbHandler.sortByPlacement(inputsAnswerResults[i["idInput"]]); - }else{ - inputsAnswerResults[i["idInput"]] = [i]; - } - } - cb(err, inputsAnswerResults); - }); - } + this.commit((error: Error, result?: QueryResult) => { + callback(error, formAnswerId); + }); + }, + (formAnswerId: number, callback: (err: Error, formResult?: FormAnswer) => void) => { + + this.readFormAnswer(formAnswerId, callback); + + } + ], (err, result: FormAnswer) => { + + if (err){ + this.rollback( (error: Error, results?: QueryResult) => { + cb(err); + return; + }); + return; + } + cb(err, result); + + }); + } + + /** + * Asynchronously insert a Input Answer on Database and return it. + * @param formAnswerId - Form Answer identifier to relate with Input Answer. + * @param inputsAnswer - InputsAnswer to be inserted. + * @param cb - Callback function. + * @param cb.err - Error information when the method fails. + */ + private writeInputAnswerWithFormId(formAnswerId: number, inputAnswer: InputAnswer, cb: (err: Error) => void){ + const queryString: string = "INSERT INTO input_answer (\ +id_form_answer, id_input, value, placement)\ +VALUES ( $1, $2, $3, $4) RETURNING id;"; + + const query: QueryOptions = { + query: queryString + , parameters: [ + formAnswerId + , inputAnswer.idInput + , inputAnswer.value + , inputAnswer.placement + ] + }; + + this.executeQuery(query, (err: Error, result?: QueryResult) => { + if (err){ + cb(err); + return; + } + + if (result.rowCount !== 1){ + cb(ErrorHandler.notInserted("InputsAnswer")); + return; + } + cb(err); + }); + + } + + /** + * Asynchronously executes a query and get a Form. + * @param id - Form identifier to be founded. + * @param cb - Callback function which contains the data read. + * @param cb.err - Error information when the method fails. + * @param cb.form - Form or null if form not exists. + */ + public readFormAnswer(id: number, cb: (err: Error, formAnswer?: FormAnswer) => void){ + + const queryString: string = "SELECT id, id_form, answered_at FROM form_answer WHERE id=$1;"; + const query: QueryOptions = {query: queryString, parameters: [id]}; + + waterfall([ + (callback: (err: Error, result?: QueryResult) => void) => { + this.begin(callback); + }, + (result: QueryResult, callback: (err: Error, result?: QueryResult) => void) => { + this.executeQuery(query, callback); + }, + (result: QueryResult, callback: (err: Error, formAnswer?: FormAnswer) => void) => { + if (result.rowCount !== 1){ + callback(ErrorHandler.badIdAmount(result.rowCount)); + return; + } + + this.readForm(result.rows[0]["id_form"], (err: Error, formResult: Form) => { + this.readInputAnswerWithFormAnswerId(result.rows[0]["id"], (error: Error, inputsAnswerOptionsResult: InputAnswerOptionsDict) => { + const formAnswerOpt: FormAnswerOptions = { + id: result.rows[0]["id"] + , form: formResult + , inputsAnswerOptions: inputsAnswerOptionsResult + , timestamp : result.rows[0]["answered_at"] + }; + let formAnswerTmp: FormAnswer; + + try{ + formAnswerTmp = new FormAnswer ( OptHandler.formAnswer(formAnswerOpt)); + } + catch (e){ + callback(e); + return; + } + + callback(error, formAnswerTmp); + }); + }); + }, + (formAnswer: FormAnswer, callback: (err: Error, formAnswer?: FormAnswer) => void) => { + + this.commit((error: Error, result?: QueryResult) => { + callback(error, formAnswer); + }); + } + ], (err, result: FormAnswer) => { + + if (err){ + this.rollback( (error: Error, results?: QueryResult) => { + cb(err); + return; + }); + return; + } + cb(err, result); + + }); + + } + + /** + * A private method to asynchronously executes a query and get a list of Inputs. + * @param id - Form identifier which inputs are linked to. + * @param cb - Callback function which contains the data read. + * @param cb.err - Error information when the method fails. + * @param cb.inputs - Input array or an empty list if there is no input linked to form. + */ + private readInputAnswerWithFormAnswerId(id: number, cb: (err: Error, inputsAnswerResult?: InputAnswerOptionsDict) => void){ + const queryString: string = "SELECT id, id_form_answer, id_input, value,\ +placement FROM input_answer WHERE id_form_answer=$1;"; + const query: QueryOptions = {query: queryString, parameters: [id]}; + + this.executeQuery(query, (err: Error, result?: QueryResult) => { + + const inputAnswersOpts: InputAnswerOptions[] = result.rows.map( (inputsAnswerResult) => { + const inputAnswersOpt: InputAnswerOptions = { + id: inputsAnswerResult["id"] + , idInput: inputsAnswerResult["id_input"] + , value: inputsAnswerResult["value"] + , placement: inputsAnswerResult["placement"] + }; + return OptHandler.inputAnswer(inputAnswersOpt); + }); + + const inputsAnswerResults: InputAnswerOptionsDict = {}; + for (const i of inputAnswersOpts){ + // FIXME: There is no coverage teste for this "IF" + // it happens because, until this date (04/Jun/2019) I did not implement multivalored inputs + // I hope someday someone implements it + if ( inputsAnswerResults[i["idInput"]] ) { + inputsAnswerResults[i["idInput"]].push(i); + inputsAnswerResults[i["idInput"]] = DbHandler.sortByPlacement(inputsAnswerResults[i["idInput"]]); + }else{ + inputsAnswerResults[i["idInput"]] = [i]; + } + } + cb(err, inputsAnswerResults); + }); + } + + /** + * Asynchronously insert a form and a input update on Database. + * @param formUpdates - Relate a form with his updates. + * @param inputUpdateObj - InputUpdate to be inserted. + * @param cb - Callback function. + * @param cb.err - Error information when the method fails. + */ + public updateForm(formUpdates: FormUpdate, inputUpdateObj: InputUpdateOptions, cb: (err: Error, updateResult?: FormUpdate) => void) { + + waterfall([ + (callback: (err: Error, result?: QueryResult) => void) => { + this.begin(callback); + }, + (result: QueryResult, callback: (err: Error, result?: InputUpdate) => void) => { + this.writeFormUpdate(formUpdates, (err, formUpdateResult: number) => { + if (err) { + callback(err); + return; + } + try { + inputUpdateObj.idFormUpdates = formUpdateResult; + const inputUpdate: InputUpdate = new InputUpdate(OptHandler.inputUpdate(inputUpdateObj)); + callback(null, inputUpdate); + return; + } + catch (e) { + callback(e); + return; + } + }); + }, + (inputUpdates: InputUpdate, callback: (err: Error) => void) => { + this.writeInputUpdate(inputUpdates, (err, result) => { + if (err) { + callback(err); + return; + } + callback(null); + }); + }, + (callback: (err: Error) => void) => { + this.commit((error: Error, result?: QueryResult) => { + callback(error); + }); + } + ], (err) => { + if (err){ + this.rollback( (error: Error, results?: QueryResult) => { + cb(err); + return; + }); + return; + } + cb(err); + }); + + } + + /** + * A private method that execute a query and insert a FormUpdate in the database + * @param formUpdate - Form Update to be inserted. + * @param cb - Callback function. + * @param cb.err - Error information when the method fails. + */ + private writeFormUpdate(formUpdate: FormUpdate, cb: (err: Error, formUpdateResult?: number) => void) { + + const queryString: string = "INSERT INTO form_updates (id_form, update_data) \ +VALUES ( $1, $2 ) RETURNING id;"; + const query: QueryOptions = { + query: queryString + , parameters: [ + formUpdate.idForm + , formUpdate.updateDate + ] + }; + + this.executeQuery(query, (err: Error, result?: QueryResult) => { + if (err) { + cb(err); + return; + } + cb(err, result.rows[0].id); + }); + + } + + /** + * A private method that execute a query and insert a InputUpdate in the database + * @param inputUpdate - Input Update to be inserted. + * @param cb - Callback function. + * @param cb.err - Error information when the method fails. + */ + private writeInputUpdate(inputUpdate: InputUpdate, cb: (err: Error, inputUpdateResult?: InputUpdate[]) => void) { + + const queryString: string = "INSERT INTO input_updates (id_form_updates, id_input, input_operation, value) VALUES ( $1, $2, $3, $4 );"; + const query: QueryOptions = { + query: queryString + , parameters: [ + inputUpdate.idFormUpdates + , inputUpdate.idInput + , inputUpdate.inputOperation + , inputUpdate.value + ] + }; + + this.executeQuery(query, (err: Error, result?: QueryResult) => { + if (err) { + cb(err); + return; + } + cb(err); + }); + } } diff --git a/src/utils/enumHandler.ts b/src/utils/enumHandler.ts index 247325fa8e573f150977ad1fe697a94a1631e4e1..0f194087b837e4b85ebb1733ad0f6006acddc0dc 100644 --- a/src/utils/enumHandler.ts +++ b/src/utils/enumHandler.ts @@ -29,6 +29,12 @@ export enum InputType { TEXT } +export enum UpdateType { + ADD, + REMOVE, + SWAP +} + /** * Available Validation types */ diff --git a/src/utils/optHandler.spec.ts b/src/utils/optHandler.spec.ts index a2742be9686f0bdf9039f97dab7aa1ff37d80826..0ed895e96d1cc54546fbf47abd20011f3e9a9a90 100644 --- a/src/utils/optHandler.spec.ts +++ b/src/utils/optHandler.spec.ts @@ -21,9 +21,11 @@ import { expect } from "chai"; import { Form, FormOptions } from "../core/form"; import { FormAnswer, FormAnswerOptions } from "../core/formAnswer"; + import { FormUpdate, FormUpdateOptions } from "../core/formUpdate"; import { Input, InputOptions, Validation } from "../core/input"; import { InputAnswer, InputAnswerDict, InputAnswerOptions, InputAnswerOptionsDict } from "../core/inputAnswer"; - import { InputType, ValidationType } from "./enumHandler"; + import { InputUpdate, InputUpdateOptions } from "../core/inputUpdate"; + import { InputType, UpdateType, ValidationType } from "./enumHandler"; import { ErrorHandler } from "./errorHandler"; import { OptHandler } from "./optHandler"; @@ -970,12 +972,122 @@ , inputsAnswerOptions: inputAnswerOptionsDict }; let formAnswerTmp: FormAnswer; - try{ formAnswerTmp = new FormAnswer(OptHandler.formAnswer(formAnswerOptions)); - }finally{ + }finally{ expect(formAnswerTmp).to.not.be.a("undefined"); } + }); + + it("should get error when missing FormUpdate idForm", () => { + const dateTMP: Date = new Date(1996, 2, 29); + const FormObj: any = { + id: 1 + , updateDate: dateTMP + }; + + let formUpdateTmp: FormUpdate; + + try{ + formUpdateTmp = new FormUpdate(OptHandler.formUpdate(FormObj)); + + } catch (e){ + expect(e).to.be.not.a("null"); + expect(e.message).to.be.equal(ErrorHandler.notFound("idForm").message); + expect(formUpdateTmp).to.be.a("undefined"); + } + + }); + + it("should get error when missing InputUpdate idInput", () => { + const inputUpdateObj: any = { + id: 1 + , idFormUpdates: 1 + , inputOperation: "rem" + , value: "remove input 1" + }; + let inputUpdateTmp: InputUpdate; + + try { + inputUpdateTmp = new InputUpdate(OptHandler.inputUpdate(inputUpdateObj)); + } + catch (e){ + expect(e).to.be.not.a("null"); + expect(e.message).to.be.equal(ErrorHandler.notFound("idInput").message); + expect(inputUpdateTmp).to.be.a("undefined"); + } + + }); + + it("should get error when missing InputUpdate inputOperation", () => { + const inputUpdateObj: any = { + id: 1 + , idFormUpdates: 1 + , idInput: 1 + , value: "remove input 1" + }; + let inputUpdateTmp: InputUpdate; + + try { + inputUpdateTmp = new InputUpdate(OptHandler.inputUpdate(inputUpdateObj)); + } catch (e) { + expect(e).to.be.not.a("null"); + expect(e.message).to.be.equal(ErrorHandler.notFound("inputOperation").message); + expect(inputUpdateTmp).to.be.a("undefined"); + } + }); + + it("should get error when missing InputUpdate value", () => { + const inputUpdateObj: any = { + id: 1 + , idFormUpdates: 1 + , idInput: 1 + , inputOperation: UpdateType.REMOVE + }; + let inputUpdateTmp: InputUpdate; + + try { + inputUpdateTmp = new InputUpdate(OptHandler.inputUpdate(inputUpdateObj)); + } catch (e) { + expect(e).to.be.not.a("null"); + expect(e.message).to.be.equal(ErrorHandler.notFound("value").message); + expect(inputUpdateTmp).to.be.a("undefined"); + } + }); + + it("should test inputUpdate no error", () => { + const inputUpdateObj: InputUpdateOptions = { + id: 1 + , idFormUpdates: 1 + , idInput: 1 + , inputOperation: UpdateType.REMOVE + , value: "remove input 1 from form 1" + }; + + let inputUpdateTmp: InputUpdate; + + try { + inputUpdateTmp = new InputUpdate(OptHandler.inputUpdate(inputUpdateObj)); + } finally { + expect(inputUpdateTmp).to.not.be.a("undefined"); + } + }); + + it("should test formUpdate no error", () => { + const dateTMP: Date = new Date(1996, 2, 29); + const formUpdateObj: FormUpdateOptions = { + id: 1 + , idForm: 1 + , updateDate: dateTMP + }; + + let formUpdateTmp: FormUpdate; + + try { + formUpdateTmp = new FormUpdate(OptHandler.formUpdate(formUpdateObj)); + } finally { + expect(formUpdateTmp).to.not.be.a("undefined"); + } + }); - }); }); diff --git a/src/utils/optHandler.ts b/src/utils/optHandler.ts index cbdbe03a882d048b5b0e82d00a210e69d99583a6..057259037c85edf8843b8a0d598d9a5c9987354d 100644 --- a/src/utils/optHandler.ts +++ b/src/utils/optHandler.ts @@ -21,11 +21,12 @@ import { Form, FormOptions } from "../core/form"; import { FormAnswerOptions } from "../core/formAnswer"; + import { FormUpdate, FormUpdateOptions } from "../core/formUpdate"; import { InputOptions, Validation } from "../core/input"; import { InputAnswer, InputAnswerDict, InputAnswerOptions, InputAnswerOptionsDict } from "../core/inputAnswer"; - import { InputType} from "./enumHandler"; + import { InputUpdate, InputUpdateOptions } from "../core/inputUpdate"; + import { InputType, UpdateType} from "./enumHandler"; import { ErrorHandler} from "./errorHandler"; - /** * OptHandler to handle an object and transform into a Classoptions to be used in Class's constructor */ @@ -128,6 +129,7 @@ return option; } + /** * Return an InputAnswerOptions instance with a parsed and validated object, The main objective is parse any error previously * @param obj - object that should be parsed. @@ -154,4 +156,50 @@ return option; } + /** + * Return an FormUpdateOptions instance with a parsed and validated object, The main objective is parse any error previously + * @param obj - object that should be parsed. + * @returns - An FormUpdateOptions instance. + */ + public static formUpdate(obj: any): FormUpdateOptions { + if (obj.idForm === undefined) { + throw ErrorHandler.notFound("idForm"); + } + + const option: FormUpdateOptions = { + id: obj.id + , idForm: obj.idForm + , updateDate: obj.updateDate + }; + + return option; + } + + /** + * Return an FormUpdateOptions instance with a parsed and validated object, The main objective is to detect parsing errors previously + * @param obj - object that should be parsed. + * @returns - An FormUpdateOptions instance. + */ + public static inputUpdate(obj: any): InputUpdateOptions { + if (obj.idInput === undefined) { + throw ErrorHandler.notFound("idInput"); + } + if (obj.inputOperation === undefined) { + throw ErrorHandler.notFound("inputOperation"); + } + if (obj.value === undefined) { + throw ErrorHandler.notFound("value"); + } + + const option: InputUpdateOptions = { + id: obj.id + , idFormUpdates: obj.idFormUpdates + , idInput: obj.idInput + , inputOperation: obj.inputOperation + , value: obj.value + }; + + return option; + } + }