diff --git a/media-store/app-src/package.json b/media-store/app-src/package.json index a3bd4554..457556aa 100644 --- a/media-store/app-src/package.json +++ b/media-store/app-src/package.json @@ -15,6 +15,7 @@ "@testing-library/user-event": "^7.1.2", "@umijs/hooks": "^1.9.3", "antd": "^4.8.2", + "@ant-design/icons": "4.3.0", "axios": "^0.20.0", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^6.3.2", diff --git a/media-store/app-src/src/api/calls.js b/media-store/app-src/src/api/calls.js index f22c18bb..10b6abb5 100644 --- a/media-store/app-src/src/api/calls.js +++ b/media-store/app-src/src/api/calls.js @@ -43,13 +43,10 @@ const invoice = (tracks) => { return axiosInstance.post( `${INVOICES_SERVICE}/invoice`, { - tracks: tracks.map(({ unitPrice, ID }) => ({ - unitPrice: `${unitPrice}`, - ID, - })), + tracks, }, { - headers: { 'content-type': 'application/json;IEEE754Compatible=true' }, + headers: { 'content-type': 'application/json' }, } ); }; diff --git a/media-store/app-src/src/components/Header.jsx b/media-store/app-src/src/components/Header.jsx index 7dd719d9..a965ff8c 100644 --- a/media-store/app-src/src/components/Header.jsx +++ b/media-store/app-src/src/components/Header.jsx @@ -36,8 +36,8 @@ const Header = () => { history.go(RELOAD_LOCATION_NUMBER); }; const localeElements = AVAILABLE_LOCALES.filter((localeName) => localeName !== locale).map( - (curLocale, index) => ( - onChangeLocale(curLocale)}> + (curLocale) => ( + onChangeLocale(curLocale)}> {curLocale} ) @@ -83,9 +83,6 @@ const Header = () => { Manages )} - - {loading && } />} - { mode="horizontal" selectedKeys={currentKey} > + + {loading && } />} + {haveInvoicedItems && ( { )} - {localeElements} - - {!!user ? ( + {user ? ( } - > + /> ) : ( history.push('/login')} icon={} - > + /> )} diff --git a/media-store/app-src/src/components/InvoicePage.jsx b/media-store/app-src/src/components/InvoicePage.jsx index 645a24a0..505ccd9a 100644 --- a/media-store/app-src/src/components/InvoicePage.jsx +++ b/media-store/app-src/src/components/InvoicePage.jsx @@ -38,9 +38,8 @@ const InvoicePage = () => { const onBuy = () => { setLoading(true); invoice( - invoicedItems.map(({ ID, unitPrice }) => ({ + invoicedItems.map(({ ID }) => ({ ID, - unitPrice, })) ) .then(() => { diff --git a/media-store/app-src/src/components/TracksPage.jsx b/media-store/app-src/src/components/TracksPage.jsx index a59cf6ea..ed6c9835 100644 --- a/media-store/app-src/src/components/TracksPage.jsx +++ b/media-store/app-src/src/components/TracksPage.jsx @@ -153,7 +153,7 @@ const TracksContainer = () => { const isAlreadyOrdered = !isEmployee && track.alreadyOrdered; const onDeleteTrack = isEmployee && ((ID) => deleteTrack(ID)); return ( - + response.data.value) .catch(this.handleError); -}; +} const TrackForm = ({ initialAlbumTitle }) => { const { handleError } = useErrors(); @@ -89,5 +89,8 @@ const TrackForm = ({ initialAlbumTitle }) => { TrackForm.propTypes = { initialAlbumTitle: PropTypes.string, }; +TrackForm.defaultProps = { + initialAlbumTitle: undefined, +}; export { TrackForm }; diff --git a/media-store/app-src/src/components/tracks/EditAction.jsx b/media-store/app-src/src/components/tracks/EditAction.jsx index 6b156076..1c8c42e6 100644 --- a/media-store/app-src/src/components/tracks/EditAction.jsx +++ b/media-store/app-src/src/components/tracks/EditAction.jsx @@ -18,6 +18,16 @@ const EditAction = ({ ID, name, composer, genre, unitPrice, album, afterTrackUpd setVisible(true); }; + const afterCloseModal = () => { + setUpdateLoading(true); + getTrack(ID) + .then((response) => { + afterTrackUpdate(response.data); + setUpdateLoading(false); + }) + .catch(handleError); + }; + const onFinish = (value) => { setConfirmLoading(true); updateTrack({ @@ -45,16 +55,6 @@ const EditAction = ({ ID, name, composer, genre, unitPrice, album, afterTrackUpd setVisible(false); }; - const afterCloseModal = () => { - setUpdateLoading(true); - getTrack(ID) - .then((response) => { - afterTrackUpdate(response.data); - setUpdateLoading(false); - }) - .catch(handleError); - }; - return ( <> {updateLoading ? : } @@ -86,11 +86,11 @@ const EditAction = ({ ID, name, composer, genre, unitPrice, album, afterTrackUpd onFinish={onFinish} onFinishFailed={() => console.log('Not valid params provided')} initialValues={{ - name: name, - composer: composer, + name, + composer, genreID: genre.ID, albumID: album.ID, - unitPrice: unitPrice, + unitPrice, }} > diff --git a/media-store/app-src/src/components/tracks/ManagedTrack.jsx b/media-store/app-src/src/components/tracks/ManagedTrack.jsx index 8beaa37c..42bea8c8 100644 --- a/media-store/app-src/src/components/tracks/ManagedTrack.jsx +++ b/media-store/app-src/src/components/tracks/ManagedTrack.jsx @@ -1,9 +1,10 @@ -import React, { useState, useRef } from "react"; -import { Card } from "antd"; -import { EditAction } from "./EditAction"; -import { DeleteAction } from "./DeleteAction"; -import { TrackCardBody } from "./TrackCardBody"; -import "./ManagedTrack.css"; +import React, { useState, useRef } from 'react'; +import { Card } from 'antd'; +import PropTypes from 'prop-types'; +import { EditAction } from './EditAction'; +import { DeleteAction } from './DeleteAction'; +import { TrackCardBody } from './TrackCardBody'; +import './ManagedTrack.css'; const ManagedTrack = ({ initialTrack, onDeleteTrack }) => { const trackElement = useRef(); @@ -39,4 +40,9 @@ const ManagedTrack = ({ initialTrack, onDeleteTrack }) => { ); }; +ManagedTrack.propTypes = { + initialTrack: PropTypes.object.isRequired, + onDeleteTrack: PropTypes.func.isRequired, +}; + export { ManagedTrack }; diff --git a/media-store/app-src/src/components/tracks/Track.jsx b/media-store/app-src/src/components/tracks/Track.jsx index 0ab57308..3fb90950 100644 --- a/media-store/app-src/src/components/tracks/Track.jsx +++ b/media-store/app-src/src/components/tracks/Track.jsx @@ -53,8 +53,11 @@ const Track = ({ initialTrack, isAlreadyOrdered }) => { }; Track.propTypes = { - initialTrack: PropTypes.object, + initialTrack: PropTypes.object.isRequired, isAlreadyOrdered: PropTypes.bool, }; +Track.defaultProps = { + isAlreadyOrdered: undefined, +}; export { Track }; diff --git a/media-store/package.json b/media-store/package.json index 334f1a55..8015f55f 100644 --- a/media-store/package.json +++ b/media-store/package.json @@ -41,4 +41,4 @@ } } } -} +} \ No newline at end of file diff --git a/media-store/srv/browse-invoices-service.cds b/media-store/srv/browse-invoices-service.cds index 8305d806..42e45545 100644 --- a/media-store/srv/browse-invoices-service.cds +++ b/media-store/srv/browse-invoices-service.cds @@ -11,8 +11,7 @@ service BrowseInvoices @(requires : 'customer') { entity Invoices as projection on my.Invoices; action invoice(tracks : array of { - ID : Integer; - unitPrice : Decimal(10, 2); + ID : Integer; }); action cancelInvoice(ID : Integer); diff --git a/media-store/srv/browse-invoices-service.js b/media-store/srv/browse-invoices-service.js index a7e490cf..c49eb057 100644 --- a/media-store/srv/browse-invoices-service.js +++ b/media-store/srv/browse-invoices-service.js @@ -6,6 +6,10 @@ const CANCEL_STATUS = -1; const SHIPPED_STATUS = 1; const UTC_DATE_TIME_FORMAT = "YYYY-MM-DDThh:mm:ss"; +function roundNumber(num) { + return Math.round((num + Number.EPSILON) * 100) / 100; +} + // the same function there is in the frontend const isLeverageTimeExpired = (utcNowTimestamp, invoiceDate) => { const duration = moment.duration( @@ -16,7 +20,7 @@ const isLeverageTimeExpired = (utcNowTimestamp, invoiceDate) => { module.exports = async function () { const db = await cds.connect.to("db"); // connect to database service - const { Invoices, InvoiceItems } = db.entities; + const { Invoices, InvoiceItems, Tracks } = db.entities; this.on("READ", "Invoices", async (req) => { return await db.run(req.query.where({ customer_ID: req.user.attr.ID })); @@ -24,13 +28,9 @@ module.exports = async function () { this.on("invoice", async (req) => { const { tracks } = req.data; - const newInvoicedTracks = tracks.map(({ ID }) => ID); + const newInvoicedTrackIds = tracks.map(({ ID }) => ID); const customerId = req.user.attr.ID; const utcNowDateTime = moment().utc().format(UTC_DATE_TIME_FORMAT); - const total = tracks.reduce( - (acc, { unitPrice }) => acc + Number(unitPrice), - 0 - ); const transaction = await db.tx(req); // check if already exists @@ -45,13 +45,21 @@ module.exports = async function () { }) ) ); - const isNewInvoiceHasInvoicedTracks = invoicedTracks.some( - ({ track_ID: curID }) => newInvoicedTracks.includes(curID) - ); - if (isNewInvoiceHasInvoicedTracks) { + const isInValidInvoice = invoicedTracks.some(({ track_ID: curID }) => { + return newInvoicedTrackIds.includes(curID); + }); + if (isInValidInvoice) { req.reject(400, "Invoice contains already owned values"); } + const newInvoicedTracks = await transaction.run( + SELECT("ID", "unitPrice").from(Tracks).where({ ID: newInvoicedTrackIds }) + ); + const total = newInvoicedTracks.reduce( + (acc, { unitPrice }) => acc + roundNumber(Number(unitPrice)), + 0 + ); + // getting last ids for new records let { ID: lastInvoiceId } = await transaction.run( SELECT.one(Invoices).columns("ID").orderBy({ ID: "desc" }) diff --git a/media-store/test/test.http b/media-store/test/test.http index 677b493b..aab092f4 100644 --- a/media-store/test/test.http +++ b/media-store/test/test.http @@ -1,74 +1,92 @@ -@media-store-service = http://localhost:4004/media @browse-tracks-service = http://localhost:4004/browse-tracks +@browse-invoices-service = http://localhost:4004/browse-invoices +@manage-store-service = http://localhost:4004/manage-store +@user-service = http://localhost:4004/users + ### ------------------------------------------------------------------------ -# Get service -GET {{media-store-service}} +## Browse Tracks service +### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------ -# Get $metadata document -GET {{media-store-service}}/$metadata +# Get browse-tracks-service +GET {{browse-tracks-service}} ### ------------------------------------------------------------------------ -# Get Artists -GET {{media-store-service}}/Artists +# Get $metadata document of browse-tracks-service +GET {{browse-tracks-service}}/$metadata ### ------------------------------------------------------------------------ -# Get Albums -GET {{media-store-service}}/Albums +# Get Trakcs +GET {{browse-tracks-service}}/Tracks + +### ------------------------------------------------------------------------ +# Get Albums by artist ID axpanding tracks and artist +GET {{browse-tracks-service}}/Albums ?$filter=artist_ID eq 1 &$expand=tracks,artist ### ------------------------------------------------------------------------ -# Get Customers -GET {{media-store-service}}/Customers(1) -?$expand=supportRep +## Users service +### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------ -# Get Employees -GET {{media-store-service}}/Employees(1) -?$expand=subordinates,reportsTo - -### ------------------------------------------------------------------------ -# Get Genres -GET {{media-store-service}}/Genres -?$top=10 - -### ------------------------------------------------------------------------ -# Get InvoiceItems -GET {{media-store-service}}/InvoiceItems -?$expand=track,invoice&$top=10 - -### ------------------------------------------------------------------------ -# Get Invoices -GET {{media-store-service}}/Invoices -?$top=10&$expand=customer - -### ------------------------------------------------------------------------ -# Get MediaTypes -GET {{media-store-service}}/MediaTypes -?$top=10 - -### ------------------------------------------------------------------------ -# Get PlaylistTrack -GET {{media-store-service}}/PlaylistTrack/$count -?$filter=playlist_ID eq 8 -# ?$count=true -# &$expand=track&$select=track - -### ------------------------------------------------------------------------ -# Get Playlists -GET {{media-store-service}}/Playlists -?$top=10 - -### ------------------------------------------------------------------------ -# Get Playlists -GET {{media-store-service}}/Tracks -# ?$top=10&$expand=genre,mediaType,album - - -### ------------------------------------------------------------------------ -# Get My tracks -POST {{browse-tracks-service}}/getInvoicedTracks +# Login user (customer/employee) +POST {{user-service}}/login Content-Type: application/json + +{ + "email": "leonekohler@surfeu.de", + "password": "some" +} + +# employee data +# { +# "email": "andrew@chinookcorp.com", +# "password": "some" +# } + +### ------------------------------------------------------------------------ +# Refresh tokens +POST {{user-service}}/refreshTokens +Content-Type: application/json + +{ + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imxlb25la29obGVyQHN1cmZldS5kZSIsIklEIjoyLCJyb2xlcyI6WyJjdXN0b21lciJdLCJpYXQiOjE2MDc0MzE2MzYsImV4cCI6MTYwNzQzMjgzNn0.5MPlOr05Qr1fYbE0dutnUu3n8JMOiuLLUnsnM0RSeA8" +} + +### ------------------------------------------------------------------------ +# Get current customer data +GET {{user-service}}/Customers(2) +Authorization: Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imxlb25la29obGVyQHN1cmZldS5kZSIsIklEIjoyLCJyb2xlcyI6WyJjdXN0b21lciJdLCJpYXQiOjE2MDc0MzE5NTgsImV4cCI6MTYwNzQzMjU1OH0.2sP3epaEo8tpBJZ-PuJInuC36TOyMgL2W6QjNFGyhSs + +### ------------------------------------------------------------------------ +# Get current employee data +GET {{user-service}}/Employees(1) +Authorization: Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFuZHJld0BjaGlub29rY29ycC5jb20iLCJJRCI6MSwicm9sZXMiOlsiZW1wbG95ZWUiXSwiaWF0IjoxNjA3NDMyMTY0LCJleHAiOjE2MDc0MzI3NjR9.HVwadUbUq3K0_5NIo9pYX9rK9awmzZ3hIqauF3yusdI + + +### ------------------------------------------------------------------------ +## Invocies service +### ------------------------------------------------------------------------ + +### ------------------------------------------------------------------------ +# Get all current customer invoices +GET {{browse-invoices-service}}/Invoices +Authorization: Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imxlb25la29obGVyQHN1cmZldS5kZSIsIklEIjoyLCJyb2xlcyI6WyJjdXN0b21lciJdLCJpYXQiOjE2MDc0MzE5NTgsImV4cCI6MTYwNzQzMjU1OH0.2sP3epaEo8tpBJZ-PuJInuC36TOyMgL2W6QjNFGyhSs + + +### ------------------------------------------------------------------------ +## Manage store service +### ------------------------------------------------------------------------ + +### ------------------------------------------------------------------------ +# Crete new Album +POST {{manage-store-service}}/Artists +Content-Type: application/json +Authorization: Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFuZHJld0BjaGlub29rY29ycC5jb20iLCJJRCI6MSwicm9sZXMiOlsiZW1wbG95ZWUiXSwiaWF0IjoxNjA3NDQxMzQwLCJleHAiOjE2MDc0NDE5NDB9._JQzhqUwbutccoSWWeCZ2R16gLzzMD7b21bZ5wxN1gU + +{ + "name": "some" +} \ No newline at end of file