From 3cf02cb567711bb6b43d6da61727c9d4feacb5ff Mon Sep 17 00:00:00 2001 From: "Dzmitry_Tamashevich@epam.com" Date: Tue, 3 Nov 2020 12:22:02 +0300 Subject: [PATCH] refactor import usage. refactor invoices implementation --- media-store/db/schema.cds | 9 ++- media-store/package.json | 6 +- media-store/server.js | 4 +- media-store/srv/browse-invoices-service.js | 27 ++++----- media-store/srv/browse-tracks-service.js | 20 +++---- media-store/srv/manage-store-service.cds | 9 +++ media-store/srv/manage-store-service.js | 26 +++++++++ media-store/srv/manage-tracks-service.cds | 13 ----- media-store/util/importData.js | 65 ++++++++-------------- 9 files changed, 91 insertions(+), 88 deletions(-) create mode 100644 media-store/srv/manage-store-service.cds create mode 100644 media-store/srv/manage-store-service.js delete mode 100644 media-store/srv/manage-tracks-service.cds diff --git a/media-store/db/schema.cds b/media-store/db/schema.cds index 74fe4e89..aa86bf4d 100644 --- a/media-store/db/schema.cds +++ b/media-store/db/schema.cds @@ -1,7 +1,7 @@ namespace sap.capire.media.store; aspect Named { - key ID : Integer; + key ID : Integer default 1; name : String(120); } @@ -76,7 +76,6 @@ entity Invoices { on invoiceItems.invoice = $self; status : Integer enum { submitted = 1; - shipped = 2; canceled = -1; } default 1; } @@ -96,9 +95,9 @@ entity Tracks { mediaType : Association to MediaTypes; genre : Association to Genres; composer : String(220); - milliseconds : Integer; - bytes : Integer; - unitPrice : Decimal(10, 2); + milliseconds : Integer default 230619; + bytes : Integer default 3990994; + unitPrice : Decimal(10, 2) default 0.99; invoiceItems : Association to many InvoiceItems on invoiceItems.track = $self; virtual alreadyOrdered : Boolean; diff --git a/media-store/package.json b/media-store/package.json index 71e0fea6..e71a80ab 100644 --- a/media-store/package.json +++ b/media-store/package.json @@ -6,7 +6,7 @@ "license": "UNLICENSED", "private": true, "dependencies": { - "@sap/cds": "^4", + "@sap/cds": "^4.2.8", "express": "^4", "moment": "^2.29.1", "passport": "^0.4.1" @@ -17,14 +17,14 @@ "scripts": { "start": "npx cds run", "deploy": "cds deploy --to sqlite:mychinook.db", - "import": "node ./util/importData.js sqlite:chinook.db sqlite:mychinook.db ./db/schema.cds", - "deploy:import": "npm run deploy && npm run import", + "rebuild": "npm run deploy && npm run start", "test": "mocha test/media-service.test.js --verbose --timeout 10000" }, "cds": { "requires": { "db": { "kind": "sqlite", + "model": "*", "credentials": { "database": "mychinook.db" } diff --git a/media-store/server.js b/media-store/server.js index 09d130e2..ac26c75f 100644 --- a/media-store/server.js +++ b/media-store/server.js @@ -1,4 +1,5 @@ const cds = require("@sap/cds"); +const { importData } = require("./util/importData"); // handle bootstrapping events... cds.on("bootstrap", (app) => { // dev only @@ -24,7 +25,8 @@ cds.on("bootstrap", (app) => { }); // add your own middleware before any by cds are added }); -cds.on("served", () => { +cds.on("served", async ({ db }) => { + await importData(db); // add more middleware after all CDS servies }); // delegate to default server.js: diff --git a/media-store/srv/browse-invoices-service.js b/media-store/srv/browse-invoices-service.js index a65832de..5f825c16 100644 --- a/media-store/srv/browse-invoices-service.js +++ b/media-store/srv/browse-invoices-service.js @@ -1,10 +1,17 @@ const cds = require("@sap/cds"); const moment = require("moment"); -const DATE_TIME_PATTERN = "YYYY-MM-DD HH:MM:SS"; -const LEVERAGE_DURATION = 1; // in hours +const LEVERAGE_DURATION = 1; // in hours. should be the same in the frontend const CANCEL_STATUS = -1; +// the same function there is in the frontend +const isLeverageTimeExpired = (invoiceDate) => { + const duration = moment.duration( + moment(moment().utc().format()).diff(invoiceDate) + ); + return duration.asHours() > LEVERAGE_DURATION; +}; + module.exports = async function () { const db = await cds.connect.to("db"); // connect to database service const { Invoices, InvoiceItems } = db.entities; @@ -27,7 +34,7 @@ module.exports = async function () { this.on("invoice", async (req) => { const { tracks } = req.data; const customerId = req.user.attr.ID; - const invoiceDate = moment(new Date(), DATE_TIME_PATTERN); + const invoiceDate = moment().utc().valueOf(); const total = tracks.reduce( (acc, { unitPrice }) => acc + Number(unitPrice), 0 @@ -44,7 +51,7 @@ module.exports = async function () { await transaction.run( INSERT.into(Invoices) .columns("ID", "customer_ID", "total", "invoiceDate") - .values(lastInvoiceId + 1, customerId, total, new Date(invoiceDate)) + .values(lastInvoiceId + 1, customerId, total, invoiceDate) ); await transaction.run( INSERT.into(InvoiceItems) @@ -77,18 +84,8 @@ module.exports = async function () { "Seems like you are not owning this invoice or it is not exists" ); } - console.log(currentInvoice); - console.log(currentInvoice.invoiceDate); - const x = moment().utc().format(DATE_TIME_PATTERN); - const y = moment(currentInvoice.invoiceDate).format(DATE_TIME_PATTERN); - const yy = moment(x).diff(y); - const durationInHours = moment.duration(yy); - console.log(x); - console.log(y); - console.log(yy); - console.log(durationInHours.asHours()); - if (durationInHours.asHours() > LEVERAGE_DURATION) { + if (isLeverageTimeExpired(currentInvoice.invoiceDate)) { req.reject(400, "Leverage time was expired"); } diff --git a/media-store/srv/browse-tracks-service.js b/media-store/srv/browse-tracks-service.js index 2339e5a6..7ed17075 100644 --- a/media-store/srv/browse-tracks-service.js +++ b/media-store/srv/browse-tracks-service.js @@ -28,16 +28,16 @@ module.exports = async function () { }); this.on("READ", "MarkedTracks", async (req) => { - const myTrackIds = ( - await db.run(cds.parse.cql(selectTracksByEmail(req.user.id))) - ).map(({ ID }) => ID); - - const result = await db.run(req.query); - return result.map((columns) => { - return { - ...columns, - alreadyOrdered: myTrackIds.includes(columns.ID), - }; + const myTrackIds = (await db.run(selectTracksByEmail(req.user.id))).map( + ({ ID }) => ID + ); + const result = []; + await db.foreach(req.query, (track) => { + result.push({ + ...track, + alreadyOrdered: myTrackIds.includes(track.ID), + }); }); + return result; }); }; diff --git a/media-store/srv/manage-store-service.cds b/media-store/srv/manage-store-service.cds new file mode 100644 index 00000000..b467b017 --- /dev/null +++ b/media-store/srv/manage-store-service.cds @@ -0,0 +1,9 @@ +using {sap.capire.media.store as my} from '../db/schema'; + +@(requires : 'employee') +service ManageStore { + entity Tracks as projection on my.Tracks; + action addTrack(name : String(25), albumTitle : String(255), genreName : String(255), composer : String(255)); + entity Albums as projection on my.Albums; + entity Genres as projection on my.Genres; +} diff --git a/media-store/srv/manage-store-service.js b/media-store/srv/manage-store-service.js new file mode 100644 index 00000000..303ed726 --- /dev/null +++ b/media-store/srv/manage-store-service.js @@ -0,0 +1,26 @@ +const cds = require("@sap/cds"); + +module.exports = async function () { + const db = await cds.connect.to("db"); // connect to database service + + const { Genres, Albums } = db.entities; + + this.before("*", (req) => { + console.log( + "[USER]:", + req.user.id, + " [LEVEL]: ", + req.user.attr.level, + "[ROLE]", + req.user.is("user") ? "user" : "other" + ); + }); + + this.on("addTrack", async (req) => { + const { albumTitle, genreName, name: trackName, composer } = req.data; + + const genre = await db.run(SELECT.one(Genres).where({ name: genreName })); + const album = await db.run(SELECT.one(Albums).where({ title: albumTitle })); + // todo impl + }); +}; diff --git a/media-store/srv/manage-tracks-service.cds b/media-store/srv/manage-tracks-service.cds deleted file mode 100644 index dfd08d52..00000000 --- a/media-store/srv/manage-tracks-service.cds +++ /dev/null @@ -1,13 +0,0 @@ -using {sap.capire.media.store as my} from '../db/schema'; - -@(requires : 'authenticated-user') -service ManageTracks { - @(restrict : [{ - grant : [ - 'READ', - 'WRITE' - ], - to : 'employee' - }, ]) - entity Genres as projection on my.Genres; -} diff --git a/media-store/util/importData.js b/media-store/util/importData.js index e9a7214a..62159a45 100644 --- a/media-store/util/importData.js +++ b/media-store/util/importData.js @@ -3,20 +3,9 @@ const cds = require("@sap/cds"); const FIRST_INDEX = 0; const ZERO_VALUE = 0; const SECOND_INDEX = 1; -const SKIP_CLI_ARGS_NUMBER = 2; -const THIRD_INDEX = 2; const ID_IF_NOT_FOUND = "ID"; -const args = process.argv.slice(SKIP_CLI_ARGS_NUMBER); -const SRC_STORAGE_NAME = args[FIRST_INDEX]; -const TARGET_STORAGE_NAME = args[SECOND_INDEX]; -const TARGET_SCHEMA_PATH = args[THIRD_INDEX]; - -function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -} +const SRC_STORAGE_NAME = "sqlite:chinook.db"; const camelCaseToSnake = (str) => str.replace( @@ -65,12 +54,6 @@ const constructInsertQuery = (targetEntityName) => { INSERT.into(targetEntityName).columns(columns).rows(row); }; -const logProcessArgs = () => { - console.log( - `[LOG]: Import data from ${SRC_STORAGE_NAME} to ${TARGET_STORAGE_NAME}, schema path: ${TARGET_SCHEMA_PATH}` - ); -}; - /** * The MAIN ISSUE such import is that it depends on: * - snake case table names @@ -78,38 +61,43 @@ const logProcessArgs = () => { * of chinook.db * There is 'Launch import' task in .vscode folder for debugging. */ -(async () => { - logProcessArgs(); +async function importData(targetDb) { try { const srcStorage = await cds.connect.to(SRC_STORAGE_NAME); - const targetStorage = await cds.connect.to(TARGET_STORAGE_NAME); - const targetCSNModel = await cds.load(TARGET_SCHEMA_PATH); + const targetCSNEntities = Object.values(targetDb.entities); + const targetCSNEntitiesNames = Object.keys(targetDb.entities); - const reflectedCSNModel = cds.reflect(targetCSNModel); - const targetCSNEntities = Object.values(reflectedCSNModel.entities); + const someEntry = await targetDb.run( + SELECT.one(targetCSNEntitiesNames[FIRST_INDEX]) + ); + if (!!someEntry) { + return; + } for (index in targetCSNEntities) { - const { name: targetEntityName, elements } = targetCSNEntities[index]; + const targetEntityName = targetCSNEntitiesNames[index]; console.log(`[LOG]: Processing ${targetEntityName}`); - const targetColumns = elementsToColumns(elements); // e.g. ['ID', ..., 'total', 'customer_ID'] - const insertQuery = constructInsertQuery(targetEntityName, targetColumns); - const srcEntityName = camelCaseToSnake(targetEntityName.split(".").pop()); + const { elements } = targetCSNEntities[index]; + const targetColumns = elementsToColumns(elements); // e.g. ['ID', ..., 'total', 'customer_ID'] + const srcEntityName = camelCaseToSnake(targetEntityName); + const insertQuery = constructInsertQuery(targetEntityName); let srcResultRows; try { - srcResultRows = await srcStorage.read(srcEntityName); // e.g. [ { AlbumId:1, ArtistId:1, Title:'some' }, ... ] + srcResultRows = await srcStorage.run(` + SELECT * from ${srcEntityName} + `); // e.g. [ { AlbumId:1, ArtistId:1, Title:'some' }, ... ] } catch (e) { console.log("[ERROR]: while trying to read source table", e.message); continue; } if (!srcResultRows || srcResultRows.length < ZERO_VALUE) { console.log( - `[LOG] Skipping ${targetEntityName}. + `[LOG] Skipping ${targetEntityName}. There is no data provided in ${SRC_STORAGE_NAME}, ${srcEntityName}` ); continue; } - const srcColumns = Object.keys(srcResultRows[FIRST_INDEX]); const columns = reorderTargetColumns(srcColumns, targetColumns); if (new Set(columns).size !== columns.length) { @@ -117,7 +105,6 @@ const logProcessArgs = () => { `Some ${targetEntityName} column name is mismatched in ${SRC_STORAGE_NAME} ${srcEntityName}` ); } - // for mock auth if (srcEntityName === "Employees" || srcEntityName === "Customers") { columns.push("password"); @@ -126,21 +113,17 @@ const logProcessArgs = () => { password: "some", })); } - if (srcEntityName === "Invoices") { - columns.push("status"); - srcResultRows = srcResultRows.map((row) => ({ - ...row, - status: 2, - })); - } - const transaction = await targetStorage.tx(); + const transaction = await targetDb.tx(); await transaction.run( srcResultRows.map((row) => insertQuery(Object.values(row), columns)) ); await transaction.commit(); } + console.log("[LOG]: ", "Import completed"); } catch (errors) { console.error(errors); } -})(); +} + +module.exports = { importData };