From fb8f6acac735268680b4d7a2affbe52ad61e5a8c Mon Sep 17 00:00:00 2001 From: d045778 Date: Mon, 23 Sep 2019 13:25:44 +0200 Subject: [PATCH] final state of exercise 2 --- packages/reviews-service/.cdsrc.json | 6 +++ packages/reviews-service/.gitignore | 15 +++++++ packages/reviews-service/.vscode/launch.json | 16 ++++++++ packages/reviews-service/db/schema.cds | 30 ++++++++++++++ packages/reviews-service/index.cds | 1 + packages/reviews-service/package.json | 19 +++++++++ .../reviews-service/srv/reviews-service.cds | 26 ++++++++++++ .../reviews-service/srv/reviews-service.js | 40 +++++++++++++++++++ 8 files changed, 153 insertions(+) create mode 100644 packages/reviews-service/.cdsrc.json create mode 100644 packages/reviews-service/.gitignore create mode 100644 packages/reviews-service/.vscode/launch.json create mode 100644 packages/reviews-service/db/schema.cds create mode 100644 packages/reviews-service/index.cds create mode 100644 packages/reviews-service/package.json create mode 100644 packages/reviews-service/srv/reviews-service.cds create mode 100644 packages/reviews-service/srv/reviews-service.js diff --git a/packages/reviews-service/.cdsrc.json b/packages/reviews-service/.cdsrc.json new file mode 100644 index 00000000..83eb56d4 --- /dev/null +++ b/packages/reviews-service/.cdsrc.json @@ -0,0 +1,6 @@ +{ + "build": { + "target": ".", + "tasks": [] + } +} \ No newline at end of file diff --git a/packages/reviews-service/.gitignore b/packages/reviews-service/.gitignore new file mode 100644 index 00000000..502ead7c --- /dev/null +++ b/packages/reviews-service/.gitignore @@ -0,0 +1,15 @@ +.che/ +.gen/ +gen/ +mta_archives/ +node_modules/ +target/ + +.cds_gen.log +connection.properties +*.db +.DS_Store +*.orig +_out +default-*.json +package-lock.json diff --git a/packages/reviews-service/.vscode/launch.json b/packages/reviews-service/.vscode/launch.json new file mode 100644 index 00000000..cf7c78f6 --- /dev/null +++ b/packages/reviews-service/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "cds run", + "request": "launch", + "type": "node", "runtimeExecutable": "npx","runtimeArgs": [ "-n" ], + "args": [ "--","cds","run","--in-memory?" ], // the leading "--" arg ensures it works with as well as without debugging + "console": "integratedTerminal", + "skipFiles": [ "/**" ] + } + ] +} diff --git a/packages/reviews-service/db/schema.cds b/packages/reviews-service/db/schema.cds new file mode 100644 index 00000000..b65eb986 --- /dev/null +++ b/packages/reviews-service/db/schema.cds @@ -0,0 +1,30 @@ +namespace sap.capire.reviews; +using { User } from '@sap/cds/common'; + +// Reviewed subjects can be any entity that is uniquely identified +type ReviewedSubject : String(111); + +entity Reviews { + key ID : String(36); + subject : ReviewedSubject; + reviewer : User; + rating : Rating; + title : String(111); + text : String(1111); + date : DateTime; + likes : Composition of many Likes on likes.review = $self; + liked : Integer default 0; // counter for likes as helpful review (count of all _likes belonging to this review) +} + +type Rating : Integer enum { + Best = 5; + Good = 4; + Avg = 3; + Poor = 2; + Worst = 1; +} + +entity Likes { + key review : Association to Reviews; + key user : User; +} \ No newline at end of file diff --git a/packages/reviews-service/index.cds b/packages/reviews-service/index.cds new file mode 100644 index 00000000..5468bbd9 --- /dev/null +++ b/packages/reviews-service/index.cds @@ -0,0 +1 @@ +using from './srv/reviews-service'; \ No newline at end of file diff --git a/packages/reviews-service/package.json b/packages/reviews-service/package.json new file mode 100644 index 00000000..b6dd7821 --- /dev/null +++ b/packages/reviews-service/package.json @@ -0,0 +1,19 @@ +{ + "name": "reviews-service", + "version": "1.0.0", + "description": "Generated by cds init", + "repository": "", + "license": "ISC", + "dependencies": { + "@sap/cds": "^3.17.4", + "express": "^4.17.1" + }, + "engines": { + "node": "^8.9" + }, + "scripts": { + "build": "cds build/all --clean", + "deploy": "cds deploy", + "start": "cds run" + } +} diff --git a/packages/reviews-service/srv/reviews-service.cds b/packages/reviews-service/srv/reviews-service.cds new file mode 100644 index 00000000..794ab514 --- /dev/null +++ b/packages/reviews-service/srv/reviews-service.cds @@ -0,0 +1,26 @@ +namespace sap.capire.reviews; +using { sap.capire.reviews as my } from '../db/schema'; + +service ReviewsService { + + event reviewed : { subject:String; rating: Decimal(2,1) }; + + // API + entity Reviews as projection on my.Reviews excluding { likes } + action like (review:Reviews.ID); + action unlike (review:Reviews.ID); + + // Input validation + annotate Reviews with { + subject @mandatory; + title @mandatory; + rating @mandatory @assert.enum; + } + + // Auto-fill reviewers and review dates + annotate Reviews with { + reviewer @cds.on.insert:$user; + date @cds.on.insert:$now; + date @cds.on.update:$now; + } +} \ No newline at end of file diff --git a/packages/reviews-service/srv/reviews-service.js b/packages/reviews-service/srv/reviews-service.js new file mode 100644 index 00000000..c83e3147 --- /dev/null +++ b/packages/reviews-service/srv/reviews-service.js @@ -0,0 +1,40 @@ +const cds = require('@sap/cds') +module.exports = cds.service.impl((srv) => { + + // Get the CSN definition for Reviews from the db schema for sub-sequent queries + // ( Note: we explicitly specify the namespace to support embedded reuse ) + const { Reviews, Likes } = cds.entities('sap.capire.reviews') + + // Increment counter for reviews considered helpful + srv.on ('like', (req) => { + if (!req.user) return req.reject(400, 'You must be identified to like a review') + const {review} = req.data, {user} = req + const tx = cds.transaction(req) + return tx.run ([ + INSERT.into (Likes) .entries ({review_ID: review, user: user.id}), + UPDATE (Reviews) .set({liked: {'+=': 1}}) .where({ID:review}) + ]).catch(() => req.reject(400, 'You already liked that review')) + }) + + // Delete a former like by the same user + srv.on('unlike', async (req) => { + if (!req.user) return req.reject(400, 'You must be identified to remove a former like of yours') + const { review } = req.data, { user } = req + const tx = cds.transaction(req) + const affectedRows = await tx.run(DELETE.from(Likes).where({ review_ID: review, user: user.id })) + if (affectedRows === 1) return tx.run(UPDATE(Reviews).set({ liked: { '-=': 1 } }).where({ ID: review })) + }) + + // Emit an event to inform subscribers about new avg ratings for reviewed subjects + // ( Note: req.on.succeeded ensures we only do that if there's no error ) + srv.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => { + const { subject } = req.data + const { rating } = await cds.transaction(req).run( + SELECT.one(['avg(rating) as rating']).from(Reviews).where({ subject }) + ) + req.on('succeeded', () => { + srv.emit('reviewed', { subject, rating }) + console.log(`Reviewed event was emitted for book "${subject}" with rating ${rating}.`) + }) + }) +}) \ No newline at end of file