Compare commits
2 Commits
CAA265-nod
...
CAA265-nod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
162bdb5b90 | ||
|
|
fb8f6acac7 |
@@ -1,3 +1,3 @@
|
|||||||
# Final state of exercise 1 for CAA265 - Rapid Service Development with SAP Cloud Application Programming Model
|
# Final state after exercise 2 for CAA265 - Rapid Service Development with SAP Cloud Application Programming Model
|
||||||
|
|
||||||
**DO NOT MERGE IN MASTER**
|
**DO NOT MERGE IN MASTER**
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"cds_version": "^3.17.4"
|
|
||||||
}
|
|
||||||
6
packages/reviews-service/.cdsrc.json
Normal file
6
packages/reviews-service/.cdsrc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"target": ".",
|
||||||
|
"tasks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
15
packages/reviews-service/.gitignore
vendored
Normal file
15
packages/reviews-service/.gitignore
vendored
Normal file
@@ -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
|
||||||
16
packages/reviews-service/.vscode/launch.json
vendored
Normal file
16
packages/reviews-service/.vscode/launch.json
vendored
Normal file
@@ -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": [ "<node_internals>/**" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
30
packages/reviews-service/db/schema.cds
Normal file
30
packages/reviews-service/db/schema.cds
Normal file
@@ -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;
|
||||||
|
}
|
||||||
1
packages/reviews-service/index.cds
Normal file
1
packages/reviews-service/index.cds
Normal file
@@ -0,0 +1 @@
|
|||||||
|
using from './srv/reviews-service';
|
||||||
19
packages/reviews-service/package.json
Normal file
19
packages/reviews-service/package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "reviews-service",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Generated by cds init",
|
||||||
|
"repository": "<Add your repository here>",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
26
packages/reviews-service/srv/reviews-service.cds
Normal file
26
packages/reviews-service/srv/reviews-service.cds
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
packages/reviews-service/srv/reviews-service.js
Normal file
40
packages/reviews-service/srv/reviews-service.js
Normal file
@@ -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}.`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user