add tracks main page. add get my tracks action

This commit is contained in:
Dzmitry_Tamashevich@epam.com
2020-10-08 11:17:06 +03:00
committed by Daniel Hutzel
parent 0f6444589f
commit bdbd9d425b
28 changed files with 381 additions and 8 deletions

View File

@@ -6,7 +6,7 @@
"configurations": [ "configurations": [
{ {
"name": "Launch import", "name": "Launch import",
"command": "npm run import:data", "command": "npm run import",
"request": "launch", "request": "launch",
"skipFiles": ["<node_internals>/**"], "skipFiles": ["<node_internals>/**"],
"type": "node-terminal" "type": "node-terminal"

View File

@@ -0,0 +1,25 @@
{
"files": {
"main.css": "/static/css/main.71730ad3.chunk.css",
"main.js": "/static/js/main.3299c2ff.chunk.js",
"main.js.map": "/static/js/main.3299c2ff.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.83c3e0c4.js",
"runtime-main.js.map": "/static/js/runtime-main.83c3e0c4.js.map",
"static/css/2.220ccb42.chunk.css": "/static/css/2.220ccb42.chunk.css",
"static/js/2.a1ecb23f.chunk.js": "/static/js/2.a1ecb23f.chunk.js",
"static/js/2.a1ecb23f.chunk.js.map": "/static/js/2.a1ecb23f.chunk.js.map",
"index.html": "/index.html",
"precache-manifest.df8261d0c015793aed10ac443d67446f.js": "/precache-manifest.df8261d0c015793aed10ac443d67446f.js",
"service-worker.js": "/service-worker.js",
"static/css/2.220ccb42.chunk.css.map": "/static/css/2.220ccb42.chunk.css.map",
"static/css/main.71730ad3.chunk.css.map": "/static/css/main.71730ad3.chunk.css.map",
"static/js/2.a1ecb23f.chunk.js.LICENSE.txt": "/static/js/2.a1ecb23f.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.83c3e0c4.js",
"static/css/2.220ccb42.chunk.css",
"static/js/2.a1ecb23f.chunk.js",
"static/css/main.71730ad3.chunk.css",
"static/js/main.3299c2ff.chunk.js"
]
}

BIN
media-store/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><link href="/static/css/2.220ccb42.chunk.css" rel="stylesheet"><link href="/static/css/main.71730ad3.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,p,l=r[0],a=r[1],f=r[2],c=0,s=[];c<l.length;c++)p=l[c],Object.prototype.hasOwnProperty.call(o,p)&&o[p]&&s.push(o[p][0]),o[p]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(i&&i(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=p(p.s=t[0]))}return e}var n={},o={1:0},u=[];function p(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,p),t.l=!0,t.exports}p.m=e,p.c=n,p.d=function(e,r,t){p.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},p.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},p.t=function(e,r){if(1&r&&(e=p(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(p.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)p.d(t,n,function(r){return e[r]}.bind(null,n));return t},p.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return p.d(r,"a",r),r},p.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},p.p="/";var l=this["webpackJsonpmy-app"]=this["webpackJsonpmy-app"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var i=a;t()}([])</script><script src="/static/js/2.a1ecb23f.chunk.js"></script><script src="/static/js/main.3299c2ff.chunk.js"></script></body></html>

BIN
media-store/app/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
media-store/app/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,30 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "c011870715d3ab45b0a485c5cfd7d0e6",
"url": "/index.html"
},
{
"revision": "07a728fc1277aa66931c",
"url": "/static/css/2.220ccb42.chunk.css"
},
{
"revision": "c9ae56ed88ee994db243",
"url": "/static/css/main.71730ad3.chunk.css"
},
{
"revision": "07a728fc1277aa66931c",
"url": "/static/js/2.a1ecb23f.chunk.js"
},
{
"revision": "3453b8997016469371284a28c0e873e2",
"url": "/static/js/2.a1ecb23f.chunk.js.LICENSE.txt"
},
{
"revision": "c9ae56ed88ee994db243",
"url": "/static/js/main.3299c2ff.chunk.js"
},
{
"revision": "73635f645c271e35ff15",
"url": "/static/js/runtime-main.83c3e0c4.js"
}
]);

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -0,0 +1,39 @@
/**
* Welcome to your Workbox-powered service worker!
*
* You'll need to register this file in your web app and you should
* disable HTTP caching for this file too.
* See https://goo.gl/nhQhGp
*
* The rest of the code is auto-generated. Please don't update this file
* directly; instead, make changes to your Workbox build configuration
* and re-run your build process.
* See https://goo.gl/2aRDsh
*/
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"/precache-manifest.df8261d0c015793aed10ac443d67446f.js"
);
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
workbox.core.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
html{overflow:hidden}#root{height:100%}section.ant-layout{height:100vh;overflow:auto}.site-layout .site-layout-background{background:#fff}.App{text-align:center}.App-logo{height:40vmin;pointer-events:none}@media (prefers-reduced-motion:no-preference){.App-logo{-webkit-animation:App-logo-spin 20s linear infinite;animation:App-logo-spin 20s linear infinite}}.App-header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.App-link{color:#61dafb}@-webkit-keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}div.ant-select-dropdown.ant-select-dropdown-placement-bottomLeft,div.ant-select.ant-select-multiple.ant-select-show-search>div{border-radius:6px}
/*# sourceMappingURL=main.71730ad3.chunk.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.css","App.css","TracksPage.css"],"names":[],"mappings":"AAEA,KACE,eACF,CACA,MACE,WACF,CACA,mBACE,YAAa,CACb,aACF,CAIA,qCACE,eACF,CCjBA,KACE,iBACF,CAEA,UACE,aAAc,CACd,mBACF,CAEA,8CACE,UACE,mDAA4C,CAA5C,2CACF,CACF,CAEA,YACE,wBAAyB,CACzB,gBAAiB,CACjB,YAAa,CACb,qBAAsB,CACtB,kBAAmB,CACnB,sBAAuB,CACvB,4BAA6B,CAC7B,UACF,CAEA,UACE,aACF,CAEA,iCACE,GACE,sBACF,CACA,GACE,uBACF,CACF,CAPA,yBACE,GACE,sBACF,CACA,GACE,uBACF,CACF,CCrCA,+HAEE,iBACF","file":"main.71730ad3.chunk.css","sourcesContent":["@import \"~antd/dist/antd.css\";\n\nhtml {\n overflow: hidden;\n}\n#root {\n height: 100%;\n}\nsection.ant-layout {\n height: 100vh;\n overflow: auto;\n}\n\n/* Layout\n*/\n.site-layout .site-layout-background {\n background: #fff;\n}\n",".App {\n text-align: center;\n}\n\n.App-logo {\n height: 40vmin;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .App-logo {\n animation: App-logo-spin infinite 20s linear;\n }\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white;\n}\n\n.App-link {\n color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n","div.ant-select.ant-select-multiple.ant-select-show-search > div,\r\ndiv.ant-select-dropdown.ant-select-dropdown-placement-bottomLeft {\r\n border-radius: 6px;\r\n}\r\n"]}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,47 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/** @license React v0.19.1
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(e){function r(r){for(var n,p,l=r[0],a=r[1],f=r[2],c=0,s=[];c<l.length;c++)p=l[c],Object.prototype.hasOwnProperty.call(o,p)&&o[p]&&s.push(o[p][0]),o[p]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(i&&i(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=p(p.s=t[0]))}return e}var n={},o={1:0},u=[];function p(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,p),t.l=!0,t.exports}p.m=e,p.c=n,p.d=function(e,r,t){p.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},p.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},p.t=function(e,r){if(1&r&&(e=p(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(p.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)p.d(t,n,function(r){return e[r]}.bind(null,n));return t},p.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return p.d(r,"a",r),r},p.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},p.p="/";var l=this["webpackJsonpmy-app"]=this["webpackJsonpmy-app"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var i=a;t()}([]);
//# sourceMappingURL=runtime-main.83c3e0c4.js.map

File diff suppressed because one or more lines are too long

View File

@@ -20,7 +20,12 @@ aspect Persone {
} }
entity MediaTypes : Named {} entity MediaTypes : Named {}
entity Genres : Named {}
entity Genres : Named {
tracks : Association to many Tracks
on tracks.genre = $self;
}
entity Playlists : Named {} entity Playlists : Named {}
entity PlaylistTrack { entity PlaylistTrack {
@@ -84,4 +89,6 @@ entity Tracks {
milliseconds : Integer; milliseconds : Integer;
bytes : Integer; bytes : Integer;
unitPrice : Decimal(10, 2); unitPrice : Decimal(10, 2);
invoices : Association to many InvoiceItems
on invoices.track.ID = $self.ID
} }

View File

@@ -7,7 +7,8 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sap/cds": "^4", "@sap/cds": "^4",
"express": "^4" "express": "^4",
"passport": "^0.4.1"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "^5" "sqlite3": "^5"
@@ -16,6 +17,7 @@
"start": "npx cds run", "start": "npx cds run",
"deploy": "cds deploy --to sqlite:mychinook.db", "deploy": "cds deploy --to sqlite:mychinook.db",
"import": "node ./util/importData.js sqlite:chinook.db sqlite:mychinook.db ./db/schema.cds", "import": "node ./util/importData.js sqlite:chinook.db sqlite:mychinook.db ./db/schema.cds",
"deploy:import": "npm run deploy && npm run import",
"test": "mocha test/media-service.test.js --verbose --timeout 10000" "test": "mocha test/media-service.test.js --verbose --timeout 10000"
}, },
"cds": { "cds": {
@@ -25,7 +27,55 @@
"credentials": { "credentials": {
"database": "mychinook.db" "database": "mychinook.db"
} }
},
"auth": {
"strategy": "mock",
"users": {
"admin5": {
"roles": [
"admin",
"content-creator"
],
"userAttributes": {
"level": 5
}
},
"admin2": {
"roles": [
"admin"
],
"userAttributes": {
"level": 2
}
},
"content-creator1": {
"roles": [
"content-creator"
],
"userAttributes": {
"level": 2
}
},
"user1": {
"roles": [
"user"
],
"userAttributes": {
"level": 1
}
},
"user0": {
"roles": [
"user"
],
"userAttributes": {
"level": 1,
"email": "hholy@gmail.com"
}
},
"*": true
}
} }
} }
} }
} }

31
media-store/server.js Normal file
View File

@@ -0,0 +1,31 @@
const cds = require("@sap/cds");
// handle bootstrapping events...
cds.on("bootstrap", (app) => {
// dev only
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Methods",
"GET, PUT, POST, DELETE, OPTIONS"
);
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
);
//intercepts OPTIONS method
if ("OPTIONS" === req.method) {
//respond with 200
res.sendStatus(200);
} else {
//move on
next();
}
});
// add your own middleware before any by cds are added
});
cds.on("served", () => {
// add more middleware after all CDS servies
});
// delegate to default server.js:
module.exports = cds.server;

View File

@@ -0,0 +1,24 @@
using {sap.capire.media.store as my} from '../db/schema';
@cds.query.limit : {
default : 12,
max : 100
}
@(requires : ['identified-user'])
service BrowseTracks {
@readonly
entity Tracks as projection on my.Tracks;
@(requires : ['authenticated-user'])
action getInvoicedTracks() returns array of {
ID : Tracks.ID;
trackName : Tracks.name;
genreName : my.Genres.name;
composer : Tracks.composer;
unitPrice : Tracks.unitPrice;
albumTitle : my.Albums.title;
};
@readonly
entity Genres as projection on my.Genres;
}

View File

@@ -0,0 +1,51 @@
const cds = require("@sap/cds");
const selectInvoicedTracksByEmail = (email) => `
select
tracks.ID,
tracks.name trackName,
tracks.composer,
tracks.unitPrice,
genre.name genreName,
album.title albumTitle
from sap_capire_media_store_Tracks tracks
join sap_capire_media_store_InvoiceItems invoiceItem
on invoiceItem.track_ID = tracks.ID
join sap_capire_media_store_Invoices invoice
on invoice.ID = invoiceItem.invoice_ID
join sap_capire_media_store_Customers customer
on customer.ID = invoice.customer_ID
join sap_capire_media_store_Genres genre
on genre.ID = tracks.genre_ID
join sap_capire_media_store_Albums album
on album.ID = tracks.album_ID
where
customer.email = '${email}'
`;
module.exports = async function () {
const db = await cds.connect.to("db"); // connect to database service
// this.before("*", (req) => {
// req.user = new Buyer();
// });
this.before("*", (req) => {
console.log(
"[USER]:",
req.user.id,
" [LEVEL]: ",
req.user.attr.level,
"[ROLE]",
req.user.is("user") ? "user" : "other"
);
});
this.on("getInvoicedTracks", async (req) => {
const user = req.user;
user.is("user") || req.reject(403);
const query = cds.parse.cql(selectInvoicedTracksByEmail(user.attr.email));
return await db.run(query);
});
};

View File

@@ -1,8 +1,12 @@
using { sap.capire.media.store as my } from '../db/schema'; using {sap.capire.media.store as my} from '../db/schema';
@cds.query.limit : {
default : 20,
max : 100
}
service MediaService { service MediaService {
entity Employees as projection on my.Employees; entity Employees as projection on my.Employees;
entity Customers as projection on my.Customers; entity Customers as projection on my.Customers;
entity Albums as projection on my.Albums; entity Albums as projection on my.Albums;
entity Artists as projection on my.Artists; entity Artists as projection on my.Artists;
entity Genres as projection on my.Genres; entity Genres as projection on my.Genres;
@@ -11,5 +15,10 @@ service MediaService {
entity MediaTypes as projection on my.MediaTypes; entity MediaTypes as projection on my.MediaTypes;
entity PlaylistTrack as projection on my.PlaylistTrack; entity PlaylistTrack as projection on my.PlaylistTrack;
entity Playlists as projection on my.Playlists; entity Playlists as projection on my.Playlists;
@(restrict : [{
grant : '*',
where : '$user.level > 1'
}])
entity Tracks as projection on my.Tracks; entity Tracks as projection on my.Tracks;
} }

View File

@@ -1,4 +1,5 @@
@media-store-service = http://localhost:4004/media @media-store-service = http://localhost:4004/media
@browse-tracks-service = http://localhost:4004/browse-tracks
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
# Get service # Get service
@@ -64,4 +65,10 @@ GET {{media-store-service}}/Playlists
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
# Get Playlists # Get Playlists
GET {{media-store-service}}/Tracks GET {{media-store-service}}/Tracks
# ?$top=10&$expand=genre,mediaType,album # ?$top=10&$expand=genre,mediaType,album
### ------------------------------------------------------------------------
# Get My tracks
POST {{browse-tracks-service}}/getInvoicedTracks
Content-Type: application/json