From dae8e96fe1d932b4900af85179a7e83e798a1a5b Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Nov 2020 12:41:11 +0100 Subject: [PATCH] Adding Vue.js apps for reviews service --- .vscode/launch.json | 9 +++ bookshop/app/vue/app.js | 4 +- bookshop/app/vue/index.html | 38 +++++----- fiori/.env | 2 + fiori/test/requests.http | 36 ++++------ orders/.env | 2 +- reviews/.env | 2 +- reviews/app/vue/app.js | 72 +++++++++++++++++++ reviews/app/vue/index.html | 62 ++++++++++++++++ .../db/data/sap.capire.reviews-Reviews.csv | 5 ++ reviews/index.cds | 1 + reviews/srv/reviews-service.cds | 9 ++- test/messaging.test.js | 1 + 13 files changed, 198 insertions(+), 45 deletions(-) create mode 100644 fiori/.env create mode 100644 reviews/app/vue/app.js create mode 100644 reviews/app/vue/index.html create mode 100644 reviews/db/data/sap.capire.reviews-Reviews.csv diff --git a/.vscode/launch.json b/.vscode/launch.json index d0f0e8eb..ad51dcef 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,15 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Attach by Process ID", + "processId": "${command:PickProcess}", + "request": "attach", + "skipFiles": [ + "/**" + ], + "type": "pwa-node" + }, { "name": "bookshop", "command": "cds watch bookshop", diff --git a/bookshop/app/vue/app.js b/bookshop/app/vue/app.js index e7aedcaa..78bd342b 100644 --- a/bookshop/app/vue/app.js +++ b/bookshop/app/vue/app.js @@ -9,7 +9,7 @@ const books = new Vue ({ data: { list: [], - book: { descr:'( click on a row to see details... )' }, + book: undefined, order: { amount:1, succeeded:'', failed:'' } }, @@ -31,7 +31,7 @@ const books = new Vue ({ }, async submitOrder () { - const {book,order} = books, amount = parseInt (order.amount) || 1 + const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict try { const res = await POST(`/submitOrder`, { amount, book: book.ID }) book.stock = res.data.stock diff --git a/bookshop/app/vue/index.html b/bookshop/app/vue/index.html index f4753234..56c79a07 100644 --- a/bookshop/app/vue/index.html +++ b/bookshop/app/vue/index.html @@ -7,22 +7,21 @@
-

Capire Books

+

{{ document.title }}

- +
@@ -34,29 +33,30 @@ - +
Book Author {{ book.title }} {{ book.author }} {{ book.genre.name }}{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} + {{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} + {{ book.currency.symbol }} {{ book.price }}
-
+
-
- -
-
- + +
+

{{ book.title }}

+

{{ book.descr }}

+
+
+ ( click on a row to see details... )
- -

{{ book.title }}

-

{{ book.descr }}

diff --git a/fiori/.env b/fiori/.env new file mode 100644 index 00000000..36644fa6 --- /dev/null +++ b/fiori/.env @@ -0,0 +1,2 @@ +# cds.requires.messaging.kind = file-based-messaging +PORT = 4004 \ No newline at end of file diff --git a/fiori/test/requests.http b/fiori/test/requests.http index a9c18a97..d759aedf 100644 --- a/fiori/test/requests.http +++ b/fiori/test/requests.http @@ -1,54 +1,48 @@ -@me = {{$processEnv USER}}: @bookshop = http://localhost:4004 @reviews-service = {{bookshop}}/reviews - -# Uncomment this when running separate reviews service -# @reviews-service = http://localhost:5005/reviews +# Uncomment this when running a separate reviews service +@reviews-service = http://localhost:4005/reviews ################################################# # -# To ReviewsService +# Reviews Service # -# move the right down: -### Get all reviews GET {{reviews-service}}/Reviews -### Add a new review (with random rating) -POST {{reviews-service}}/Reviews -Content-Type: application/json;IEEE754Compatible=true -Authorization: Basic {{me}} +### -{"subject":"201", "title":"boo" } +POST {{reviews-service}}/Reviews +Authorization: Basic {{$processEnv USER}}: +Content-Type: application/json + +{"subject":"201", "title":"boo", "rating":3 } ################################################# # -# Bookshop Requests involving reviews -# (both in-process as well as separate one) +# Bookshop Services # -### Request to CatalogService > delegated to ReviewsService -GET {{bookshop}}/browse/Books(201)/reviews? -&$select=rating,date,reviewer,title - -### Alternative OData URL GET {{bookshop}}/browse/Books/201/reviews? &$select=rating,date,title &$top=3 ### + GET {{bookshop}}/browse/Books(201)? &$select=ID,title,rating &$expand=reviews -# Note: the $expand only works in case of ReviewsService in same process -### +################################################# +# +# Bookshop Services +# GET {{bookshop}}/orders/Orders diff --git a/orders/.env b/orders/.env index b9da5c42..616dd8d0 100644 --- a/orders/.env +++ b/orders/.env @@ -1,2 +1,2 @@ cds.requires.messaging.kind = file-based-messaging -PORT = 4005 \ No newline at end of file +PORT = 4006 \ No newline at end of file diff --git a/reviews/.env b/reviews/.env index 8184d48d..b9da5c42 100644 --- a/reviews/.env +++ b/reviews/.env @@ -1,2 +1,2 @@ cds.requires.messaging.kind = file-based-messaging -PORT = 5005 \ No newline at end of file +PORT = 4005 \ No newline at end of file diff --git a/reviews/app/vue/app.js b/reviews/app/vue/app.js new file mode 100644 index 00000000..fb115d68 --- /dev/null +++ b/reviews/app/vue/app.js @@ -0,0 +1,72 @@ +/* global Vue axios */ //> from vue.html +const $ = sel => document.querySelector(sel) +const GET = (url) => axios.get('/reviews'+url) +const PUT = (cmd,data) => axios.patch('/reviews'+cmd,data) +const POST = (cmd,data) => axios.post('/reviews'+cmd,data) + +const reviews = new Vue ({ + + el:'#app', + + data: { + list: [], + review: undefined, + message: {}, + Ratings: Object.entries({ + 5 : '★★★★★', + 4 : '★★★★', + 3 : '★★★', + 2 : '★★', + 1 : '★', + }).reverse() + }, + + methods: { + + search: ({target:{value:v}}) => reviews.fetch(v && '&$search='+v), + + async fetch (etc='') { + const {data} = await GET(`/Reviews?${etc}`) + reviews.list = data.value + }, + + async inspect (eve) { + const review = reviews.review = reviews.list [eve.currentTarget.rowIndex-1] + const res = await GET(`/Reviews/${review.ID}/text/$value`) + review.text = res.data + reviews.message = {} + }, + + async newReview () { + reviews.review = {} + reviews.message = {} + setTimeout (()=> $('form > input').focus(), 111) + }, + + async submitReview () { + const review = reviews.review; review.rating = parseInt (review.rating) // REVISIT: Okra should be less strict + try { + if (!review.ID) { + const res = await POST(`/Reviews`,review) + reviews.ID = res.data.ID + } else { + console.trace() + await PUT(`/Reviews/${review.ID}`,review) + } + reviews.message = { succeeded: 'Your review was submitted successfully. Thanks.' } + } catch (e) { + reviews.message = { failed: e.response.data.error.message } + } + } + + }, + + filters: { + stars: (r) => ('★'.repeat(Math.round(r))+'☆☆☆☆☆').slice(0,5), + datetime: (d) => d && new Date(d).toLocaleString(), + }, + +}) + +// initially fill list of my reviews +reviews.fetch() diff --git a/reviews/app/vue/index.html b/reviews/app/vue/index.html new file mode 100644 index 00000000..a361387f --- /dev/null +++ b/reviews/app/vue/index.html @@ -0,0 +1,62 @@ + + + + + Capire Reviews + + + + + + + +
+ +

{{ document.title }}

+ + + + + + + + + + + + + + + + +
Subject Rating Title Date
{{ review.subject }}{{ review.rating | stars }}{{ review.title }}{{ review.date | datetime }}
+ + + +
+ + + + + + {{ message.succeeded }} + {{ message.failed }} +
+
+ ( click on a row to see details... ) +
+ + +
+ + + + diff --git a/reviews/db/data/sap.capire.reviews-Reviews.csv b/reviews/db/data/sap.capire.reviews-Reviews.csv new file mode 100644 index 00000000..5e7f3d3f --- /dev/null +++ b/reviews/db/data/sap.capire.reviews-Reviews.csv @@ -0,0 +1,5 @@ +subject;rating;title;text +201;5;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. +201;4;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum. +207;2;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius. +251;3;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse. \ No newline at end of file diff --git a/reviews/index.cds b/reviews/index.cds index c126bf5e..ac2c4e7a 100644 --- a/reviews/index.cds +++ b/reviews/index.cds @@ -1 +1,2 @@ using from './srv/reviews-service'; +namespace sap.capire.reviews; diff --git a/reviews/srv/reviews-service.cds b/reviews/srv/reviews-service.cds index 17a46578..eb26d9ae 100644 --- a/reviews/srv/reviews-service.cds +++ b/reviews/srv/reviews-service.cds @@ -27,7 +27,14 @@ service ReviewsService { annotate ReviewsService.Reviews with @restrict:[ { grant:'READ', to:'any' }, // everybody can read reviews { grant:'CREATE', to:'authenticated-user' }, // users must login to add reviews - { grant:'UPDATE', to:'authenticated-user', where:'reviewer=$user' }, + ///////////////////////////////////////////////// + // + // Temporarily disabling this due to glitch in CAP Node.js runtime: + // { grant:'UPDATE', to:'authenticated-user', where:'reviewer=$user' }, + // -> reenable it when the issue is fixed + { grant:'UPDATE', to:'authenticated-user' }, + // + //////////////////////////////////////////////////// { grant:'DELETE', to:'admin' }, ]; diff --git a/test/messaging.test.js b/test/messaging.test.js index 78866a37..9c2e541d 100644 --- a/test/messaging.test.js +++ b/test/messaging.test.js @@ -10,6 +10,7 @@ describe('Messaging', ()=>{ it ('should bootstrap sqlite in-memory db', async()=>{ const db = await cds.deploy (_model) .to ('sqlite::memory:') + await db.delete('Reviews') expect (db.model) .not.undefined })