Compare commits

..

2 Commits

Author SHA1 Message Date
johannes-vogel
162bdb5b90 Update README.md 2019-09-23 14:12:30 +02:00
d045778
fb8f6acac7 final state of exercise 2 2019-09-23 13:25:44 +02:00
10 changed files with 154 additions and 4 deletions

View File

@@ -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**

View File

@@ -1,3 +0,0 @@
{
"cds_version": "^3.17.4"
}

View File

@@ -0,0 +1,6 @@
{
"build": {
"target": ".",
"tasks": []
}
}

15
packages/reviews-service/.gitignore vendored Normal file
View 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

View 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>/**" ]
}
]
}

View 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;
}

View File

@@ -0,0 +1 @@
using from './srv/reviews-service';

View 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"
}
}

View 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;
}
}

View 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}.`)
})
})
})