Compare commits

...

80 Commits

Author SHA1 Message Date
Daniel
67b845c32e simplified setup 2021-01-04 17:03:58 +01:00
Dmitriynj
fe52eec28e add right datetime format 2021-01-04 14:37:01 +01:00
Dmitriynj
4816028fc1 changing package.json scripts. adjusting mock datetime format 2021-01-04 14:37:01 +01:00
Dmitriynj
1c9a24444a change jest timeout. add @capire/chinook as dependency 2021-01-04 14:37:01 +01:00
Dmitriynj
193e762554 returning package.json dependencies 2021-01-04 14:37:01 +01:00
Dmitriynj
9abeb67d82 returning subfolders names 2021-01-04 14:37:01 +01:00
Dmitriynj
9d28ca9844 changing chinook sql kind property to sql 2021-01-04 14:37:01 +01:00
Dmitriynj
a45d79e8e8 change tests 2021-01-04 14:37:01 +01:00
Dmitriynj
4fd0b74b8c change folders structure 2021-01-04 14:37:01 +01:00
Daniel
69e510a407 Moved to chinook + added .env 2021-01-04 14:37:01 +01:00
Daniel
5cec82fa00 re-including app folder 2021-01-04 14:37:01 +01:00
Daniel
ef0f5bea65 Cleanup folder layout 2021-01-04 14:37:01 +01:00
Dmitriynj
317d45074a change tests 2021-01-04 14:37:01 +01:00
Dmitriynj
cb71e2ed9b update tests 2021-01-04 14:37:01 +01:00
Dmitriynj
145becb1c4 change retry count 2021-01-04 14:37:01 +01:00
Dmitriynj
aeafb1d010 change invoice action impl. changing .http sample file 2021-01-04 14:37:01 +01:00
Dmitriynj
72616ae4ce changing retry number. add readme.md point 2021-01-04 14:37:01 +01:00
Dmitriynj
fc41981eb9 resolve quotation mark error when searching 2021-01-04 14:37:01 +01:00
Dmitriynj
b3ea0cc4f1 add retry interceptor impl 2021-01-04 14:37:01 +01:00
Dmitriynj
09dd526f22 remove unnecessary assosiation. add /index.html route to main page 2021-01-04 14:37:01 +01:00
Dmitriynj
1dd1863266 changing webpack-dev-server-config 2021-01-04 14:37:01 +01:00
Dmitriynj
4ebc20f8ce changing readme.md style 2021-01-04 14:37:01 +01:00
Dmitriynj
bebc18a3e6 changing endings 2021-01-04 14:37:01 +01:00
Dmitriynj
fcd1bf9c20 change words to lowwer case in readme.md 2021-01-04 14:37:01 +01:00
Dmitriynj
d3d4b32c79 change readme.md. add folders description 2021-01-04 14:37:01 +01:00
Dmitriynj
de04a896d1 changing cds services 2021-01-04 14:37:01 +01:00
Dmitriynj
723bd93ef3 change readme.md 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
64cc4ec26a move xs-securiry.json 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
ee63541845 change gitignore. change server.js 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
00474edffe add app to gitignore 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
dbe4b8a7bd moving front app to subfolder. add webpack config with watch and dev-server mode. moving deploy things in subfolder 2021-01-04 14:37:01 +01:00
Tamashevich, Dzmitry
0e86e1e1fd change token live time 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
90fc300ada add .mta files to gitignore 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
a04cc0c25f changing tests. removing importData func 2021-01-04 14:37:01 +01:00
Tamashevich, Dzmitry
029ba61098 changing invoice request 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
d9b607919a changing logger 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
f439119e73 change readme.md. clean up console.logs. add check for exsisting invoices 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
fe0562f38b edit person page. remove invoicedItems on logout 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
58af1879f7 clean up things 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
6454019713 now create tracks method works properly 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
3d176237c1 refactoring error page 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
938abb6387 add response interceptors for refreshTokens method 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
76cbf7f9ca add flow when invalid credentials 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
4b4fe2dc3f refactoring user settings in the frontend 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
d78e759bf7 add hana kind 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
185e6b939f change invoiceDate impl 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
7045914e57 add env vars. change db kind 2021-01-04 14:37:01 +01:00
Tamashevich, Dzmitry
05550a14b1 add frontend code. add deploy config 2021-01-04 14:37:01 +01:00
Tamashevich, Dzmitry
25bdc0a6b2 refactor csv data 2021-01-04 14:37:01 +01:00
Tamashevich, Dzmitry
29fb47f2e9 add dev only things 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
fdd2a7a2c5 providing initial data. replacing bycript with bycriptjs 2021-01-04 14:37:01 +01:00
Tamashevich, Dzmitry
3d1502ddfe return bool flag 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
c932b486e1 add compositions for cascade delete 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
e08b1c6246 refactoring code 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
ecdc32bad1 replacing restriction conditions 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
34acef85b6 refactoring requests 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
70b0c85346 add custom authentication checks 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
3cf02cb567 refactor import usage. refactor invoices implementation 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
bcfce87276 add customer restriction when browsing invoices 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
a319199e10 add cancel invoice action 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
52f00c62b7 add status to invoices 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
9a63f406ec rename person type 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
49b8f4ef95 reaname services 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
30d5c789bc add getUser method 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
0690762207 add invoice action 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
49f6b8c060 add mocked auth 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
937d9caf2b add app bundles to medi-store 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
ae05e8a609 remove compiled bundles from app 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
a917dac1b2 add getMyTracks option 2021-01-04 14:37:01 +01:00
Tamashevich, Dzmitry
99d4da34d7 adjust README.md with frontend repo link provided 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
bdbd9d425b add tracks main page. add get my tracks action 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
0f6444589f return timeout for mocha runner 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
d796bf1ec9 remove redundant args 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
41da47aa4f adjast regex for omiting id from column name 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
4783a5729d add tests. add to many associations 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
e58ad84a2f add reorder for column names 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
fc07f7ebba remove redundantt folder 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
7a0e8fdba6 remove redundantt folder 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
e7be207911 add cli tool 2021-01-04 14:37:01 +01:00
Dzmitry_Tamashevich@epam.com
e33a455154 creating cds model of chinook db 2021-01-04 14:37:01 +01:00
95 changed files with 19997 additions and 1 deletions

3
chinook/.env Normal file
View File

@@ -0,0 +1,3 @@
# REVISIT: This is not a good practice -> don't do it that way, we just did it to save some time :)
ACCESS_TOKEN_SECRET=secret
REFRESH_TOKEN_SECRET=refresh-secret

35
chinook/.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
# CAP media-store
_out
*.db
connection.properties
default-*.json
gen/
node_modules/
target/
package-lock.json
app/build
# html5Deployer
app/deployers/html5Deployer/resources/
# Web IDE, App Studio
.che/
.gen/
# MTA
*_mta_build_tmp
*.mtar
*.mta
mta_archives/
# Other
.DS_Store
*.orig
*.log
*.iml
*.flattened-pom.xml
# IDEs
# .vscode
# .idea

20
chinook/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
// >>>>>>>> Add CDS Editor here as soon it is available of vscode marketplace!,
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mechatroner.rainbow-csv",
"humao.rest-client",
"alexcvzz.vscode-sqlite",
"hbenl.vscode-mocha-test-adapter",
"sdras.night-owl"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

17
chinook/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
// 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": [
{
"command": "cds run --with-mocks --in-memory?",
"name": "cds run",
"request": "launch",
"type": "node-terminal",
"skipFiles": ["<node_internals>/**"]
}
]
}

8
chinook/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"files.exclude": {
"**/.gitignore": true,
"**/.git": true,
"**/.vscode": true
},
"files.watcherExclude": {}
}

25
chinook/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,25 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "cds watch",
"command": "cds",
"args": ["watch"],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"type": "shell",
"label": "cds run",
"command": "cds",
"args": ["run", "--with-mocks", "--in-memory?"],
"problemMatcher": []
}
]
}

96
chinook/README.md Normal file
View File

@@ -0,0 +1,96 @@
# Getting Started
Welcome to your new project.
It contains these folders and files, following our recommended project layout:
| File or Folder | Purpose |
| ---------------- | ------------------------------------ |
| `app/` | will contain compiled front bundles |
| `app/front/` | contains frontend app on react |
| `app/deployers/` | contains deployment stuff |
| `db/` | your domain models and data go here |
| `srv/` | your service models and code go here |
| `test/` | your services tests |
| `package.json` | project metadata and configuration |
| `mta.yaml` | deployment config |
| `readme.md` | this getting started guide |
| `server.js` | initial server set up |
## Development
- Start cds service on 4004 port in watch mode:
```json
cds watch
```
- Open `app/front` folder and run next commands. This will install dependencies and run frontend src files watcher. When you will change src files your bundles in app directory will re-compiled. Now you can enjoy development:
```json
npm install
npm run watch
```
> For better frontend development experience use below command instead of watcher. This will start frontend dev server on 3000 port. Now your bundles will be hot reloaded, this means you do not need reload the page to see changes:
>
> ```json
> npm run start
> ```
## Test
- Change package.json db section
```json
"db": {
"kind": "sql"
}
```
- Run tests
```json
npm run test
```
## Deployment
- Make sure you already have hana trial instance in your cockpit dashboard (SAP Cloud Platform).
Or if you are using hana instance - change it in mta.yaml config file from hanatrial to hana
- Change package.json db section
```json
"db": {
"kind": "hana"
}
```
- Authenticate to the Cloud Foundry:
```json
cf login
```
- Open `app/front` folder and run the following commands. This will create frontend production bundles in app subfolder:
```json
npm install
npm run build:prod
```
- Clean up app/deployers/html5Deployer/resources folder from the previous frontend build
- From root directory run:
```json
mbt build -t ./
cf deploy media-store_1.0.0.mtar
```
- Now your services should be deployed with hanatrial instance and filled with initial data
## Learn More
- [Learn more about CAP](https://cap.cloud.sap/docs/get-started/)
- [Deploying to Cloud Foundry](https://cap.cloud.sap/docs/advanced/deploy-to-cloud)

View File

@@ -0,0 +1,11 @@
{
"name": "media-store-approuter",
"description": "Approuter",
"version": "1.0.0",
"dependencies": {
"@sap/approuter": "^6.8.2"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}

View File

@@ -0,0 +1,17 @@
{
"welcomeFile": "/index.html",
"authenticationMethod": "none",
"routes": [
{
"source": "/api/(.*)",
"target": "$1",
"destination": "srv-binding",
"authenticationType": "none"
},
{
"source": "^(.*)",
"target": "mediastore/$1",
"service": "html5-apps-repo-rt"
}
]
}

View File

@@ -0,0 +1,12 @@
{
"name": "media-store-html5deployer",
"engines": {
"node": ">=6.0.0"
},
"dependencies": {
"@sap/html5-app-deployer": "^2.0.0"
},
"scripts": {
"start": "node node_modules/@sap/html5-app-deployer/index.js"
}
}

View File

@@ -0,0 +1,7 @@
{
"xsappname": "media-store-xsuaa",
"tenant-mode": "dedicated",
"scopes": [],
"attributes": [],
"role-templates": []
}

View File

@@ -0,0 +1,5 @@
{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime", "babel-plugin-syntax-dynamic-import"]
}

View File

@@ -0,0 +1,43 @@
{
"env": {
"browser": true,
"es2020": true
},
"extends": ["plugin:react/recommended", "airbnb", "prettier"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": ["react", "prettier"],
"rules": {
"prettier/prettier": ["error", { "parser": "flow", "endOfLine": "auto" }],
"linebreak-style": [0, "error", "windows"],
"import/prefer-default-export": "off",
"no-shadow": "off",
"react/forbid-prop-types": "off",
"no-alert": "off",
"jsx-a11y/label-has-associated-control": [
"error",
{
"required": {
"some": ["nesting", "id"]
}
}
],
"jsx-a11y/label-has-for": [
"error",
{
"required": {
"some": ["nesting", "id"]
}
}
],
"react/jsx-props-no-spreading": "off", // props spreading,
"no-console": "off",
"consistent-return": "off",
"prefer-destructuring": "off"
}
}

23
chinook/app/front/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -0,0 +1,4 @@
{
"printWidth": 100,
"singleQuote": true
}

13
chinook/app/front/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceRoot}/src"
}
]
}

View File

@@ -0,0 +1 @@
"# Media store UI"

View File

@@ -0,0 +1,67 @@
{
"name": "mediastore",
"version": "0.1.0",
"private": false,
"scripts": {
"start": "./node_modules/.bin/webpack-dev-server --config ./webpack/webpack-dev-server.js",
"watch": "./node_modules/.bin/webpack -w --config ./webpack/webpack.dev.js",
"build:dev": "./node_modules/.bin/webpack --config ./webpack/webpack.dev.js",
"build:prod": "./node_modules/.bin/webpack --config ./webpack/webpack.prod.js",
"lint": "./node_modules/.bin/eslint"
},
"dependencies": {
"@ant-design/icons": "4.3.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@umijs/hooks": "^1.9.3",
"antd": "^4.8.2",
"axios": "^0.20.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.3.2",
"css-minimizer-webpack-plugin": "^1.1.5",
"events": "^3.2.0",
"html-webpack-plugin": "^4.5.0",
"lodash": "^4.17.20",
"mini-css-extract-plugin": "^1.3.1",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"react": "^16.14.0",
"react-dev-utils": "^11.0.1",
"react-dom": "^16.14.0",
"react-refresh": "^0.9.0",
"react-router-dom": "^5.2.0",
"terser-webpack-plugin": "^5.0.3",
"webpack": "5.8.0",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.4.0"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.7",
"@babel/runtime": "^7.12.5",
"babel-loader": "^8.2.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"cowsay": "^1.4.0",
"css-loader": "^5.0.1",
"eslint": "^7.14.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"prettier": "^2.2.1",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack-cli": "^3.3.12"
},
"eslintConfig": {
"extends": "react-app"
}
}

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<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="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,31 @@
{
"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",
"sap.app": {
"id": "mediastore",
"applicationVersion": {
"version": "1.0.0"
}
}
}

View File

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

View File

@@ -0,0 +1,10 @@
{
"welcomeFile": "/index.html",
"routes": [
{
"source": "^(.*)",
"target": "$1",
"service": "html5-apps-repo-rt"
}
]
}

View File

@@ -0,0 +1,57 @@
@import "~antd/dist/antd.css";
html {
overflow: hidden;
}
#root {
height: 100%;
}
section.ant-layout {
height: 100vh;
overflow: auto;
}
/* Layout
*/
.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 {
animation: App-logo-spin infinite 20s linear;
}
}
.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: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import 'antd/dist/antd.css';
import './App.css';
import { Layout } from 'antd';
import { MyRouter } from './components/Router';
import { AppStateContextProvider } from './contexts/AppStateContext';
const App = () => {
return (
<Layout style={{ height: '100%' }}>
<AppStateContextProvider>
<MyRouter />
</AppStateContextProvider>
</Layout>
);
};
export default App;

View File

@@ -0,0 +1,168 @@
import axios from 'axios';
import { getUserFromLS, getLocaleFromLS } from '../util/localStorageService';
import { emitter } from '../util/EventEmitter';
const TIMEOUT = 2000;
const RETRY_COUNT = 3;
/**
* This is axios instance
*/
const axiosInstance = axios.create({
baseURL: process.env.SERVICE_URL,
timeout: TIMEOUT,
retryDelay: TIMEOUT,
retry: RETRY_COUNT,
});
/**
* Changing user axios default params,
* which are used in api call functions (calls.js)
* @param {*} currentUser current user from react state and local storage
*/
function changeUserDefaults(currentUser) {
if (currentUser) {
axiosInstance.defaults.headers.common.Authorization = `Basic ${currentUser.accessToken}`;
axiosInstance.defaults.userID = currentUser.ID;
if (currentUser.roles.includes('customer')) {
axiosInstance.defaults.userEntity = `Customers/${currentUser.ID}`;
axiosInstance.defaults.tracksEntity = 'MarkedTracks';
} else {
axiosInstance.defaults.userEntity = `Employees/${currentUser.ID}`;
axiosInstance.defaults.tracksEntity = 'Tracks';
}
} else {
axiosInstance.defaults.tracksEntity = 'Tracks';
}
}
/**
* This func changing axios instance default params
* @param {*} locale current locale from react state and local storage
*/
function changeLocaleDefaults(locale) {
if (locale) {
axiosInstance.defaults.headers.common['Accept-language'] = locale;
}
}
/**
* Init axios defaults
*/
const user = getUserFromLS();
const locale = getLocaleFromLS();
changeUserDefaults(user);
changeLocaleDefaults(locale);
/**
* Retry request if response time is too long
* See link below
* {@link https://github.com/axios/axios/issues/164#issuecomment-327837467 GitHub}
* @param {*} err response error object
*/
function axiosRetryInterceptor(err) {
const config = err.config;
// If config does not exist or the retry option is not set, reject
if (config && config.retry) {
// Set the variable for keeping track of the retry count
config.retryCount = config.retryCount || 0;
// Check if we've maxed out the total number of retries
if (config.retryCount >= config.retry) {
// Reject with the error
return Promise.reject(err);
}
// Increase the retry count
config.retryCount += 1;
// Create new promise to handle exponential backoff
const backoff = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, config.retryDelay || 1);
});
// Return the promise in which recalls axios to retry the request
return backoff.then(() => {
return axios(config);
});
}
}
/**
* Things below needed for refresh tokens mechanism implementation
*/
let isRefreshing = false;
let subscribers = [];
const refreshTokens = (refreshToken) => {
return axiosInstance.post(
'users/refreshTokens',
{ refreshToken },
{
headers: { 'content-type': 'application/json' },
}
);
};
/**
* Refresh tokens interceptor
* See link below
* {@link https://gist.github.com/mkjiau/650013a99c341c9f23ca00ccb213db1c#gistcomment-3536511 GitHub}
* @param {*} error error response object
*/
function axiosRefreshTokensInterceptor(error) {
const originalRequest = error.config;
const user = getUserFromLS();
if (error.response && error.response.status === 401 && !!user) {
if (originalRequest.url === 'users/login') {
return Promise.reject(error);
}
// if users/refreshTokens request failed
if (isRefreshing && originalRequest.url === 'users/refreshTokens') {
subscribers.forEach((request) => request.reject(error));
subscribers = [];
isRefreshing = false;
return Promise.reject(error);
}
// if got a 401 error we sending users/refreshTokens request
if (!isRefreshing) {
isRefreshing = true;
refreshTokens(user.refreshToken)
.then((response) => {
emitter.emit('UPDATE_USER', response.data);
subscribers.forEach((request) => request.resolve(response.data.accessToken));
subscribers = [];
isRefreshing = false;
})
.catch(() => {
emitter.emit('UPDATE_USER', undefined);
});
}
// holding requests which should be sended after users/refreshTokens complete
// otherwise if users/refreshTokens failed an error will be thrown
return new Promise((resolve, reject) => {
subscribers.push({
resolve: (newAccessToken) => {
originalRequest.headers.Authorization = `Basic ${newAccessToken}`;
resolve(axiosInstance(originalRequest));
},
reject: (err) => {
reject(err);
},
});
});
}
}
axiosInstance.interceptors.response.use(null, (error) => {
return (
axiosRefreshTokensInterceptor(error) || axiosRetryInterceptor(error) || Promise.reject(error)
);
});
export { axiosInstance, changeLocaleDefaults, changeUserDefaults };

View File

@@ -0,0 +1,164 @@
import { isEmpty } from 'lodash';
import { axiosInstance } from './axiosInstance';
const BROWSE_TRACKS_SERVICE = 'browse-tracks';
const INVOICES_SERVICE = 'browse-invoices';
const USER_SERVICE = 'users';
const MANAGE_STORE = 'manage-store';
const constructGenresQuery = (genreIds) => {
return !isEmpty(genreIds)
? ` and ${genreIds.map((value) => `genre_ID eq ${value}`).join(' or ')}`
: '';
};
const fetchTacks = ({ $top = 20, $skip = 0, genreIds = [], substr = '' } = {}) => {
const serializeTracksUrl = () => {
return `$expand=genre,album($expand=artist)&$top=${$top}&$skip=${$skip}&$filter=${`contains(name,'${substr}')${constructGenresQuery(
genreIds
)}`}`;
};
return axiosInstance.get(`${BROWSE_TRACKS_SERVICE}/${axiosInstance.defaults.tracksEntity}`, {
params: {},
paramsSerializer: () => serializeTracksUrl(),
});
};
const countTracks = ({ genreIds = [], substr = '' } = {}) => {
const { tracksEntity } = axiosInstance.defaults;
return axiosInstance.get(
`${BROWSE_TRACKS_SERVICE}/${tracksEntity}/$count?$filter=${`contains(name,'${substr}')${constructGenresQuery(
genreIds
)}`}`
);
};
const fetchGenres = () => {
return axiosInstance.get(`${BROWSE_TRACKS_SERVICE}/Genres`);
};
const invoice = (tracks) => {
return axiosInstance.post(
`${INVOICES_SERVICE}/invoice`,
{
tracks,
},
{
headers: { 'content-type': 'application/json' },
}
);
};
const fetchPerson = () => {
return axiosInstance.get(`${USER_SERVICE}/${axiosInstance.defaults.userEntity}`);
};
const confirmPerson = (person) => {
return axiosInstance.put(
`${USER_SERVICE}/${axiosInstance.defaults.userEntity}`,
{
...person,
},
{
headers: { 'content-type': 'application/json' },
}
);
};
const fetchInvoices = () => {
return axiosInstance.get(
`${INVOICES_SERVICE}/Invoices?$expand=invoiceItems($expand=track($expand=album($expand=artist)))`
);
};
const cancelInvoice = (ID) => {
return axiosInstance.post(
`${INVOICES_SERVICE}/cancelInvoice`,
{
ID,
},
{
headers: { 'content-type': 'application/json' },
}
);
};
const fetchAlbumsByName = (substr = '', top) => {
return axiosInstance.get(
`${BROWSE_TRACKS_SERVICE}/Albums?$filter=${`contains(title,'${substr}')&$top=${top}`}`
);
};
const addTrack = (data) => {
return axiosInstance.post(`${MANAGE_STORE}/Tracks`, data, {
headers: { 'content-type': 'application/json;IEEE754Compatible=true' },
});
};
const addArtist = (data) => {
return axiosInstance.post(`${MANAGE_STORE}/Artists`, data, {
headers: { 'content-type': 'application/json' },
});
};
const addAlbum = (data) => {
return axiosInstance.post(`${MANAGE_STORE}/Albums`, data, {
headers: { 'content-type': 'application/json' },
});
};
const fetchArtistsByName = (substr = '', top) => {
return axiosInstance.get(
`${MANAGE_STORE}/Artists?$filter=${`contains(name,'${substr}')&$top=${top}`}`
);
};
const login = (data) => {
return axiosInstance.post(`${USER_SERVICE}/login`, data, {
headers: { 'content-type': 'application/json' },
});
};
const updateTrack = (track) => {
return axiosInstance.put(
`${MANAGE_STORE}/Tracks/${track.ID}`,
{
...track,
},
{
headers: { 'content-type': 'application/json;IEEE754Compatible=true' },
}
);
};
const getTrack = (ID) => {
return axiosInstance.get(
`${BROWSE_TRACKS_SERVICE}/${axiosInstance.defaults.tracksEntity}/${ID}?$expand=genre,album($expand=artist)`
);
};
const deleteTrack = (ID) => {
return axiosInstance.delete(`${MANAGE_STORE}/Tracks(${ID})`);
};
export {
fetchTacks,
countTracks,
fetchGenres,
invoice,
fetchPerson,
confirmPerson,
fetchInvoices,
cancelInvoice,
fetchAlbumsByName,
addTrack,
addArtist,
addAlbum,
fetchArtistsByName,
login,
updateTrack,
getTrack,
deleteTrack,
};

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { isEmpty } from 'lodash';
import { Result, Button } from 'antd';
import { useAppState } from '../hooks/useAppState';
const ErrorPage = () => {
const { error, setError } = useAppState();
const history = useHistory();
const onGoHome = () => {
setError({});
history.push('/');
};
const goLoginPage = () => {
setError({});
history.push('/login');
};
const goHomeButton = (
<Button onClick={onGoHome} key={1} type="primary">
Back Home
</Button>
);
const goLoginButton = (
<Button onClick={goLoginPage} key={2} type="primary">
Login
</Button>
);
const errorResultProps = isEmpty(error)
? {
status: 404,
title: 'Not found',
subTitle: 'Sorry, the page you visited does not exist.',
extra: goHomeButton,
}
: {
status: [404, 403, 500].includes(error.status) ? error.status : 'error',
title: error.statusText,
subTitle: error.message,
extra: error.status === 401 ? [goHomeButton, goLoginButton] : goHomeButton,
};
return <Result {...errorResultProps} />;
};
export default ErrorPage;

View File

@@ -0,0 +1,3 @@
.ant-menu-item .anticon {
margin: 0;
}

View File

@@ -0,0 +1,141 @@
import React from 'react';
import { Menu, Badge, Spin, message } from 'antd';
import { isEmpty } from 'lodash';
import {
CreditCardOutlined,
LogoutOutlined,
LoginOutlined,
LoadingOutlined,
} from '@ant-design/icons';
import { useHistory, useLocation } from 'react-router-dom';
import { useAppState } from '../hooks/useAppState';
import { setLocaleToLS } from '../util/localStorageService';
import { changeLocaleDefaults } from '../api/axiosInstance';
import { emitter } from '../util/EventEmitter';
import './Header.css';
import { requireEmployee, requireCustomer, MESSAGE_TIMEOUT } from '../util/constants';
const { SubMenu } = Menu;
const keys = ['/', '/person', '/login', '/manage', '/invoice', '/invoices'];
const AVAILABLE_LOCALES = ['en', 'fr', 'de'];
const RELOAD_LOCATION_NUMBER = 0;
const Header = () => {
const history = useHistory();
const location = useLocation();
const { user, invoicedItems, locale, setLocale, loading } = useAppState();
const currentKey = [keys.find((key) => key === location.pathname)];
const haveInvoicedItems = !isEmpty(invoicedItems);
const invoicedItemsLength = invoicedItems.length;
const onChangeLocale = (value) => {
setLocaleToLS(value);
changeLocaleDefaults(value);
setLocale(value);
history.go(RELOAD_LOCATION_NUMBER);
};
const localeElements = AVAILABLE_LOCALES.filter((localeName) => localeName !== locale).map(
(curLocale) => (
<Menu.Item key={curLocale} onClick={() => onChangeLocale(curLocale)}>
{curLocale}
</Menu.Item>
)
);
const onUserLogout = () => {
emitter.emit('UPDATE_USER', undefined);
message.warn(
'Now you are not authenticated. Log in to use full functionality',
MESSAGE_TIMEOUT
);
history.push('/');
};
return (
<div
style={{
display: 'flex',
justifyContent: 'baseline',
alignItems: 'center',
paddingLeft: '15vh',
paddingRight: '15vh',
background: 'white',
}}
>
<Menu theme="light" mode="horizontal" style={{ width: '50%' }} selectedKeys={currentKey}>
<Menu.Item key="/" onClick={() => history.push('/')}>
Browse
</Menu.Item>
{!!user && (
<Menu.Item key="/person" onClick={() => history.push('/person')}>
Profile
</Menu.Item>
)}
{requireCustomer(user) && (
<Menu.Item key="/invoices" onClick={() => history.push('/invoices')}>
Invoices
</Menu.Item>
)}
{requireEmployee(user) && (
<Menu.Item key="/manage" onClick={() => history.push('/manage')}>
Manages
</Menu.Item>
)}
</Menu>
<Menu
style={{ width: '50%', display: 'flex', justifyContent: 'flex-end' }}
theme="light"
mode="horizontal"
selectedKeys={currentKey}
>
<Menu.Item>
{loading && <Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />}
</Menu.Item>
{haveInvoicedItems && (
<Menu.Item
style={{
width: 40,
display: 'flex',
justifyContent: 'center',
}}
onClick={() => history.push('/invoice')}
key="/invoice"
>
<div
style={{
height: '100%',
}}
>
<Badge
size="default"
style={{ backgroundColor: '#2db7f5' }}
count={invoicedItemsLength}
>
<CreditCardOutlined style={{ fontSize: 16 }} />
</Badge>
</div>
</Menu.Item>
)}
<SubMenu title={locale}>{localeElements}</SubMenu>
{user ? (
<Menu.Item
onClick={onUserLogout}
danger
icon={<LogoutOutlined style={{ fontSize: 16 }} />}
/>
) : (
<Menu.Item
key="/login"
onClick={() => history.push('/login')}
icon={<LoginOutlined style={{ fontSize: 16 }} />}
/>
)}
</Menu>
</div>
);
};
export default Header;

View File

@@ -0,0 +1,101 @@
import React from 'react';
import { Table, Button, message } from 'antd';
import { useHistory } from 'react-router-dom';
import { useAppState } from '../hooks/useAppState';
import { invoice } from '../api/calls';
import { useErrors } from '../hooks/useErrors';
import { MESSAGE_TIMEOUT } from '../util/constants';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Artist',
dataIndex: 'artist',
},
{
title: 'Album',
dataIndex: 'albumTitle',
},
{
title: 'Price',
dataIndex: 'unitPrice',
},
];
const InvoicePage = () => {
const history = useHistory();
const { handleError } = useErrors();
const { user, invoicedItems, setInvoicedItems, setLoading } = useAppState();
const data = invoicedItems.map(({ ID, ...otherProps }) => ({
key: `invoiceItem${ID}`,
...otherProps,
}));
const onBuy = () => {
setLoading(true);
invoice(
invoicedItems.map(({ ID }) => ({
ID,
}))
)
.then(() => {
setInvoicedItems([]);
message.success('Invoice successfully completed', MESSAGE_TIMEOUT);
history.push('/invoices');
})
.catch(handleError)
.finally(() => setLoading(false));
};
const onCancel = () => {
setInvoicedItems([]);
history.push('/');
};
const goLogin = () => {
history.push('/login');
};
return (
<div style={{ backgroundColor: 'white', padding: 10 }}>
<Table
bordered={false}
pagination={false}
columns={columns}
dataSource={data}
size="middle"
footer={() => (
<div
style={{
display: 'flex',
justifyContent: 'flex-end',
padding: 5,
}}
>
{user ? (
<>
<Button type="primary" size="large" onClick={onBuy}>
Buy
</Button>
<Button size="large" style={{ marginLeft: 5 }} onClick={onCancel} danger>
Cancel
</Button>
</>
) : (
<section>
<Button type="primary" size="large" onClick={goLogin}>
Login
</Button>
<span> to buy selected</span>
</section>
)}
</div>
)}
/>
</div>
);
};
export default InvoicePage;

View File

@@ -0,0 +1,107 @@
import React from 'react';
import { Form, Input, Button, Checkbox, message } from 'antd';
import { useHistory } from 'react-router-dom';
import { login } from '../api/calls';
import { useAppState } from '../hooks/useAppState';
import { useErrors } from '../hooks/useErrors';
import { MESSAGE_TIMEOUT } from '../util/constants';
import { emitter } from '../util/EventEmitter';
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 8,
},
};
const tailLayout = {
wrapperCol: {
offset: 8,
span: 8,
},
};
const Login = () => {
const [form] = Form.useForm();
const history = useHistory();
const { setLoading, setInvoicedItems } = useAppState();
const { handleError } = useErrors();
const onFinish = (values) => {
setLoading(true);
login({ email: values.email, password: values.password })
.then(({ data: user }) => {
emitter.emit('UPDATE_USER', user);
if (user.roles.includes('employee')) {
setInvoicedItems([]);
}
history.push('/');
})
.catch((error) => {
console.log(error);
if (error.response && error.response.status === 401) {
form.resetFields();
message.error('Invalid credentials!', MESSAGE_TIMEOUT);
} else {
handleError(error);
}
})
.finally(() => setLoading(false));
};
const onFinishFailed = (errorInfo) => {
console.log('Validation Failed:', errorInfo);
};
return (
<Form
form={form}
{...layout}
name="basic"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Form.Item
label="Email"
name="email"
rules={[
{
required: true,
message: 'Please input your email!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="password"
rules={[
{
required: true,
message: 'Please input your password!',
},
]}
>
<Input.Password style={{}} />
</Form.Item>
<Form.Item {...tailLayout} name="remember" valuePropName="checked">
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default Login;

View File

@@ -0,0 +1,115 @@
import React, { useState, useMemo, useEffect } from 'react';
import { Form, Radio, Button, message } from 'antd';
import { TrackForm } from './manage-store/TrackForm';
import { AddArtistForm } from './manage-store/AddArtistForm';
import { AddAlbumForm } from './manage-store/AddAlbumForm';
import { useErrors } from '../hooks/useErrors';
import { useAppState } from '../hooks/useAppState';
import { addTrack, addArtist, addAlbum } from '../api/calls';
import { MESSAGE_TIMEOUT } from '../util/constants';
const FORM_TYPES = {
track: 'track',
artist: 'artist',
album: 'album',
playlist: '',
};
const chooseForm = (type) => {
return (
(type === 'track' && <TrackForm />) ||
(type === 'artist' && <AddArtistForm />) ||
(type === 'album' && <AddAlbumForm />)
);
};
const ManageStore = () => {
const [form] = Form.useForm();
const { handleError } = useErrors();
const { setLoading } = useAppState();
const [formType, setFormType] = useState('track');
useEffect(() => {
form.resetFields();
}, [formType]);
const formElement = useMemo(() => {
return chooseForm(formType);
}, [formType]);
const onChangeForm = (event) => {
setFormType(event.target.value);
};
const sendCreateRequest = ({ type, ...data }) => {
setLoading(true);
let promise;
switch (type) {
case FORM_TYPES.track:
promise = addTrack({
name: data.name,
composer: data.composer,
album: { ID: data.albumID },
genre: { ID: data.genreID },
unitPrice: data.unitPrice.toString(),
});
break;
case FORM_TYPES.artist:
promise = addArtist(data);
break;
case FORM_TYPES.album:
promise = addAlbum({ title: data.name, artist: { ID: data.artistID } });
break;
default:
}
promise
.then(() => {
message.success('Entity successfully created', MESSAGE_TIMEOUT);
form.resetFields();
})
.catch(handleError)
.finally(() => setLoading(false));
};
return (
<Form
style={{ width: 700 }}
form={form}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 14,
}}
layout="horizontal"
initialValues={{
type: formType,
}}
type={formType}
onFinish={sendCreateRequest}
onFinishFailed={() => console.log('Not valid params provided')}
>
<Form.Item label="Entity" name="type">
<Radio.Group onChange={onChangeForm}>
<Radio.Button value="track">Track</Radio.Button>
<Radio.Button value="album">Album</Radio.Button>
<Radio.Button value="artist">Artist</Radio.Button>
</Radio.Group>
</Form.Item>
{formElement}
<Form.Item
type="primary"
wrapperCol={{
span: 14,
offset: 4,
}}
>
<Button onClick={() => form.submit()}>Create</Button>
</Form.Item>
</Form>
);
};
export default ManageStore;

View File

@@ -0,0 +1,170 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import PropTypes from 'prop-types';
import { Button, message, Tag, Collapse, Table, Spin } from 'antd';
import moment from 'moment';
import { useErrors } from '../hooks/useErrors';
import { useAppState } from '../hooks/useAppState';
import { cancelInvoice, fetchInvoices } from '../api/calls';
import { MESSAGE_TIMEOUT } from '../util/constants';
const { Panel } = Collapse;
const INVOICE_STATUS = {
2: {
tagTitle: 'Shipped',
color: 'green',
},
1: {
tagTitle: 'Submitted',
color: 'processing',
canCancel: true,
},
'-1': {
tagTitle: 'Cancelled',
color: 'default',
},
};
const CANCELLED_STATUS = -1;
const DATE_TIME_FORMAT_PATTERN = 'LLLL';
const UTC_DATE_TIME_FORMAT = 'YYYY-MM-DDThh:mm:ssZ';
const INVOICE_ITEMS_COLUMNS = [
{
title: 'Track name',
dataIndex: 'name',
},
{
title: 'Artist',
dataIndex: 'artistName',
},
{
title: 'Album',
dataIndex: 'albumTitle',
},
{
title: 'Price',
dataIndex: 'unitPrice',
},
];
const LEVERAGE_DURATION = 1; // in hours
const STATUSES = { submitted: 1, shipped: 2, canceled: -1 };
const isLeverageTimeExpired = (utcNowTimestamp, invoiceDate) => {
const duration = moment.duration(moment(utcNowTimestamp).diff(moment(invoiceDate).valueOf()));
return duration.asHours() > LEVERAGE_DURATION;
};
const chooseStatus = (utcNowTimestamp, invoiceDate, statusFromDb) => {
if (isLeverageTimeExpired(utcNowTimestamp, invoiceDate) && statusFromDb !== STATUSES.canceled) {
return INVOICE_STATUS[STATUSES.shipped];
}
return INVOICE_STATUS[statusFromDb];
};
const ExtraHeader = ({ ID, invoiceDate, status: initialStatus }) => {
const { loading, setLoading } = useAppState();
const { handleError } = useErrors();
const [loadingHeaderId, setLoadingHeaderId] = useState();
const [status, setStatus] = useState(initialStatus);
const statusConfig = useMemo(() => {
const utcNowTimestamp = moment(moment().utc().format(UTC_DATE_TIME_FORMAT)).valueOf();
return chooseStatus(utcNowTimestamp, invoiceDate, status);
}, [status]);
const onCancelInvoice = (event, ID) => {
event.stopPropagation();
setLoading(true);
setLoadingHeaderId(ID);
cancelInvoice(ID)
.then(() => {
message.success('Invoice successfully cancelled', MESSAGE_TIMEOUT);
setLoadingHeaderId(undefined);
setStatus(CANCELLED_STATUS);
})
.catch(handleError)
.finally(() => setLoading(false));
};
return (
<Spin spinning={loading && loadingHeaderId === ID}>
<Tag color={statusConfig.color}>{statusConfig.tagTitle}</Tag>
{statusConfig.canCancel && (
<Button onClick={(event) => onCancelInvoice(event, ID)} size="small" danger>
Cancel
</Button>
)}
</Spin>
);
};
ExtraHeader.propTypes = {
ID: PropTypes.number.isRequired,
status: PropTypes.number.isRequired,
invoiceDate: PropTypes.string.isRequired,
};
const MyInvoicesPage = () => {
const { handleError } = useErrors();
const { setLoading } = useAppState();
const [invoices, setInvoices] = useState([]);
useEffect(() => {
setLoading(true);
fetchInvoices()
.then(({ data: { value } }) => setInvoices(value))
.catch(handleError)
.finally(() => setLoading(false));
}, []);
const genExtra = useCallback(
(ID, status, invoiceDate) => <ExtraHeader ID={ID} status={status} invoiceDate={invoiceDate} />,
[]
);
const invoiceElements = useMemo(() => {
return invoices.map(({ ID, status, invoiceDate, total, invoiceItems }) => {
const invoiceItemsData = invoiceItems.map(
({
ID,
track: {
name,
unitPrice,
album: {
title: albumTitle,
artist: { name: artistName },
},
},
}) => ({
key: ID,
ID,
name,
unitPrice,
albumTitle,
artistName,
})
);
return (
<Panel
header={moment(invoiceDate).format(DATE_TIME_FORMAT_PATTERN)}
key={ID}
extra={genExtra(ID, status, invoiceDate)}
>
<div>
<Table
bordered={false}
pagination={false}
columns={INVOICE_ITEMS_COLUMNS}
dataSource={invoiceItemsData}
size="middle"
footer={() => <span style={{ fontWeight: 600 }}>{`Total price: ${total}`}</span>}
/>
</div>
</Panel>
);
});
}, [invoices]);
return (
<div>{invoiceElements && <Collapse expandIconPosition="left">{invoiceElements}</Collapse>}</div>
);
};
export default MyInvoicesPage;

View File

@@ -0,0 +1,108 @@
import React, { useState } from 'react';
import { Form, Button, message, Input } from 'antd';
import { omit, map } from 'lodash';
import { fetchPerson, confirmPerson } from '../api/calls';
import { useErrors } from '../hooks/useErrors';
import { useAppState } from '../hooks/useAppState';
import { MESSAGE_TIMEOUT } from '../util/constants';
import { useAbortableEffect } from '../hooks/useAbortableEffect';
const PERSON_PROP = {
address: 'Address ',
city: 'City ',
country: 'Country ',
fax: 'Fax: ',
firstName: 'First name: ',
lastName: 'Last name: ',
phone: 'Phone: ',
postalCode: 'Postal code: ',
state: 'State',
email: 'email',
company: 'Company: ',
};
const PersonPage = () => {
const { setLoading } = useAppState();
const { handleError } = useErrors();
const [form] = Form.useForm();
const [person, setPerson] = useState({
lastName: '',
firstName: '',
city: '',
state: '',
address: '',
country: '',
phone: '',
postalCode: '',
fax: '',
email: '',
company: '',
});
useAbortableEffect((status) => {
setLoading(true);
fetchPerson()
.then(({ data }) => {
const personData = omit(data, '@odata.context', 'ID');
if (!status.aborted) {
setPerson(personData);
}
})
.catch(handleError)
.finally(() => setLoading(false));
}, []);
const onConfirmChanges = (newPerson) => {
setLoading(true);
confirmPerson(newPerson)
.then(() => {
message.success('Person successfully updated', MESSAGE_TIMEOUT);
})
.catch(handleError)
.finally(() => setLoading(false));
};
const personProperties = map(Object.keys(person), (currentKey) => (
<div key={currentKey}>
<Form.Item label={PERSON_PROP[currentKey]} name={currentKey}>
<Input />
</Form.Item>
</div>
));
return (
<>
{person.lastName !== '' && (
<Form
form={form}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 14,
}}
layout="horizontal"
onFinish={onConfirmChanges}
onFinishFailed={() => console.log('Not valid params provided')}
initialValues={{
...person,
}}
>
{personProperties}
<Form.Item
type="primary"
wrapperCol={{
span: 14,
offset: 4,
}}
>
<Button onClick={() => form.submit()}>Confirm changes</Button>
</Form.Item>
</Form>
)}
</>
);
};
export default PersonPage;

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { isEmpty } from 'lodash';
import TracksContainer from './TracksPage';
import Header from './Header';
import PersonPage from './PersonPage';
import ErrorPage from './ErrorPage';
import InvoicePage from './InvoicePage';
import ManageStore from './ManageStore';
import MyInvoicesPage from './MyInvoicesPage';
import Login from './Login';
import { withRestrictions } from '../hocs/withRestrictions';
import { requireEmployee } from '../util/constants';
const RestrictedLogin = withRestrictions(Login, ({ user }) => !user);
const RestrictedInvoicePage = withRestrictions(
InvoicePage,
({ user, invoicedItems }) => !requireEmployee(user) && !isEmpty(invoicedItems)
);
const RestrictedPersonPage = withRestrictions(PersonPage, ({ user }) => !!user);
const RestrictedManageStore = withRestrictions(ManageStore, ({ user }) => requireEmployee(user));
const MyRouter = () => {
return (
<Router>
<Header />
<div style={{ padding: '2em 20vh' }}>
<React.Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path={['/index.html', '/tracks', '/']}>
<TracksContainer />
</Route>
<Route exact path="/person">
<RestrictedPersonPage />
</Route>
<Route exact path="/login">
<RestrictedLogin />
</Route>
<Route exact path="/invoice">
<RestrictedInvoicePage />
</Route>
<Route exact path="/invoices">
<MyInvoicesPage />
</Route>
<Route exact path="/manage">
<RestrictedManageStore />
</Route>
<Route path="/">
<ErrorPage />
</Route>
</Switch>
</React.Suspense>
</div>
</Router>
);
};
export { MyRouter };

View File

@@ -0,0 +1,4 @@
.ant-select > div.ant-select-selector {
padding: 5px;
min-width: 300px;
}

View File

@@ -0,0 +1,221 @@
import React, { useState } from 'react';
import { debounce } from 'lodash';
import { Input, Col, Row, Select, Pagination } from 'antd';
import { Track } from './tracks/Track';
import { ManagedTrack } from './tracks/ManagedTrack';
import { useAppState } from '../hooks/useAppState';
import { useErrors } from '../hooks/useErrors';
import { fetchTacks, countTracks, fetchGenres } from '../api/calls';
import { useAbortableEffect } from '../hooks/useAbortableEffect';
import { requireEmployee } from '../util/constants';
import './TracksPage.css';
const { Search } = Input;
const { Option } = Select;
const DEBOUNCE_TIMER = 500;
const DEBOUNCE_OPTIONS = {
leading: true,
trailing: false,
};
const isEven = (value) => {
return value % 2 === 0;
};
const renderGenres = (genres) =>
genres.map(({ ID, name }) => (
<Option key={ID} value={ID.toString()}>
{name}
</Option>
));
const TracksContainer = () => {
const { setLoading, user } = useAppState();
const { handleError } = useErrors();
const [state, setState] = useState({
tracks: [],
genres: [],
pagination: {
currentPage: 1,
totalItems: 0,
pageSize: 20,
},
searchOptions: {
substr: '',
genreIds: [],
},
});
useAbortableEffect((status) => {
setLoading(true);
const countTracksReq = countTracks();
const getTracksRequest = fetchTacks();
const getGenresReq = fetchGenres();
Promise.all([countTracksReq, getTracksRequest, getGenresReq])
.then(
([
{ data: totalItems },
{
data: { value: tracks },
},
{
data: { value: genres },
},
]) => {
if (!status.aborted) {
setState({
...state,
tracks,
genres,
pagination: { ...state.pagination, totalItems },
});
}
}
)
.catch(handleError)
.finally(() => setLoading(false));
}, []);
const onSearch = debounce(
() => {
setLoading(true);
const options = {
$top: state.pagination.pageSize,
substr: state.searchOptions.substr.replace(/'*/g, (value) =>
isEven(value.length) ? value : `${value}'`
),
genreIds: state.searchOptions.genreIds,
};
Promise.all([
fetchTacks(options),
countTracks({
substr: options.substr,
genreIds: options.genreIds,
}),
])
.then(([{ data: { value: tracks } }, { data: totalItems }]) =>
setState({
...state,
tracks,
pagination: { ...state.pagination, totalItems },
})
)
.catch(handleError)
.finally(() => setLoading(false));
},
DEBOUNCE_TIMER,
DEBOUNCE_OPTIONS
);
const onSelectChange = (genres) => {
setState({
...state,
searchOptions: {
...state.searchOptions,
genreIds: genres.map((value) => parseInt(value, 10)),
},
});
};
const onSearchChange = (event) => {
setState({
...state,
searchOptions: { ...state.searchOptions, substr: event.target.value },
});
};
const onChangePage = (pageNumber) => {
document.querySelector('section.ant-layout').scrollTo({ top: 0, left: 0, behavior: 'smooth' });
setLoading(true);
const options = {
$top: state.pagination.pageSize,
substr: state.searchOptions.substr,
genreIds: state.searchOptions.genreIds,
$skip: (pageNumber - 1) * state.pagination.pageSize,
};
fetchTacks(options)
.then((response) =>
setState({
...state,
tracks: response.data.value,
pagination: { ...state.pagination, currentPage: pageNumber },
})
)
.catch(handleError)
.finally(() => setLoading(false));
};
const deleteTrack = (ID) => {
setState({
...state,
tracks: state.tracks.filter(({ ID: curID }) => curID !== ID),
});
};
const renderTracks = (tracks) => {
const isEmployee = requireEmployee(user);
const TrackComponent = isEmployee ? ManagedTrack : Track;
return tracks.map((track) => {
const isAlreadyOrdered = !isEmployee && track.alreadyOrdered;
const onDeleteTrack = isEmployee && ((ID) => deleteTrack(ID));
return (
<Col key={`track-col${track.ID}`} className="gutter-row" span={8}>
<TrackComponent
initialTrack={track}
onDeleteTrack={onDeleteTrack}
isAlreadyOrdered={isAlreadyOrdered}
/>
</Col>
);
});
};
const trackElements = renderTracks(state.tracks);
const genreElements = renderGenres(state.genres);
return (
<>
<div
style={{
display: 'flex',
alignItems: 'start',
maxWidth: 600,
paddingBottom: 10,
}}
>
<Select
mode="multiple"
allowClear
style={{ marginRight: 10, borderRadius: 6 }}
placeholder="Genres"
onChange={(value) => onSelectChange(value)}
>
{genreElements}
</Select>
<Search
style={{
borderRadius: 6,
}}
placeholder="Search tracks"
size="large"
onSearch={onSearch}
onChange={onSearchChange}
/>
</div>
<div>
<Row gutter={[{ xs: 8, sm: 16, md: 24, lg: 32 }, 24]}>{trackElements}</Row>
</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Pagination
showSizeChanger={false}
defaultCurrent={1}
total={state.pagination.totalItems}
pageSize={state.pagination.pageSize}
onChange={onChangePage}
/>
</div>
</>
);
};
export default TracksContainer;

View File

@@ -0,0 +1,62 @@
import React, { useEffect } from 'react';
import { Form, Input, Select } from 'antd';
import { useSearch } from '@umijs/hooks';
import { useErrors } from '../../hooks/useErrors';
import { fetchArtistsByName } from '../../api/calls';
const REQUIRED = [
{
required: true,
message: 'This filed is required!',
},
];
const ARTISTS_LIMIT = 10;
const getArtists = function (value) {
return fetchArtistsByName(value, ARTISTS_LIMIT)
.then((response) => response.data.value)
.catch(this.handleError);
};
const AddAlbumForm = () => {
const { handleError } = useErrors();
const {
data: artists,
loading: isArtistsLoading,
onChange: onChangeArtistInput,
cancel: onArtistCancel,
} = useSearch(getArtists.bind({ handleError }));
useEffect(() => {
onChangeArtistInput();
}, []);
return (
<>
<h3>Add album</h3>
<Form.Item label="Name" name="name" rules={REQUIRED}>
<Input />
</Form.Item>
<Form.Item label="Artist" name="artistID" rules={REQUIRED}>
<Select
showSearch
placeholder="Select artist"
filterOption={false}
onSearch={onChangeArtistInput}
loading={isArtistsLoading}
onBlur={onArtistCancel}
style={{ width: 300 }}
>
{artists &&
artists.map((artist) => (
<Select.Option key={artist.name} value={artist.ID}>
{artist.name}
</Select.Option>
))}
</Select>
</Form.Item>
</>
);
};
export { AddAlbumForm };

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { Form, Input } from 'antd';
const REQUIRED = [
{
required: true,
message: 'This filed is required!',
},
];
const AddArtistForm = () => {
return (
<>
<h3>Add artist</h3>
<Form.Item label="Name" name="name" rules={REQUIRED}>
<Input />
</Form.Item>
</>
);
};
export { AddArtistForm };

View File

@@ -0,0 +1,96 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Form, Input, Select, InputNumber } from 'antd';
import { head } from 'lodash';
import { useSearch } from '@umijs/hooks';
import { useAppState } from '../../hooks/useAppState';
import { fetchAlbumsByName, fetchGenres } from '../../api/calls';
import { useErrors } from '../../hooks/useErrors';
const ALBUMS_LIMIT = 10;
const REQUIRED = [
{
required: true,
message: 'This filed is required!',
},
];
function getAlbums(value) {
return fetchAlbumsByName(value, ALBUMS_LIMIT)
.then((response) => response.data.value)
.catch(this.handleError);
}
const TrackForm = ({ initialAlbumTitle }) => {
const { handleError } = useErrors();
const {
data: albums,
loading: isAlbumsLoading,
onChange: onChangeAlbumInput,
cancel: onAlbumCancel,
} = useSearch(getAlbums.bind({ handleError }));
const { setLoading } = useAppState();
const [genres, setGenres] = useState([]);
useEffect(() => {
setLoading(true);
Promise.all([fetchGenres(), onChangeAlbumInput(initialAlbumTitle)])
.then((responses) => setGenres(head(responses).data.value))
.catch(handleError)
.finally(() => setLoading(false));
}, []);
return (
<div>
<Form.Item label="Name" name="name" rules={REQUIRED}>
<Input />
</Form.Item>
<Form.Item label="Composer" name="composer" rules={REQUIRED}>
<Input />
</Form.Item>
<Form.Item label="Album" name="albumID" rules={REQUIRED}>
<Select
showSearch
placeholder="Select album"
filterOption={false}
onSearch={onChangeAlbumInput}
loading={isAlbumsLoading}
onBlur={onAlbumCancel}
>
{albums &&
albums.map((album) => (
<Select.Option key={album.title} value={album.ID}>
{album.title}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="Genre" name="genreID" rules={REQUIRED}>
<Select showSearch placeholder="Select genre" filterOption={false}>
{genres &&
genres.map((genre) => (
<Select.Option key={genre.name} value={genre.ID}>
{genre.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="Unit price" name="unitPrice" precision={2} rules={REQUIRED}>
<InputNumber
precision={2}
decimalSeparator="."
parser={(value) => value.replace(/\$\s?|(,*)/g, '')}
/>
</Form.Item>
</div>
);
};
TrackForm.propTypes = {
initialAlbumTitle: PropTypes.string,
};
TrackForm.defaultProps = {
initialAlbumTitle: undefined,
};
export { TrackForm };

View File

@@ -0,0 +1,44 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Modal, message } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { deleteTrack } from '../../api/calls';
import { useErrors } from '../../hooks/useErrors';
import { MESSAGE_TIMEOUT } from '../../util/constants';
const DeleteAction = ({ ID, onDeleteTrack }) => {
const [modalVisible, setModalVisible] = useState(false);
const { handleError } = useErrors();
const onOk = () => {
setModalVisible(false);
deleteTrack(ID)
.then(() => {
onDeleteTrack();
setModalVisible(false);
message.success('Track successfully deleted!', MESSAGE_TIMEOUT);
})
.catch(handleError);
};
const onCancel = () => setModalVisible(false);
const onOpenModal = () => {
setModalVisible(true);
};
return (
<>
<DeleteOutlined onClick={onOpenModal}>Delete</DeleteOutlined>
<Modal title="Confirm" visible={modalVisible} onOk={onOk} onCancel={onCancel}>
<p>Are You really want to delete this track?</p>
</Modal>
</>
);
};
DeleteAction.propTypes = {
ID: PropTypes.number.isRequired,
onDeleteTrack: PropTypes.func.isRequired,
};
export { DeleteAction };

View File

@@ -0,0 +1,117 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, Form, message } from 'antd';
import { EditOutlined, LoadingOutlined } from '@ant-design/icons';
import { useErrors } from '../../hooks/useErrors';
import { TrackForm } from '../manage-store/TrackForm';
import { updateTrack, getTrack } from '../../api/calls';
import { MESSAGE_TIMEOUT } from '../../util/constants';
const EditAction = ({ ID, name, composer, genre, unitPrice, album, afterTrackUpdate }) => {
const [visible, setVisible] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [updateLoading, setUpdateLoading] = React.useState(false);
const [form] = Form.useForm();
const { handleError } = useErrors();
const onShowModal = () => {
setVisible(true);
};
const afterCloseModal = () => {
setUpdateLoading(true);
getTrack(ID)
.then((response) => {
afterTrackUpdate(response.data);
setUpdateLoading(false);
})
.catch(handleError);
};
const onFinish = (value) => {
setConfirmLoading(true);
updateTrack({
ID,
name: value.name,
composer: value.composer,
album: { ID: value.albumID },
genre: { ID: value.genreID },
unitPrice: value.unitPrice.toString(),
})
.then(() => {
message.success('Track successfully updated!', MESSAGE_TIMEOUT);
setConfirmLoading(false);
setVisible(false);
afterCloseModal();
})
.catch(handleError);
};
const handleOk = () => {
form.submit();
};
const handleCancel = () => {
setVisible(false);
};
return (
<>
{updateLoading ? <LoadingOutlined /> : <EditOutlined onClick={onShowModal} />}
<Modal
title="Edit track"
visible={visible}
confirmLoading={confirmLoading}
onOk={handleOk}
onCancel={handleCancel}
width={600}
footer={[
<Button key="back" onClick={handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" loading={confirmLoading} onClick={handleOk}>
Submit
</Button>,
]}
>
<Form
form={form}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 14,
}}
layout="horizontal"
onFinish={onFinish}
onFinishFailed={() => console.log('Not valid params provided')}
initialValues={{
name,
composer,
genreID: genre.ID,
albumID: album.ID,
unitPrice,
}}
>
<TrackForm initialAlbumTitle={album.title} />
</Form>
</Modal>
</>
);
};
EditAction.propTypes = {
ID: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
composer: PropTypes.string,
genre: PropTypes.object.isRequired,
unitPrice: PropTypes.number.isRequired,
album: PropTypes.object.isRequired,
afterTrackUpdate: PropTypes.func.isRequired,
};
EditAction.defaultProps = {
composer: undefined,
};
export { EditAction };

View File

@@ -0,0 +1,7 @@
span > span.anticon.anticon-delete:hover {
color: #ff4d4f;
}
.card-element {
transition: opacity 0.5s ease-in-out;
}

View File

@@ -0,0 +1,48 @@
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();
const [track, setTrack] = useState(initialTrack);
return (
<div className="card-element" ref={trackElement}>
<Card
actions={[
<DeleteAction
ID={track.ID}
onDeleteTrack={() => {
trackElement.current.style.opacity = 0;
setTimeout(() => onDeleteTrack(track.ID), 500);
}}
/>,
<EditAction
ID={track.ID}
name={track.name}
composer={track.composer}
album={track.album}
genre={track.genre}
unitPrice={track.unitPrice}
afterTrackUpdate={(value) => setTrack(value)}
/>,
]}
title={track.name}
bordered={false}
>
<TrackCardBody track={track} />
</Card>
</div>
);
};
ManagedTrack.propTypes = {
initialTrack: PropTypes.object.isRequired,
onDeleteTrack: PropTypes.func.isRequired,
};
export { ManagedTrack };

View File

@@ -0,0 +1,63 @@
import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { Card, Button } from 'antd';
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
import { useAppState } from '../../hooks/useAppState';
import { TrackCardBody } from './TrackCardBody';
const Track = ({ initialTrack, isAlreadyOrdered }) => {
const trackElement = useRef();
const { setInvoicedItems, invoicedItems } = useAppState();
const [isJustInvoiced, setIsJustInvoiced] = useState(
invoicedItems.find((curTrack) => curTrack.ID === initialTrack.ID)
);
const onChangedStatus = () => {
const newIsJustInvoiced = !isJustInvoiced;
if (newIsJustInvoiced) {
setInvoicedItems([
...invoicedItems,
{
ID: initialTrack.ID,
name: initialTrack.name,
artist: initialTrack.album.artist.name,
albumTitle: initialTrack.album.title,
unitPrice: initialTrack.unitPrice,
},
]);
} else {
setInvoicedItems(invoicedItems.filter(({ ID: curID }) => curID !== initialTrack.ID));
}
setIsJustInvoiced(newIsJustInvoiced);
};
return (
<div className="card-element" ref={trackElement}>
<Card
actions={[
<>
{!isAlreadyOrdered && (
<Button onClick={onChangedStatus} danger={isJustInvoiced}>
{isJustInvoiced ? <MinusOutlined /> : <PlusOutlined />}
</Button>
)}
</>,
]}
title={initialTrack.name}
bordered={false}
>
<TrackCardBody track={initialTrack} />
</Card>
</div>
);
};
Track.propTypes = {
initialTrack: PropTypes.object.isRequired,
isAlreadyOrdered: PropTypes.bool,
};
Track.defaultProps = {
isAlreadyOrdered: undefined,
};
export { Track };

View File

@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
const TrackCardBody = ({ track }) => {
return (
<>
<div>
Artist:
<span style={{ fontWeight: 600 }}>{track.album.artist.name}</span>
</div>
<div>
Album:
<span style={{ fontWeight: 600 }}>{track.album.title}</span>
</div>
<div>
Genre:
<span style={{ fontWeight: 600 }}>{track.genre.name}</span>
</div>
<div>
{track.composer && (
<span>
Compositor:
<span style={{ fontWeight: 600 }}>{track.composer}</span>
</span>
)}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
Price:
<span style={{ fontWeight: 600 }}>{track.unitPrice}</span>
</span>
</div>
</>
);
};
TrackCardBody.propTypes = {
track: PropTypes.object.isRequired,
};
export { TrackCardBody };

View File

@@ -0,0 +1,66 @@
import React, { useMemo, createContext, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { getUserFromLS, getLocaleFromLS, setUserToLS } from '../util/localStorageService';
import { changeUserDefaults } from '../api/axiosInstance';
import { emitter } from '../util/EventEmitter';
const globalContext = {
error: {},
loading: true,
user: {
ID: undefined,
roles: [],
email: undefined,
accessToken: undefined,
refreshToken: undefined,
},
locale: undefined,
invoicedItems: [],
notifications: [],
};
const AppStateContext = createContext(globalContext);
const AppStateContextProvider = ({ children }) => {
const [invoicedItems, setInvoicedItems] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState({});
const [user, setUser] = useState(getUserFromLS());
const [locale, setLocale] = useState(getLocaleFromLS());
useEffect(() => {
const updateUser = (newUser) => {
console.log('USER_UPDATE WAS TRIGGERED');
changeUserDefaults(newUser);
setUserToLS(newUser);
setUser(newUser);
};
emitter.on('UPDATE_USER', updateUser);
return () => {
emitter.removeListener('UPDATE_USER', updateUser);
};
}, []);
const value = useMemo(
() => ({
error,
loading,
invoicedItems,
user,
locale,
setLoading,
setError,
setInvoicedItems,
setUser,
setLocale,
}),
[locale, user, loading, error, invoicedItems]
);
return <AppStateContext.Provider value={value}>{children}</AppStateContext.Provider>;
};
AppStateContextProvider.propTypes = {
children: PropTypes.element.isRequired,
};
export { AppStateContextProvider, AppStateContext };

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { Redirect } from 'react-router-dom';
import { useAppState } from '../hooks/useAppState';
const withRestrictions = (Component, isUserMeetRestrictions) => {
return (props) => {
const { user, invoicedItems } = useAppState();
return isUserMeetRestrictions({ user, invoicedItems }) ? (
<Component {...props} />
) : (
<Redirect exact to="/error" />
);
};
};
export { withRestrictions };

View File

@@ -0,0 +1,22 @@
import { useEffect } from 'react';
function useAbortableEffect(effect, dependencies) {
const status = {}; // mutable status object
useEffect(() => {
status.aborted = false;
// pass the mutable object to the effect callback
// store the returned value for cleanup
const cleanUpFn = effect(status);
return () => {
// mutate the object to signal the consumer
// this effect is cleaning up
status.aborted = true;
if (typeof cleanUpFn === 'function') {
// run the cleanup function
cleanUpFn();
}
};
}, [...dependencies]);
}
export { useAbortableEffect };

View File

@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { AppStateContext } from '../contexts/AppStateContext';
const useAppState = () => useContext(AppStateContext);
export { useAppState };

View File

@@ -0,0 +1,34 @@
import { useHistory } from 'react-router-dom';
import { useAppState } from './useAppState';
const useErrors = () => {
const history = useHistory();
const { setError } = useAppState();
const handleError = (error) => {
console.error('Error', error);
if (error.response) {
const { status, statusText, data } = error.response;
setError({
status,
statusText,
message: data.error ? data.error.message : data,
});
} else {
setError({
status: '',
statusText: 'Error',
message: 'Something went wrong. Seems like request is too long',
});
}
history.push('/error');
};
return {
handleError,
};
};
export { useErrors };

View File

@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
// serviceWorker.unregister();

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@@ -0,0 +1,5 @@
import EventEmitter from 'events';
const emitter = new EventEmitter();
export { emitter };

View File

@@ -0,0 +1,7 @@
export const AVAILABLE_LOCALES = ['en', 'fr', 'de'];
export const MESSAGE_TIMEOUT = 2;
export const requireEmployee = (user) => !!user && user.roles.includes('employee');
export const requireCustomer = (user) => !!user && user.roles.includes('customer');

View File

@@ -0,0 +1,36 @@
import { isValidUser } from './validateUser';
import { AVAILABLE_LOCALES } from './constants';
const setUserToLS = (user) => {
if (user) {
localStorage.setItem('user', JSON.stringify(user));
} else {
localStorage.removeItem('user');
}
};
const getUserFromLS = () => {
let userFromLS;
try {
userFromLS = JSON.parse(localStorage.getItem('user'));
if (isValidUser(userFromLS)) {
return userFromLS;
}
} catch (e) {
console.error('User from local storage are not valid');
}
return undefined;
};
const getLocaleFromLS = () => {
const localeFromLS = localStorage.getItem('locale');
return localeFromLS && localeFromLS !== 'undefined' && AVAILABLE_LOCALES.includes(localeFromLS)
? localeFromLS
: 'en';
};
const setLocaleToLS = (locale) => {
localStorage.setItem('locale', locale);
};
export { setLocaleToLS, getLocaleFromLS, getUserFromLS, setUserToLS };

View File

@@ -0,0 +1,18 @@
import { isArray, isEmpty, isString, isNumber } from 'lodash';
const CUSTOMER_ROLE = 'customer';
const EMPLOYEE_ROLE = 'employee';
const isValidUser = (user) => {
return (
!isEmpty(user) &&
isNumber(user.ID) &&
isArray(user.roles) &&
!!user.roles.some((role) => role === CUSTOMER_ROLE || role === EMPLOYEE_ROLE) &&
isString(user.email) &&
isString(user.accessToken) &&
isString(user.refreshToken)
);
};
export { isValidUser };

View File

@@ -0,0 +1,33 @@
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
module.exports = {
plugins: [
new CleanWebpackPlugin({ dangerouslyAllowCleanPatternsOutsideProject: true }),
new HtmlWebpackPlugin({
template: path.join(__dirname, '../public/index.html'),
filename: path.join(__dirname, '../../build/index.html'),
publicPath: '/static/', // for js bundles path
}),
new InterpolateHtmlPlugin(HtmlWebpackPlugin, {
PUBLIC_URL: '',
}),
new CopyPlugin({
patterns: [
{
from: path.join(__dirname, '../public'),
to: path.join(__dirname, '../../build'),
globOptions: {
dot: true,
ignore: ['**/index.html'],
},
},
],
}),
new webpack.ProgressPlugin(),
],
};

View File

@@ -0,0 +1,19 @@
module.exports = {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
{
test: /\.(png|jpg)$/,
use: [{ loader: 'url-loader' }],
},
],
};

View File

@@ -0,0 +1,68 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
entry: {
app: './src/index.jsx',
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
hot: true,
port: 3000,
compress: true, // compress files to gzip to increase download speed
disableHostCheck: false, // by default true, it is not recomended,
// because it makes app vulnerable to DNS rebinding attacks
open: true, // open the browser after server had been started
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['react-refresh/babel'].filter(Boolean),
},
},
},
{
test: /\.(png|jpg)$/,
use: [{ loader: 'url-loader' }],
},
{
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
},
],
},
plugins: [
new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
new HtmlWebpackPlugin({
template: path.join(__dirname, '../public/index.html'),
}),
new InterpolateHtmlPlugin(HtmlWebpackPlugin, {
PUBLIC_URL: '',
}),
new webpack.DefinePlugin({
'process.env.SERVICE_URL': JSON.stringify('http://localhost:4004/'),
}),
new webpack.ProgressPlugin(),
new webpack.HotModuleReplacementPlugin(), // for hot module replacement option of devServer
new ReactRefreshWebpackPlugin(),
].filter(Boolean),
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: { extensions: ['*', '.js', '.jsx'] },
};

View File

@@ -0,0 +1,25 @@
const path = require('path');
module.exports = {
entry: {
app: './src/index.jsx', // Bundle with our code
react: ['react', 'react-dom'],
lodash: ['lodash'],
moment: ['moment'],
events: ['events'],
axios: ['axios'],
antd: ['antd'],
},
output: {
// [name] - name of the entry (bundle),
// [checksum] or [hash] - to cache different bundles
// from update when developing (doing changes in the files)
filename: '[name].[fullhash].js',
// in this folder path bundles will be placed
path: path.resolve(__dirname, '../../build/static'),
// where you uploaded your bundled files. (Relative to server root)
// needs for react-router-dom
publicPath: '/static/',
},
resolve: { extensions: ['*', '.js', '.jsx'] },
};

View File

@@ -0,0 +1,25 @@
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const { rules } = require('./common-rules');
const { plugins } = require('./common-plugins');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
plugins: [
...plugins,
new webpack.DefinePlugin({
'process.env.SERVICE_URL': JSON.stringify('http://localhost:4004/'),
}),
],
module: {
rules: [
...rules,
{
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
},
],
},
});

View File

@@ -0,0 +1,40 @@
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const common = require('./webpack.common.js');
const { rules } = require('./common-rules');
const { plugins } = require('./common-plugins');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
plugins: [
...plugins,
new webpack.DefinePlugin({
'process.env.SERVICE_URL': JSON.stringify('api/'),
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
optimization: {
splitChunks: {
// To split up js code to different bundles.
chunks: 'all', // Now bundle with our code will be cleaned up
}, // from vendors imports (2mb ~> 100kb)
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()], // to minimize file size
},
module: {
rules: [
...rules,
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
});

View File

@@ -0,0 +1,349 @@
ID,title,artist_ID
1,For Those About To Rock We Salute You,1
2,Balls to the Wall,2
3,Restless and Wild,2
4,Let There Be Rock,1
5,Big Ones,3
6,Jagged Little Pill,4
7,Facelift,5
8,Warner 25 Anos,6
9,Plays Metallica By Four Cellos,7
10,Audioslave,8
11,Out Of Exile,8
12,BackBeat Soundtrack,9
13,The Best Of Billy Cobham,10
14,Alcohol Fueled Brewtality Live! [Disc 1],11
15,Alcohol Fueled Brewtality Live! [Disc 2],11
16,Black Sabbath,12
17,Black Sabbath Vol. 4 (Remaster),12
18,Body Count,13
19,Chemical Wedding,14
20,The Best Of Buddy Guy - The Millenium Collection,15
21,Prenda Minha,16
22,Sozinho Remix Ao Vivo,16
23,Minha Historia,17
24,Afrociberdelia,18
25,Da Lama Ao Caos,18
26,Acústico MTV [Live],19
27,Cidade Negra - Hits,19
28,Na Pista,20
29,Axé Bahia 2001,21
30,BBC Sessions [Disc 1] [Live],22
31,Bongo Fury,23
32,Carnaval 2001,21
33,Chill: Brazil (Disc 1),24
34,Chill: Brazil (Disc 2),6
35,Garage Inc. (Disc 1),50
36,Greatest Hits II,51
37,Greatest Kiss,52
38,Heart of the Night,53
39,International Superhits,54
40,Into The Light,55
41,Meus Momentos,56
42,Minha História,57
43,MK III The Final Concerts [Disc 1],58
44,Physical Graffiti [Disc 1],22
45,Sambas De Enredo 2001,21
46,Supernatural,59
47,The Best of Ed Motta,37
48,The Essential Miles Davis [Disc 1],68
49,The Essential Miles Davis [Disc 2],68
50,The Final Concerts (Disc 2),58
51,Up An' Atom,69
52,Vinícius De Moraes - Sem Limite,70
53,Vozes do MPB,21
54,"Chronicle, Vol. 1",76
55,"Chronicle, Vol. 2",76
56,Cássia Eller - Coleção Sem Limite [Disc 2],77
57,Cássia Eller - Sem Limite [Disc 1],77
58,Come Taste The Band,58
59,Deep Purple In Rock,58
60,Fireball,58
61,Knocking at Your Back Door: The Best Of Deep Purple in the 80's,58
62,Machine Head,58
63,Purpendicular,58
64,Slaves And Masters,58
65,Stormbringer,58
66,The Battle Rages On,58
67,Vault: Def Leppard's Greatest Hits,78
68,Outbreak,79
69,Djavan Ao Vivo - Vol. 02,80
70,Djavan Ao Vivo - Vol. 1,80
71,Elis Regina-Minha História,41
72,The Cream Of Clapton,81
73,Unplugged,81
74,Album Of The Year,82
75,Angel Dust,82
76,King For A Day Fool For A Lifetime,82
77,The Real Thing,82
78,Deixa Entrar,83
79,In Your Honor [Disc 1],84
80,In Your Honor [Disc 2],84
81,One By One,84
82,The Colour And The Shape,84
83,My Way: The Best Of Frank Sinatra [Disc 1],85
84,Roda De Funk,86
85,As Canções de Eu Tu Eles,27
86,Quanta Gente Veio Ver (Live),27
87,Quanta Gente Veio ver--Bônus De Carnaval,27
88,Faceless,87
89,American Idiot,54
90,Appetite for Destruction,88
91,Use Your Illusion I,88
92,Use Your Illusion II,88
93,Blue Moods,89
94,A Matter of Life and Death,90
95,A Real Dead One,90
96,A Real Live One,90
97,Brave New World,90
98,Dance Of Death,90
99,Fear Of The Dark,90
100,Iron Maiden,90
101,Killers,90
102,Live After Death,90
103,Live At Donington 1992 (Disc 1),90
104,Live At Donington 1992 (Disc 2),90
105,No Prayer For The Dying,90
106,Piece Of Mind,90
107,Powerslave,90
108,Rock In Rio [CD1],90
109,Rock In Rio [CD2],90
110,Seventh Son of a Seventh Son,90
111,Somewhere in Time,90
112,The Number of The Beast,90
113,The X Factor,90
114,Virtual XI,90
115,Sex Machine,91
116,Emergency On Planet Earth,92
117,Synkronized,92
118,The Return Of The Space Cowboy,92
119,Get Born,93
120,Are You Experienced?,94
121,Surfing with the Alien (Remastered),95
122,Jorge Ben Jor 25 Anos,46
123,Jota Quest-1995,96
124,Cafezinho,97
125,Living After Midnight,98
126,Unplugged [Live],52
127,BBC Sessions [Disc 2] [Live],22
128,Coda,22
129,Houses Of The Holy,22
130,In Through The Out Door,22
131,IV,22
132,Led Zeppelin I,22
133,Led Zeppelin II,22
134,Led Zeppelin III,22
135,Physical Graffiti [Disc 2],22
136,Presence,22
137,The Song Remains The Same (Disc 1),22
138,The Song Remains The Same (Disc 2),22
139,A TempestadeTempestade Ou O Livro Dos Dias,99
140,Mais Do Mesmo,99
141,Greatest Hits,100
142,Lulu Santos - RCA 100 Anos De Música - Álbum 01,101
143,Lulu Santos - RCA 100 Anos De Música - Álbum 02,101
144,Misplaced Childhood,102
145,Barulhinho Bom,103
146,Seek And Shall Find: More Of The Best (1963-1981),104
147,The Best Of Men At Work,105
148,Black Album,50
149,Garage Inc. (Disc 2),50
150,Kill 'Em All,50
151,Load,50
152,Master Of Puppets,50
153,ReLoad,50
154,Ride The Lightning,50
155,St. Anger,50
156,...And Justice For All,50
157,Miles Ahead,68
158,Milton Nascimento Ao Vivo,42
159,Minas,42
160,Ace Of Spades,106
161,Demorou...,108
162,Motley Crue Greatest Hits,109
163,From The Muddy Banks Of The Wishkah [Live],110
164,Nevermind,110
165,Compositores,111
166,Olodum,112
167,Acústico MTV,113
168,Arquivo II,113
169,Arquivo Os Paralamas Do Sucesso,113
170,Bark at the Moon (Remastered),114
171,Blizzard of Ozz,114
172,Diary of a Madman (Remastered),114
173,No More Tears (Remastered),114
174,Tribute,114
175,Walking Into Clarksdale,115
176,Original Soundtracks 1,116
177,The Beast Live,117
178,Live On Two Legs [Live],118
179,Pearl Jam,118
180,Riot Act,118
181,Ten,118
182,Vs.,118
183,Dark Side Of The Moon,120
184,Os Cães Ladram Mas A Caravana Não Pára,121
185,Greatest Hits I,51
186,News Of The World,51
187,Out Of Time,122
188,Green,124
189,New Adventures In Hi-Fi,124
190,The Best Of R.E.M.: The IRS Years,124
191,Cesta Básica,125
192,Raul Seixas,126
193,Blood Sugar Sex Magik,127
194,By The Way,127
195,Californication,127
196,Retrospective I (1974-1980),128
197,Santana - As Years Go By,59
198,Santana Live,59
199,Maquinarama,130
200,O Samba Poconé,130
201,Judas 0: B-Sides and Rarities,131
202,Rotten Apples: Greatest Hits,131
203,A-Sides,132
204,Morning Dance,53
205,In Step,133
206,Core,134
207,Mezmerize,135
208,[1997] Black Light Syndrome,136
209,Live [Disc 1],137
210,Live [Disc 2],137
211,The Singles,138
212,Beyond Good And Evil,139
213,"Pure Cult: The Best Of The Cult (For Rockers, Ravers, Lovers & Sinners) [UK]",139
214,The Doors,140
215,The Police Greatest Hits,141
216,"Hot Rocks, 1964-1971 (Disc 1)",142
217,No Security,142
218,Voodoo Lounge,142
219,Tangents,143
220,Transmission,143
221,My Generation - The Very Best Of The Who,144
222,Serie Sem Limite (Disc 1),145
223,Serie Sem Limite (Disc 2),145
224,Acústico,146
225,Volume Dois,146
226,Battlestar Galactica: The Story So Far,147
227,"Battlestar Galactica, Season 3",147
228,"Heroes, Season 1",148
229,"Lost, Season 3",149
230,"Lost, Season 1",149
231,"Lost, Season 2",149
232,Achtung Baby,150
233,All That You Can't Leave Behind,150
234,B-Sides 1980-1990,150
235,How To Dismantle An Atomic Bomb,150
236,Pop,150
237,Rattle And Hum,150
238,The Best Of 1980-1990,150
239,War,150
240,Zooropa,150
241,UB40 The Best Of - Volume Two [UK],151
242,Diver Down,152
243,"The Best Of Van Halen, Vol. I",152
244,Van Halen,152
245,Van Halen III,152
246,Contraband,153
247,Vinicius De Moraes,72
248,Ao Vivo [IMPORT],155
249,"The Office, Season 1",156
250,"The Office, Season 2",156
251,"The Office, Season 3",156
252,Un-Led-Ed,157
253,"Battlestar Galactica (Classic), Season 1",158
254,Aquaman,159
255,Instant Karma: The Amnesty International Campaign to Save Darfur,150
256,Speak of the Devil,114
257,20th Century Masters - The Millennium Collection: The Best of Scorpions,179
258,House of Pain,180
259,Radio Brasil (O Som da Jovem Vanguarda) - Seleccao de Henrique Amaro,36
260,Cake: B-Sides and Rarities,196
261,"LOST, Season 4",149
262,Quiet Songs,197
263,Muso Ko,198
264,Realize,199
265,Every Kind of Light,200
266,Duos II,201
267,Worlds,202
268,The Best of Beethoven,203
269,Temple of the Dog,204
270,Carry On,205
271,Revelations,8
272,Adorate Deum: Gregorian Chant from the Proper of the Mass,206
273,Allegri: Miserere,207
274,Pachelbel: Canon & Gigue,208
275,Vivaldi: The Four Seasons,209
276,Bach: Violin Concertos,210
277,Bach: Goldberg Variations,211
278,Bach: The Cello Suites,212
279,Handel: The Messiah (Highlights),213
280,The World of Classical Favourites,214
281,Sir Neville Marriner: A Celebration,215
282,Mozart: Wind Concertos,216
283,Haydn: Symphonies 99 - 104,217
284,Beethoven: Symhonies Nos. 5 & 6,218
285,A Soprano Inspired,219
286,Great Opera Choruses,220
287,Wagner: Favourite Overtures,221
288,"Fauré: Requiem, Ravel: Pavane & Others",222
289,Tchaikovsky: The Nutcracker,223
290,The Last Night of the Proms,224
291,Puccini: Madama Butterfly - Highlights,225
292,"Holst: The Planets, Op. 32 & Vaughan Williams: Fantasies",226
293,Pavarotti's Opera Made Easy,227
294,Great Performances - Barber's Adagio and Other Romantic Favorites for Strings,228
295,Carmina Burana,229
296,"A Copland Celebration, Vol. I",230
297,Bach: Toccata & Fugue in D Minor,231
298,Prokofiev: Symphony No.1,232
299,Scheherazade,233
300,Bach: The Brandenburg Concertos,234
301,Chopin: Piano Concertos Nos. 1 & 2,235
302,Mascagni: Cavalleria Rusticana,236
303,Sibelius: Finlandia,237
304,Beethoven Piano Sonatas: Moonlight & Pastorale,238
305,Great Recordings of the Century - Mahler: Das Lied von der Erde,240
306,Elgar: Cello Concerto & Vaughan Williams: Fantasias,241
307,"Adams, John: The Chairman Dances",242
308,"Tchaikovsky: 1812 Festival Overture, Op.49, Capriccio Italien & Beethoven: Wellington's Victory",243
309,Palestrina: Missa Papae Marcelli & Allegri: Miserere,244
310,Prokofiev: Romeo & Juliet,245
311,Strauss: Waltzes,226
312,Berlioz: Symphonie Fantastique,245
313,Bizet: Carmen Highlights,246
314,English Renaissance,247
315,Handel: Music for the Royal Fireworks (Original Version 1749),208
316,Grieg: Peer Gynt Suites & Sibelius: Pelléas et Mélisande,248
317,Mozart Gala: Famous Arias,249
318,SCRIABIN: Vers la flamme,250
319,Armada: Music from the Courts of England and Spain,251
320,Mozart: Symphonies Nos. 40 & 41,248
321,Back to Black,252
322,Frank,252
323,Carried to Dust (Bonus Track Version),253
324,Beethoven: Symphony No. 6 'Pastoral' Etc.,254
325,Bartok: Violin & Viola Concertos,255
326,Mendelssohn: A Midsummer Night's Dream,256
327,Bach: Orchestral Suites Nos. 1 - 4,257
328,"Charpentier: Divertissements, Airs & Concerts",258
329,South American Getaway,259
330,Górecki: Symphony No. 3,260
331,Purcell: The Fairy Queen,261
332,The Ultimate Relexation Album,262
333,Purcell: Music for the Queen Mary,263
334,Weill: The Seven Deadly Sins,264
335,"J.S. Bach: Chaconne, Suite in E Minor, Partita in E Major & Prelude, Fugue and Allegro",265
336,Prokofiev: Symphony No.5 & Stravinksy: Le Sacre Du Printemps,248
337,"Szymanowski: Piano Works, Vol. 1",266
338,Nielsen: The Six Symphonies,267
339,Great Recordings of the Century: Paganini's 24 Caprices,268
340,Liszt - 12 Études D'Execution Transcendante,269
341,"Great Recordings of the Century - Shubert: Schwanengesang, 4 Lieder",270
342,"Locatelli: Concertos for Violin, Strings and Continuo, Vol. 3",271
343,Respighi:Pines of Rome,226
344,Schubert: The Late String Quartets & String Quintet (3 CD's),272
345,Monteverdi: L'Orfeo,273
346,Mozart: Chamber Music,274
347,Koyaanisqatsi (Soundtrack from the Motion Picture),275
348,asdaasdasdsd,3
1 ID title artist_ID
2 1 For Those About To Rock We Salute You 1
3 2 Balls to the Wall 2
4 3 Restless and Wild 2
5 4 Let There Be Rock 1
6 5 Big Ones 3
7 6 Jagged Little Pill 4
8 7 Facelift 5
9 8 Warner 25 Anos 6
10 9 Plays Metallica By Four Cellos 7
11 10 Audioslave 8
12 11 Out Of Exile 8
13 12 BackBeat Soundtrack 9
14 13 The Best Of Billy Cobham 10
15 14 Alcohol Fueled Brewtality Live! [Disc 1] 11
16 15 Alcohol Fueled Brewtality Live! [Disc 2] 11
17 16 Black Sabbath 12
18 17 Black Sabbath Vol. 4 (Remaster) 12
19 18 Body Count 13
20 19 Chemical Wedding 14
21 20 The Best Of Buddy Guy - The Millenium Collection 15
22 21 Prenda Minha 16
23 22 Sozinho Remix Ao Vivo 16
24 23 Minha Historia 17
25 24 Afrociberdelia 18
26 25 Da Lama Ao Caos 18
27 26 Acústico MTV [Live] 19
28 27 Cidade Negra - Hits 19
29 28 Na Pista 20
30 29 Axé Bahia 2001 21
31 30 BBC Sessions [Disc 1] [Live] 22
32 31 Bongo Fury 23
33 32 Carnaval 2001 21
34 33 Chill: Brazil (Disc 1) 24
35 34 Chill: Brazil (Disc 2) 6
36 35 Garage Inc. (Disc 1) 50
37 36 Greatest Hits II 51
38 37 Greatest Kiss 52
39 38 Heart of the Night 53
40 39 International Superhits 54
41 40 Into The Light 55
42 41 Meus Momentos 56
43 42 Minha História 57
44 43 MK III The Final Concerts [Disc 1] 58
45 44 Physical Graffiti [Disc 1] 22
46 45 Sambas De Enredo 2001 21
47 46 Supernatural 59
48 47 The Best of Ed Motta 37
49 48 The Essential Miles Davis [Disc 1] 68
50 49 The Essential Miles Davis [Disc 2] 68
51 50 The Final Concerts (Disc 2) 58
52 51 Up An' Atom 69
53 52 Vinícius De Moraes - Sem Limite 70
54 53 Vozes do MPB 21
55 54 Chronicle, Vol. 1 76
56 55 Chronicle, Vol. 2 76
57 56 Cássia Eller - Coleção Sem Limite [Disc 2] 77
58 57 Cássia Eller - Sem Limite [Disc 1] 77
59 58 Come Taste The Band 58
60 59 Deep Purple In Rock 58
61 60 Fireball 58
62 61 Knocking at Your Back Door: The Best Of Deep Purple in the 80's 58
63 62 Machine Head 58
64 63 Purpendicular 58
65 64 Slaves And Masters 58
66 65 Stormbringer 58
67 66 The Battle Rages On 58
68 67 Vault: Def Leppard's Greatest Hits 78
69 68 Outbreak 79
70 69 Djavan Ao Vivo - Vol. 02 80
71 70 Djavan Ao Vivo - Vol. 1 80
72 71 Elis Regina-Minha História 41
73 72 The Cream Of Clapton 81
74 73 Unplugged 81
75 74 Album Of The Year 82
76 75 Angel Dust 82
77 76 King For A Day Fool For A Lifetime 82
78 77 The Real Thing 82
79 78 Deixa Entrar 83
80 79 In Your Honor [Disc 1] 84
81 80 In Your Honor [Disc 2] 84
82 81 One By One 84
83 82 The Colour And The Shape 84
84 83 My Way: The Best Of Frank Sinatra [Disc 1] 85
85 84 Roda De Funk 86
86 85 As Canções de Eu Tu Eles 27
87 86 Quanta Gente Veio Ver (Live) 27
88 87 Quanta Gente Veio ver--Bônus De Carnaval 27
89 88 Faceless 87
90 89 American Idiot 54
91 90 Appetite for Destruction 88
92 91 Use Your Illusion I 88
93 92 Use Your Illusion II 88
94 93 Blue Moods 89
95 94 A Matter of Life and Death 90
96 95 A Real Dead One 90
97 96 A Real Live One 90
98 97 Brave New World 90
99 98 Dance Of Death 90
100 99 Fear Of The Dark 90
101 100 Iron Maiden 90
102 101 Killers 90
103 102 Live After Death 90
104 103 Live At Donington 1992 (Disc 1) 90
105 104 Live At Donington 1992 (Disc 2) 90
106 105 No Prayer For The Dying 90
107 106 Piece Of Mind 90
108 107 Powerslave 90
109 108 Rock In Rio [CD1] 90
110 109 Rock In Rio [CD2] 90
111 110 Seventh Son of a Seventh Son 90
112 111 Somewhere in Time 90
113 112 The Number of The Beast 90
114 113 The X Factor 90
115 114 Virtual XI 90
116 115 Sex Machine 91
117 116 Emergency On Planet Earth 92
118 117 Synkronized 92
119 118 The Return Of The Space Cowboy 92
120 119 Get Born 93
121 120 Are You Experienced? 94
122 121 Surfing with the Alien (Remastered) 95
123 122 Jorge Ben Jor 25 Anos 46
124 123 Jota Quest-1995 96
125 124 Cafezinho 97
126 125 Living After Midnight 98
127 126 Unplugged [Live] 52
128 127 BBC Sessions [Disc 2] [Live] 22
129 128 Coda 22
130 129 Houses Of The Holy 22
131 130 In Through The Out Door 22
132 131 IV 22
133 132 Led Zeppelin I 22
134 133 Led Zeppelin II 22
135 134 Led Zeppelin III 22
136 135 Physical Graffiti [Disc 2] 22
137 136 Presence 22
138 137 The Song Remains The Same (Disc 1) 22
139 138 The Song Remains The Same (Disc 2) 22
140 139 A TempestadeTempestade Ou O Livro Dos Dias 99
141 140 Mais Do Mesmo 99
142 141 Greatest Hits 100
143 142 Lulu Santos - RCA 100 Anos De Música - Álbum 01 101
144 143 Lulu Santos - RCA 100 Anos De Música - Álbum 02 101
145 144 Misplaced Childhood 102
146 145 Barulhinho Bom 103
147 146 Seek And Shall Find: More Of The Best (1963-1981) 104
148 147 The Best Of Men At Work 105
149 148 Black Album 50
150 149 Garage Inc. (Disc 2) 50
151 150 Kill 'Em All 50
152 151 Load 50
153 152 Master Of Puppets 50
154 153 ReLoad 50
155 154 Ride The Lightning 50
156 155 St. Anger 50
157 156 ...And Justice For All 50
158 157 Miles Ahead 68
159 158 Milton Nascimento Ao Vivo 42
160 159 Minas 42
161 160 Ace Of Spades 106
162 161 Demorou... 108
163 162 Motley Crue Greatest Hits 109
164 163 From The Muddy Banks Of The Wishkah [Live] 110
165 164 Nevermind 110
166 165 Compositores 111
167 166 Olodum 112
168 167 Acústico MTV 113
169 168 Arquivo II 113
170 169 Arquivo Os Paralamas Do Sucesso 113
171 170 Bark at the Moon (Remastered) 114
172 171 Blizzard of Ozz 114
173 172 Diary of a Madman (Remastered) 114
174 173 No More Tears (Remastered) 114
175 174 Tribute 114
176 175 Walking Into Clarksdale 115
177 176 Original Soundtracks 1 116
178 177 The Beast Live 117
179 178 Live On Two Legs [Live] 118
180 179 Pearl Jam 118
181 180 Riot Act 118
182 181 Ten 118
183 182 Vs. 118
184 183 Dark Side Of The Moon 120
185 184 Os Cães Ladram Mas A Caravana Não Pára 121
186 185 Greatest Hits I 51
187 186 News Of The World 51
188 187 Out Of Time 122
189 188 Green 124
190 189 New Adventures In Hi-Fi 124
191 190 The Best Of R.E.M.: The IRS Years 124
192 191 Cesta Básica 125
193 192 Raul Seixas 126
194 193 Blood Sugar Sex Magik 127
195 194 By The Way 127
196 195 Californication 127
197 196 Retrospective I (1974-1980) 128
198 197 Santana - As Years Go By 59
199 198 Santana Live 59
200 199 Maquinarama 130
201 200 O Samba Poconé 130
202 201 Judas 0: B-Sides and Rarities 131
203 202 Rotten Apples: Greatest Hits 131
204 203 A-Sides 132
205 204 Morning Dance 53
206 205 In Step 133
207 206 Core 134
208 207 Mezmerize 135
209 208 [1997] Black Light Syndrome 136
210 209 Live [Disc 1] 137
211 210 Live [Disc 2] 137
212 211 The Singles 138
213 212 Beyond Good And Evil 139
214 213 Pure Cult: The Best Of The Cult (For Rockers, Ravers, Lovers & Sinners) [UK] 139
215 214 The Doors 140
216 215 The Police Greatest Hits 141
217 216 Hot Rocks, 1964-1971 (Disc 1) 142
218 217 No Security 142
219 218 Voodoo Lounge 142
220 219 Tangents 143
221 220 Transmission 143
222 221 My Generation - The Very Best Of The Who 144
223 222 Serie Sem Limite (Disc 1) 145
224 223 Serie Sem Limite (Disc 2) 145
225 224 Acústico 146
226 225 Volume Dois 146
227 226 Battlestar Galactica: The Story So Far 147
228 227 Battlestar Galactica, Season 3 147
229 228 Heroes, Season 1 148
230 229 Lost, Season 3 149
231 230 Lost, Season 1 149
232 231 Lost, Season 2 149
233 232 Achtung Baby 150
234 233 All That You Can't Leave Behind 150
235 234 B-Sides 1980-1990 150
236 235 How To Dismantle An Atomic Bomb 150
237 236 Pop 150
238 237 Rattle And Hum 150
239 238 The Best Of 1980-1990 150
240 239 War 150
241 240 Zooropa 150
242 241 UB40 The Best Of - Volume Two [UK] 151
243 242 Diver Down 152
244 243 The Best Of Van Halen, Vol. I 152
245 244 Van Halen 152
246 245 Van Halen III 152
247 246 Contraband 153
248 247 Vinicius De Moraes 72
249 248 Ao Vivo [IMPORT] 155
250 249 The Office, Season 1 156
251 250 The Office, Season 2 156
252 251 The Office, Season 3 156
253 252 Un-Led-Ed 157
254 253 Battlestar Galactica (Classic), Season 1 158
255 254 Aquaman 159
256 255 Instant Karma: The Amnesty International Campaign to Save Darfur 150
257 256 Speak of the Devil 114
258 257 20th Century Masters - The Millennium Collection: The Best of Scorpions 179
259 258 House of Pain 180
260 259 Radio Brasil (O Som da Jovem Vanguarda) - Seleccao de Henrique Amaro 36
261 260 Cake: B-Sides and Rarities 196
262 261 LOST, Season 4 149
263 262 Quiet Songs 197
264 263 Muso Ko 198
265 264 Realize 199
266 265 Every Kind of Light 200
267 266 Duos II 201
268 267 Worlds 202
269 268 The Best of Beethoven 203
270 269 Temple of the Dog 204
271 270 Carry On 205
272 271 Revelations 8
273 272 Adorate Deum: Gregorian Chant from the Proper of the Mass 206
274 273 Allegri: Miserere 207
275 274 Pachelbel: Canon & Gigue 208
276 275 Vivaldi: The Four Seasons 209
277 276 Bach: Violin Concertos 210
278 277 Bach: Goldberg Variations 211
279 278 Bach: The Cello Suites 212
280 279 Handel: The Messiah (Highlights) 213
281 280 The World of Classical Favourites 214
282 281 Sir Neville Marriner: A Celebration 215
283 282 Mozart: Wind Concertos 216
284 283 Haydn: Symphonies 99 - 104 217
285 284 Beethoven: Symhonies Nos. 5 & 6 218
286 285 A Soprano Inspired 219
287 286 Great Opera Choruses 220
288 287 Wagner: Favourite Overtures 221
289 288 Fauré: Requiem, Ravel: Pavane & Others 222
290 289 Tchaikovsky: The Nutcracker 223
291 290 The Last Night of the Proms 224
292 291 Puccini: Madama Butterfly - Highlights 225
293 292 Holst: The Planets, Op. 32 & Vaughan Williams: Fantasies 226
294 293 Pavarotti's Opera Made Easy 227
295 294 Great Performances - Barber's Adagio and Other Romantic Favorites for Strings 228
296 295 Carmina Burana 229
297 296 A Copland Celebration, Vol. I 230
298 297 Bach: Toccata & Fugue in D Minor 231
299 298 Prokofiev: Symphony No.1 232
300 299 Scheherazade 233
301 300 Bach: The Brandenburg Concertos 234
302 301 Chopin: Piano Concertos Nos. 1 & 2 235
303 302 Mascagni: Cavalleria Rusticana 236
304 303 Sibelius: Finlandia 237
305 304 Beethoven Piano Sonatas: Moonlight & Pastorale 238
306 305 Great Recordings of the Century - Mahler: Das Lied von der Erde 240
307 306 Elgar: Cello Concerto & Vaughan Williams: Fantasias 241
308 307 Adams, John: The Chairman Dances 242
309 308 Tchaikovsky: 1812 Festival Overture, Op.49, Capriccio Italien & Beethoven: Wellington's Victory 243
310 309 Palestrina: Missa Papae Marcelli & Allegri: Miserere 244
311 310 Prokofiev: Romeo & Juliet 245
312 311 Strauss: Waltzes 226
313 312 Berlioz: Symphonie Fantastique 245
314 313 Bizet: Carmen Highlights 246
315 314 English Renaissance 247
316 315 Handel: Music for the Royal Fireworks (Original Version 1749) 208
317 316 Grieg: Peer Gynt Suites & Sibelius: Pelléas et Mélisande 248
318 317 Mozart Gala: Famous Arias 249
319 318 SCRIABIN: Vers la flamme 250
320 319 Armada: Music from the Courts of England and Spain 251
321 320 Mozart: Symphonies Nos. 40 & 41 248
322 321 Back to Black 252
323 322 Frank 252
324 323 Carried to Dust (Bonus Track Version) 253
325 324 Beethoven: Symphony No. 6 'Pastoral' Etc. 254
326 325 Bartok: Violin & Viola Concertos 255
327 326 Mendelssohn: A Midsummer Night's Dream 256
328 327 Bach: Orchestral Suites Nos. 1 - 4 257
329 328 Charpentier: Divertissements, Airs & Concerts 258
330 329 South American Getaway 259
331 330 Górecki: Symphony No. 3 260
332 331 Purcell: The Fairy Queen 261
333 332 The Ultimate Relexation Album 262
334 333 Purcell: Music for the Queen Mary 263
335 334 Weill: The Seven Deadly Sins 264
336 335 J.S. Bach: Chaconne, Suite in E Minor, Partita in E Major & Prelude, Fugue and Allegro 265
337 336 Prokofiev: Symphony No.5 & Stravinksy: Le Sacre Du Printemps 248
338 337 Szymanowski: Piano Works, Vol. 1 266
339 338 Nielsen: The Six Symphonies 267
340 339 Great Recordings of the Century: Paganini's 24 Caprices 268
341 340 Liszt - 12 Études D'Execution Transcendante 269
342 341 Great Recordings of the Century - Shubert: Schwanengesang, 4 Lieder 270
343 342 Locatelli: Concertos for Violin, Strings and Continuo, Vol. 3 271
344 343 Respighi:Pines of Rome 226
345 344 Schubert: The Late String Quartets & String Quintet (3 CD's) 272
346 345 Monteverdi: L'Orfeo 273
347 346 Mozart: Chamber Music 274
348 347 Koyaanisqatsi (Soundtrack from the Motion Picture) 275
349 348 asdaasdasdsd 3

View File

@@ -0,0 +1,276 @@
ID,name
1,AC/DC
2,Accept
3,Aerosmith
4,Alanis Morissette
5,Alice In Chains
6,Antônio Carlos Jobim
7,Apocalyptica
8,Audioslave
9,BackBeat
10,Billy Cobham
11,Black Label Society
12,Black Sabbath
13,Body Count
14,Bruce Dickinson
15,Buddy Guy
16,Caetano Veloso
17,Chico Buarque
18,Chico Science & Nação Zumbi
19,Cidade Negra
20,Cláudio Zoli
21,Various Artists
22,Led Zeppelin
23,Frank Zappa & Captain Beefheart
24,Marcos Valle
25,Milton Nascimento & Bebeto
26,Azymuth
27,Gilberto Gil
28,João Gilberto
29,Bebel Gilberto
30,Jorge Vercilo
31,Baby Consuelo
32,Ney Matogrosso
33,Luiz Melodia
34,Nando Reis
35,Pedro Luís & A Parede
36,O Rappa
37,Ed Motta
38,Banda Black Rio
39,Fernanda Porto
40,Os Cariocas
41,Elis Regina
42,Milton Nascimento
43,A Cor Do Som
44,Kid Abelha
45,Sandra De Sá
46,Jorge Ben
47,Hermeto Pascoal
48,Barão Vermelho
49,"Edson, DJ Marky & DJ Patife Featuring Fernanda Porto"
50,Metallica
51,Queen
52,Kiss
53,Spyro Gyra
54,Green Day
55,David Coverdale
56,Gonzaguinha
57,Os Mutantes
58,Deep Purple
59,Santana
60,Santana Feat. Dave Matthews
61,Santana Feat. Everlast
62,Santana Feat. Rob Thomas
63,Santana Feat. Lauryn Hill & Cee-Lo
64,Santana Feat. The Project G&B
65,Santana Feat. Maná
66,Santana Feat. Eagle-Eye Cherry
67,Santana Feat. Eric Clapton
68,Miles Davis
69,Gene Krupa
70,Toquinho & Vinícius
71,Vinícius De Moraes & Baden Powell
72,Vinícius De Moraes
73,Vinícius E Qurteto Em Cy
74,Vinícius E Odette Lara
75,"Vinicius, Toquinho & Quarteto Em Cy"
76,Creedence Clearwater Revival
77,Cássia Eller
78,Def Leppard
79,Dennis Chambers
80,Djavan
81,Eric Clapton
82,Faith No More
83,Falamansa
84,Foo Fighters
85,Frank Sinatra
86,Funk Como Le Gusta
87,Godsmack
88,Guns N' Roses
89,Incognito
90,Iron Maiden
91,James Brown
92,Jamiroquai
93,JET
94,Jimi Hendrix
95,Joe Satriani
96,Jota Quest
97,João Suplicy
98,Judas Priest
99,Legião Urbana
100,Lenny Kravitz
101,Lulu Santos
102,Marillion
103,Marisa Monte
104,Marvin Gaye
105,Men At Work
106,Motörhead
107,Motörhead & Girlschool
108,Mônica Marianno
109,Mötley Crüe
110,Nirvana
111,O Terço
112,Olodum
113,Os Paralamas Do Sucesso
114,Ozzy Osbourne
115,Page & Plant
116,Passengers
117,Paul D'Ianno
118,Pearl Jam
119,Peter Tosh
120,Pink Floyd
121,Planet Hemp
122,R.E.M. Feat. Kate Pearson
123,R.E.M. Feat. KRS-One
124,R.E.M.
125,Raimundos
126,Raul Seixas
127,Red Hot Chili Peppers
128,Rush
129,Simply Red
130,Skank
131,Smashing Pumpkins
132,Soundgarden
133,Stevie Ray Vaughan & Double Trouble
134,Stone Temple Pilots
135,System Of A Down
136,"Terry Bozzio, Tony Levin & Steve Stevens"
137,The Black Crowes
138,The Clash
139,The Cult
140,The Doors
141,The Police
142,The Rolling Stones
143,The Tea Party
144,The Who
145,Tim Maia
146,Titãs
147,Battlestar Galactica
148,Heroes
149,Lost
150,U2
151,UB40
152,Van Halen
153,Velvet Revolver
154,Whitesnake
155,Zeca Pagodinho
156,The Office
157,Dread Zeppelin
158,Battlestar Galactica (Classic)
159,Aquaman
160,Christina Aguilera featuring BigElf
161,Aerosmith & Sierra Leone's Refugee Allstars
162,Los Lonely Boys
163,Corinne Bailey Rae
164,Dhani Harrison & Jakob Dylan
165,Jackson Browne
166,Avril Lavigne
167,Big & Rich
168,Youssou N'Dour
169,Black Eyed Peas
170,Jack Johnson
171,Ben Harper
172,Snow Patrol
173,Matisyahu
174,The Postal Service
175,Jaguares
176,The Flaming Lips
177,Jack's Mannequin & Mick Fleetwood
178,Regina Spektor
179,Scorpions
180,House Of Pain
181,Xis
182,Nega Gizza
183,Gustavo & Andres Veiga & Salazar
184,Rodox
185,Charlie Brown Jr.
186,Pedro Luís E A Parede
187,Los Hermanos
188,Mundo Livre S/A
189,Otto
190,Instituto
191,Nação Zumbi
192,DJ Dolores & Orchestra Santa Massa
193,Seu Jorge
194,Sabotage E Instituto
195,Stereo Maracana
196,Cake
197,Aisha Duo
198,Habib Koité and Bamada
199,Karsh Kale
200,The Posies
201,Luciana Souza/Romero Lubambo
202,Aaron Goldberg
203,Nicolaus Esterhazy Sinfonia
204,Temple of the Dog
205,Chris Cornell
206,Alberto Turco & Nova Schola Gregoriana
207,"Richard Marlow & The Choir of Trinity College, Cambridge"
208,English Concert & Trevor Pinnock
209,"Anne-Sophie Mutter, Herbert Von Karajan & Wiener Philharmoniker"
210,"Hilary Hahn, Jeffrey Kahane, Los Angeles Chamber Orchestra & Margaret Batjer"
211,Wilhelm Kempff
212,Yo-Yo Ma
213,Scholars Baroque Ensemble
214,Academy of St. Martin in the Fields & Sir Neville Marriner
215,Academy of St. Martin in the Fields Chamber Ensemble & Sir Neville Marriner
216,"Berliner Philharmoniker, Claudio Abbado & Sabine Meyer"
217,Royal Philharmonic Orchestra & Sir Thomas Beecham
218,Orchestre Révolutionnaire et Romantique & John Eliot Gardiner
219,"Britten Sinfonia, Ivor Bolton & Lesley Garrett"
220,"Chicago Symphony Chorus, Chicago Symphony Orchestra & Sir Georg Solti"
221,Sir Georg Solti & Wiener Philharmoniker
222,"Academy of St. Martin in the Fields, John Birch, Sir Neville Marriner & Sylvia McNair"
223,London Symphony Orchestra & Sir Charles Mackerras
224,Barry Wordsworth & BBC Concert Orchestra
225,"Herbert Von Karajan, Mirella Freni & Wiener Philharmoniker"
226,Eugene Ormandy
227,Luciano Pavarotti
228,Leonard Bernstein & New York Philharmonic
229,Boston Symphony Orchestra & Seiji Ozawa
230,Aaron Copland & London Symphony Orchestra
231,Ton Koopman
232,Sergei Prokofiev & Yuri Temirkanov
233,Chicago Symphony Orchestra & Fritz Reiner
234,Orchestra of The Age of Enlightenment
235,"Emanuel Ax, Eugene Ormandy & Philadelphia Orchestra"
236,James Levine
237,Berliner Philharmoniker & Hans Rosbaud
238,Maurizio Pollini
239,"Academy of St. Martin in the Fields, Sir Neville Marriner & William Bennett"
240,Gustav Mahler
241,"Felix Schmidt, London Symphony Orchestra & Rafael Frühbeck de Burgos"
242,Edo de Waart & San Francisco Symphony
243,Antal Doráti & London Symphony Orchestra
244,Choir Of Westminster Abbey & Simon Preston
245,Michael Tilson Thomas & San Francisco Symphony
246,"Chor der Wiener Staatsoper, Herbert Von Karajan & Wiener Philharmoniker"
247,The King's Singers
248,Berliner Philharmoniker & Herbert Von Karajan
249,"Sir Georg Solti, Sumi Jo & Wiener Philharmoniker"
250,Christopher O'Riley
251,Fretwork
252,Amy Winehouse
253,Calexico
254,Otto Klemperer & Philharmonia Orchestra
255,Yehudi Menuhin
256,Philharmonia Orchestra & Sir Neville Marriner
257,"Academy of St. Martin in the Fields, Sir Neville Marriner & Thurston Dart"
258,Les Arts Florissants & William Christie
259,The 12 Cellists of The Berlin Philharmonic
260,Adrian Leaper & Doreen de Feis
261,"Roger Norrington, London Classical Players"
262,Charles Dutoit & L'Orchestre Symphonique de Montréal
263,"Equale Brass Ensemble, John Eliot Gardiner & Munich Monteverdi Orchestra and Choir"
264,Kent Nagano and Orchestre de l'Opéra de Lyon
265,Julian Bream
266,Martin Roscoe
267,Göteborgs Symfoniker & Neeme Järvi
268,Itzhak Perlman
269,Michele Campanella
270,Gerald Moore
271,"Mela Tenenbaum, Pro Musica Prague & Richard Kapp"
272,Emerson String Quartet
273,"C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu"
274,Nash Ensemble
275,Philip Glass Ensemble
1 ID name
2 1 AC/DC
3 2 Accept
4 3 Aerosmith
5 4 Alanis Morissette
6 5 Alice In Chains
7 6 Antônio Carlos Jobim
8 7 Apocalyptica
9 8 Audioslave
10 9 BackBeat
11 10 Billy Cobham
12 11 Black Label Society
13 12 Black Sabbath
14 13 Body Count
15 14 Bruce Dickinson
16 15 Buddy Guy
17 16 Caetano Veloso
18 17 Chico Buarque
19 18 Chico Science & Nação Zumbi
20 19 Cidade Negra
21 20 Cláudio Zoli
22 21 Various Artists
23 22 Led Zeppelin
24 23 Frank Zappa & Captain Beefheart
25 24 Marcos Valle
26 25 Milton Nascimento & Bebeto
27 26 Azymuth
28 27 Gilberto Gil
29 28 João Gilberto
30 29 Bebel Gilberto
31 30 Jorge Vercilo
32 31 Baby Consuelo
33 32 Ney Matogrosso
34 33 Luiz Melodia
35 34 Nando Reis
36 35 Pedro Luís & A Parede
37 36 O Rappa
38 37 Ed Motta
39 38 Banda Black Rio
40 39 Fernanda Porto
41 40 Os Cariocas
42 41 Elis Regina
43 42 Milton Nascimento
44 43 A Cor Do Som
45 44 Kid Abelha
46 45 Sandra De Sá
47 46 Jorge Ben
48 47 Hermeto Pascoal
49 48 Barão Vermelho
50 49 Edson, DJ Marky & DJ Patife Featuring Fernanda Porto
51 50 Metallica
52 51 Queen
53 52 Kiss
54 53 Spyro Gyra
55 54 Green Day
56 55 David Coverdale
57 56 Gonzaguinha
58 57 Os Mutantes
59 58 Deep Purple
60 59 Santana
61 60 Santana Feat. Dave Matthews
62 61 Santana Feat. Everlast
63 62 Santana Feat. Rob Thomas
64 63 Santana Feat. Lauryn Hill & Cee-Lo
65 64 Santana Feat. The Project G&B
66 65 Santana Feat. Maná
67 66 Santana Feat. Eagle-Eye Cherry
68 67 Santana Feat. Eric Clapton
69 68 Miles Davis
70 69 Gene Krupa
71 70 Toquinho & Vinícius
72 71 Vinícius De Moraes & Baden Powell
73 72 Vinícius De Moraes
74 73 Vinícius E Qurteto Em Cy
75 74 Vinícius E Odette Lara
76 75 Vinicius, Toquinho & Quarteto Em Cy
77 76 Creedence Clearwater Revival
78 77 Cássia Eller
79 78 Def Leppard
80 79 Dennis Chambers
81 80 Djavan
82 81 Eric Clapton
83 82 Faith No More
84 83 Falamansa
85 84 Foo Fighters
86 85 Frank Sinatra
87 86 Funk Como Le Gusta
88 87 Godsmack
89 88 Guns N' Roses
90 89 Incognito
91 90 Iron Maiden
92 91 James Brown
93 92 Jamiroquai
94 93 JET
95 94 Jimi Hendrix
96 95 Joe Satriani
97 96 Jota Quest
98 97 João Suplicy
99 98 Judas Priest
100 99 Legião Urbana
101 100 Lenny Kravitz
102 101 Lulu Santos
103 102 Marillion
104 103 Marisa Monte
105 104 Marvin Gaye
106 105 Men At Work
107 106 Motörhead
108 107 Motörhead & Girlschool
109 108 Mônica Marianno
110 109 Mötley Crüe
111 110 Nirvana
112 111 O Terço
113 112 Olodum
114 113 Os Paralamas Do Sucesso
115 114 Ozzy Osbourne
116 115 Page & Plant
117 116 Passengers
118 117 Paul D'Ianno
119 118 Pearl Jam
120 119 Peter Tosh
121 120 Pink Floyd
122 121 Planet Hemp
123 122 R.E.M. Feat. Kate Pearson
124 123 R.E.M. Feat. KRS-One
125 124 R.E.M.
126 125 Raimundos
127 126 Raul Seixas
128 127 Red Hot Chili Peppers
129 128 Rush
130 129 Simply Red
131 130 Skank
132 131 Smashing Pumpkins
133 132 Soundgarden
134 133 Stevie Ray Vaughan & Double Trouble
135 134 Stone Temple Pilots
136 135 System Of A Down
137 136 Terry Bozzio, Tony Levin & Steve Stevens
138 137 The Black Crowes
139 138 The Clash
140 139 The Cult
141 140 The Doors
142 141 The Police
143 142 The Rolling Stones
144 143 The Tea Party
145 144 The Who
146 145 Tim Maia
147 146 Titãs
148 147 Battlestar Galactica
149 148 Heroes
150 149 Lost
151 150 U2
152 151 UB40
153 152 Van Halen
154 153 Velvet Revolver
155 154 Whitesnake
156 155 Zeca Pagodinho
157 156 The Office
158 157 Dread Zeppelin
159 158 Battlestar Galactica (Classic)
160 159 Aquaman
161 160 Christina Aguilera featuring BigElf
162 161 Aerosmith & Sierra Leone's Refugee Allstars
163 162 Los Lonely Boys
164 163 Corinne Bailey Rae
165 164 Dhani Harrison & Jakob Dylan
166 165 Jackson Browne
167 166 Avril Lavigne
168 167 Big & Rich
169 168 Youssou N'Dour
170 169 Black Eyed Peas
171 170 Jack Johnson
172 171 Ben Harper
173 172 Snow Patrol
174 173 Matisyahu
175 174 The Postal Service
176 175 Jaguares
177 176 The Flaming Lips
178 177 Jack's Mannequin & Mick Fleetwood
179 178 Regina Spektor
180 179 Scorpions
181 180 House Of Pain
182 181 Xis
183 182 Nega Gizza
184 183 Gustavo & Andres Veiga & Salazar
185 184 Rodox
186 185 Charlie Brown Jr.
187 186 Pedro Luís E A Parede
188 187 Los Hermanos
189 188 Mundo Livre S/A
190 189 Otto
191 190 Instituto
192 191 Nação Zumbi
193 192 DJ Dolores & Orchestra Santa Massa
194 193 Seu Jorge
195 194 Sabotage E Instituto
196 195 Stereo Maracana
197 196 Cake
198 197 Aisha Duo
199 198 Habib Koité and Bamada
200 199 Karsh Kale
201 200 The Posies
202 201 Luciana Souza/Romero Lubambo
203 202 Aaron Goldberg
204 203 Nicolaus Esterhazy Sinfonia
205 204 Temple of the Dog
206 205 Chris Cornell
207 206 Alberto Turco & Nova Schola Gregoriana
208 207 Richard Marlow & The Choir of Trinity College, Cambridge
209 208 English Concert & Trevor Pinnock
210 209 Anne-Sophie Mutter, Herbert Von Karajan & Wiener Philharmoniker
211 210 Hilary Hahn, Jeffrey Kahane, Los Angeles Chamber Orchestra & Margaret Batjer
212 211 Wilhelm Kempff
213 212 Yo-Yo Ma
214 213 Scholars Baroque Ensemble
215 214 Academy of St. Martin in the Fields & Sir Neville Marriner
216 215 Academy of St. Martin in the Fields Chamber Ensemble & Sir Neville Marriner
217 216 Berliner Philharmoniker, Claudio Abbado & Sabine Meyer
218 217 Royal Philharmonic Orchestra & Sir Thomas Beecham
219 218 Orchestre Révolutionnaire et Romantique & John Eliot Gardiner
220 219 Britten Sinfonia, Ivor Bolton & Lesley Garrett
221 220 Chicago Symphony Chorus, Chicago Symphony Orchestra & Sir Georg Solti
222 221 Sir Georg Solti & Wiener Philharmoniker
223 222 Academy of St. Martin in the Fields, John Birch, Sir Neville Marriner & Sylvia McNair
224 223 London Symphony Orchestra & Sir Charles Mackerras
225 224 Barry Wordsworth & BBC Concert Orchestra
226 225 Herbert Von Karajan, Mirella Freni & Wiener Philharmoniker
227 226 Eugene Ormandy
228 227 Luciano Pavarotti
229 228 Leonard Bernstein & New York Philharmonic
230 229 Boston Symphony Orchestra & Seiji Ozawa
231 230 Aaron Copland & London Symphony Orchestra
232 231 Ton Koopman
233 232 Sergei Prokofiev & Yuri Temirkanov
234 233 Chicago Symphony Orchestra & Fritz Reiner
235 234 Orchestra of The Age of Enlightenment
236 235 Emanuel Ax, Eugene Ormandy & Philadelphia Orchestra
237 236 James Levine
238 237 Berliner Philharmoniker & Hans Rosbaud
239 238 Maurizio Pollini
240 239 Academy of St. Martin in the Fields, Sir Neville Marriner & William Bennett
241 240 Gustav Mahler
242 241 Felix Schmidt, London Symphony Orchestra & Rafael Frühbeck de Burgos
243 242 Edo de Waart & San Francisco Symphony
244 243 Antal Doráti & London Symphony Orchestra
245 244 Choir Of Westminster Abbey & Simon Preston
246 245 Michael Tilson Thomas & San Francisco Symphony
247 246 Chor der Wiener Staatsoper, Herbert Von Karajan & Wiener Philharmoniker
248 247 The King's Singers
249 248 Berliner Philharmoniker & Herbert Von Karajan
250 249 Sir Georg Solti, Sumi Jo & Wiener Philharmoniker
251 250 Christopher O'Riley
252 251 Fretwork
253 252 Amy Winehouse
254 253 Calexico
255 254 Otto Klemperer & Philharmonia Orchestra
256 255 Yehudi Menuhin
257 256 Philharmonia Orchestra & Sir Neville Marriner
258 257 Academy of St. Martin in the Fields, Sir Neville Marriner & Thurston Dart
259 258 Les Arts Florissants & William Christie
260 259 The 12 Cellists of The Berlin Philharmonic
261 260 Adrian Leaper & Doreen de Feis
262 261 Roger Norrington, London Classical Players
263 262 Charles Dutoit & L'Orchestre Symphonique de Montréal
264 263 Equale Brass Ensemble, John Eliot Gardiner & Munich Monteverdi Orchestra and Choir
265 264 Kent Nagano and Orchestre de l'Opéra de Lyon
266 265 Julian Bream
267 266 Martin Roscoe
268 267 Göteborgs Symfoniker & Neeme Järvi
269 268 Itzhak Perlman
270 269 Michele Campanella
271 270 Gerald Moore
272 271 Mela Tenenbaum, Pro Musica Prague & Richard Kapp
273 272 Emerson String Quartet
274 273 C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu
275 274 Nash Ensemble
276 275 Philip Glass Ensemble

View File

@@ -0,0 +1,60 @@
ID,lastName,firstName,city,address,country,phone,email,password,supportRep_ID
1,Gonçalves,Luís,São José dos Campos,"Av. Brigadeiro Faria Lima, 2170",Brazil,+55 (12) 3923-5555,luisg@embraer.com.br,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
2,Köhler,Leonie,Stuttgart,Theodor-Heuss-Straße 34,Germany,+49 0711 2842222,leonekohler@surfeu.de,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
3,Tremblay,François,Montréal,1498 rue Bélanger,Canada,+1 (514) 721-4711,ftremblay@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
4,Hansen,Bjørn,Oslo,Ullevålsveien 14,Norway,+47 22 44 22 22,bjorn.hansen@yahoo.no,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
5,Wichterlová,František,Prague,Klanova 9/506,Czech Republic,+420 2 4172 5555,frantisekw@jetbrains.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
6,Holý,Helena,Prague,Rilská 3174/6,Czech Republic,+420 2 4177 0449,hholy@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
7,Gruber,Astrid,Vienne,"Rotenturmstraße 4, 1010 Innere Stadt",Austria,+43 01 5134505,astrid.gruber@apple.at,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
8,Peeters,Daan,Brussels,Grétrystraat 63,Belgium,+32 02 219 03 03,daan_peeters@apple.be,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
9,Nielsen,Kara,Copenhagen,Sønder Boulevard 51,Denmark,+453 3331 9991,kara.nielsen@jubii.dk,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
10,Martins,Eduardo,São Paulo,"Rua Dr. Falcão Filho, 155",Brazil,+55 (11) 3033-5446,eduardo@woodstock.com.br,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
11,Rocha,Alexandre,São Paulo,"Av. Paulista, 2022",Brazil,+55 (11) 3055-3278,alero@uol.com.br,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
12,Almeida,Roberto,Rio de Janeiro,"Praça Pio X, 119",Brazil,+55 (21) 2271-7000,roberto.almeida@riotur.gov.br,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
13,Ramos,Fernanda,Brasília,Qe 7 Bloco G,Brazil,+55 (61) 3363-5547,fernadaramos4@uol.com.br,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
14,Philips,Mark,Edmonton,8210 111 ST NW,Canada,+1 (780) 434-4554,mphilips12@shaw.ca,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
15,Peterson,Jennifer,Vancouver,700 W Pender Street,Canada,+1 (604) 688-2255,jenniferp@rogers.ca,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
16,Harris,Frank,Mountain View,1600 Amphitheatre Parkway,USA,+1 (650) 253-0000,fharris@google.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
17,Smith,Jack,Redmond,1 Microsoft Way,USA,+1 (425) 882-8080,jacksmith@microsoft.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
18,Brooks,Michelle,New York,627 Broadway,USA,+1 (212) 221-3546,michelleb@aol.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
19,Goyer,Tim,Cupertino,1 Infinite Loop,USA,+1 (408) 996-1010,tgoyer@apple.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
20,Miller,Dan,Mountain View,541 Del Medio Avenue,USA,+1 (650) 644-3358,dmiller@comcast.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
21,Chase,Kathy,Reno,801 W 4th Street,USA,+1 (775) 223-7665,kachase@hotmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
22,Leacock,Heather,Orlando,120 S Orange Ave,USA,+1 (407) 999-7788,hleacock@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
23,Gordon,John,Boston,69 Salem Street,USA,+1 (617) 522-1333,johngordon22@yahoo.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
24,Ralston,Frank,Chicago,162 E Superior Street,USA,+1 (312) 332-3232,fralston@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
25,Stevens,Victor,Madison,319 N. Frances Street,USA,+1 (608) 257-0597,vstevens@yahoo.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
26,Cunningham,Richard,Fort Worth,2211 W Berry Street,USA,+1 (817) 924-7272,ricunningham@hotmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
27,Gray,Patrick,Tucson,1033 N Park Ave,USA,+1 (520) 622-4200,patrick.gray@aol.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
28,Barnett,Julia,Salt Lake City,302 S 700 E,USA,+1 (801) 531-7272,jubarnett@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
29,Brown,Robert,Toronto,796 Dundas Street West,Canada,+1 (416) 363-8888,robbrown@shaw.ca,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
30,Francis,Edward,Ottawa,230 Elgin Street,Canada,+1 (613) 234-3322,edfrancis@yachoo.ca,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
31,Silk,Martha,Halifax,194A Chain Lake Drive,Canada,+1 (902) 450-0450,marthasilk@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
32,Mitchell,Aaron,Winnipeg,696 Osborne Street,Canada,+1 (204) 452-6452,aaronmitchell@yahoo.ca,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
33,Sullivan,Ellie,Yellowknife,5112 48 Street,Canada,+1 (867) 920-2233,ellie.sullivan@shaw.ca,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
34,Fernandes,João,Lisbon,Rua da Assunção 53,Portugal,+351 (213) 466-111,jfernandes@yahoo.pt,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
35,Sampaio,Madalena,Porto,"Rua dos Campeões Europeus de Viena, 4350",Portugal,+351 (225) 022-448,masampaio@sapo.pt,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
36,Schneider,Hannah,Berlin,Tauentzienstraße 8,Germany,+49 030 26550280,hannah.schneider@yahoo.de,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
37,Zimmermann,Fynn,Frankfurt,Berger Straße 10,Germany,+49 069 40598889,fzimmermann@yahoo.de,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
38,Schröder,Niklas,Berlin,Barbarossastraße 19,Germany,+49 030 2141444,nschroder@surfeu.de,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
39,Bernard,Camille,Paris,"4, Rue Milton",France,+33 01 49 70 65 65,camille.bernard@yahoo.fr,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
40,Lefebvre,Dominique,Paris,"8, Rue Hanovre",France,+33 01 47 42 71 71,dominiquelefebvre@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
41,Dubois,Marc,Lyon,"11, Place Bellecour",France,+33 04 78 30 30 30,marc.dubois@hotmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
42,Girard,Wyatt,Bordeaux,"9, Place Louis Barthou",France,+33 05 56 96 96 96,wyatt.girard@yahoo.fr,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
43,Mercier,Isabelle,Dijon,"68, Rue Jouvence",France,+33 03 80 73 66 99,isabelle_mercier@apple.fr,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
44,Hämäläinen,Terhi,Helsinki,Porthaninkatu 9,Finland,+358 09 870 2000,terhi.hamalainen@apple.fi,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
45,Kovács,Ladislav,Budapest,Erzsébet krt. 58.,Hungary,,ladislav_kovacs@apple.hu,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
46,O'Reilly,Hugh,Dublin,3 Chatham Street,Ireland,+353 01 6792424,hughoreilly@apple.ie,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
47,Mancini,Lucas,Rome,"Via Degli Scipioni, 43",Italy,+39 06 39733434,lucas.mancini@yahoo.it,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
48,Van der Berg,Johannes,Amsterdam,Lijnbaansgracht 120bg,Netherlands,+31 020 6223130,johavanderberg@yahoo.nl,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
49,Wójcik,Stanisław,Warsaw,Ordynacka 10,Poland,+48 22 828 37 39,stanisław.wójcik@wp.pl,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
50,Muñoz,Enrique,Madrid,C/ San Bernardo 85,Spain,+34 914 454 454,enrique_munoz@yahoo.es,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
51,Johansson,Joakim,Stockholm,Celsiusg. 9,Sweden,+46 08-651 52 52,joakim.johansson@yahoo.se,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
52,Jones,Emma,London,202 Hoxton Street,United Kingdom,+44 020 7707 0707,emma_jones@hotmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
53,Hughes,Phil,London,113 Lupus St,United Kingdom,+44 020 7976 5722,phil.hughes@gmail.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
54,Murray,Steve,Edinburgh,110 Raeburn Pl,United Kingdom,+44 0131 315 3300,steve.murray@yahoo.uk,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
55,Taylor,Mark,Sidney,421 Bourke Street,Australia,+61 (02) 9332 3633,mark.taylor@yahoo.au,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
56,Gutiérrez,Diego,Buenos Aires,307 Macacha Güemes,Argentina,+54 (0)11 4311 4333,diego.gutierrez@yahoo.ar,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,4
57,Rojas,Luis,Santiago,"Calle Lira, 198",Chile,+56 (0)2 635 4444,luisrojas@yahoo.cl,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,5
58,Pareek,Manoj,Delhi,"12,Community Centre",India,+91 0124 39883988,manoj.pareek@rediff.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
59,Srivastava,Puja,Bangalore,"3,Raj Bhavan Road",India,+91 080 22289999,puja_srivastava@yahoo.in,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,3
1 ID lastName firstName city address country phone email password supportRep_ID
2 1 Gonçalves Luís São José dos Campos Av. Brigadeiro Faria Lima, 2170 Brazil +55 (12) 3923-5555 luisg@embraer.com.br $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
3 2 Köhler Leonie Stuttgart Theodor-Heuss-Straße 34 Germany +49 0711 2842222 leonekohler@surfeu.de $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
4 3 Tremblay François Montréal 1498 rue Bélanger Canada +1 (514) 721-4711 ftremblay@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
5 4 Hansen Bjørn Oslo Ullevålsveien 14 Norway +47 22 44 22 22 bjorn.hansen@yahoo.no $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
6 5 Wichterlová František Prague Klanova 9/506 Czech Republic +420 2 4172 5555 frantisekw@jetbrains.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
7 6 Holý Helena Prague Rilská 3174/6 Czech Republic +420 2 4177 0449 hholy@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
8 7 Gruber Astrid Vienne Rotenturmstraße 4, 1010 Innere Stadt Austria +43 01 5134505 astrid.gruber@apple.at $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
9 8 Peeters Daan Brussels Grétrystraat 63 Belgium +32 02 219 03 03 daan_peeters@apple.be $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
10 9 Nielsen Kara Copenhagen Sønder Boulevard 51 Denmark +453 3331 9991 kara.nielsen@jubii.dk $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
11 10 Martins Eduardo São Paulo Rua Dr. Falcão Filho, 155 Brazil +55 (11) 3033-5446 eduardo@woodstock.com.br $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
12 11 Rocha Alexandre São Paulo Av. Paulista, 2022 Brazil +55 (11) 3055-3278 alero@uol.com.br $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
13 12 Almeida Roberto Rio de Janeiro Praça Pio X, 119 Brazil +55 (21) 2271-7000 roberto.almeida@riotur.gov.br $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
14 13 Ramos Fernanda Brasília Qe 7 Bloco G Brazil +55 (61) 3363-5547 fernadaramos4@uol.com.br $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
15 14 Philips Mark Edmonton 8210 111 ST NW Canada +1 (780) 434-4554 mphilips12@shaw.ca $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
16 15 Peterson Jennifer Vancouver 700 W Pender Street Canada +1 (604) 688-2255 jenniferp@rogers.ca $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
17 16 Harris Frank Mountain View 1600 Amphitheatre Parkway USA +1 (650) 253-0000 fharris@google.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
18 17 Smith Jack Redmond 1 Microsoft Way USA +1 (425) 882-8080 jacksmith@microsoft.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
19 18 Brooks Michelle New York 627 Broadway USA +1 (212) 221-3546 michelleb@aol.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
20 19 Goyer Tim Cupertino 1 Infinite Loop USA +1 (408) 996-1010 tgoyer@apple.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
21 20 Miller Dan Mountain View 541 Del Medio Avenue USA +1 (650) 644-3358 dmiller@comcast.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
22 21 Chase Kathy Reno 801 W 4th Street USA +1 (775) 223-7665 kachase@hotmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
23 22 Leacock Heather Orlando 120 S Orange Ave USA +1 (407) 999-7788 hleacock@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
24 23 Gordon John Boston 69 Salem Street USA +1 (617) 522-1333 johngordon22@yahoo.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
25 24 Ralston Frank Chicago 162 E Superior Street USA +1 (312) 332-3232 fralston@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
26 25 Stevens Victor Madison 319 N. Frances Street USA +1 (608) 257-0597 vstevens@yahoo.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
27 26 Cunningham Richard Fort Worth 2211 W Berry Street USA +1 (817) 924-7272 ricunningham@hotmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
28 27 Gray Patrick Tucson 1033 N Park Ave USA +1 (520) 622-4200 patrick.gray@aol.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
29 28 Barnett Julia Salt Lake City 302 S 700 E USA +1 (801) 531-7272 jubarnett@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
30 29 Brown Robert Toronto 796 Dundas Street West Canada +1 (416) 363-8888 robbrown@shaw.ca $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
31 30 Francis Edward Ottawa 230 Elgin Street Canada +1 (613) 234-3322 edfrancis@yachoo.ca $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
32 31 Silk Martha Halifax 194A Chain Lake Drive Canada +1 (902) 450-0450 marthasilk@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
33 32 Mitchell Aaron Winnipeg 696 Osborne Street Canada +1 (204) 452-6452 aaronmitchell@yahoo.ca $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
34 33 Sullivan Ellie Yellowknife 5112 48 Street Canada +1 (867) 920-2233 ellie.sullivan@shaw.ca $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
35 34 Fernandes João Lisbon Rua da Assunção 53 Portugal +351 (213) 466-111 jfernandes@yahoo.pt $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
36 35 Sampaio Madalena Porto Rua dos Campeões Europeus de Viena, 4350 Portugal +351 (225) 022-448 masampaio@sapo.pt $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
37 36 Schneider Hannah Berlin Tauentzienstraße 8 Germany +49 030 26550280 hannah.schneider@yahoo.de $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
38 37 Zimmermann Fynn Frankfurt Berger Straße 10 Germany +49 069 40598889 fzimmermann@yahoo.de $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
39 38 Schröder Niklas Berlin Barbarossastraße 19 Germany +49 030 2141444 nschroder@surfeu.de $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
40 39 Bernard Camille Paris 4, Rue Milton France +33 01 49 70 65 65 camille.bernard@yahoo.fr $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
41 40 Lefebvre Dominique Paris 8, Rue Hanovre France +33 01 47 42 71 71 dominiquelefebvre@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
42 41 Dubois Marc Lyon 11, Place Bellecour France +33 04 78 30 30 30 marc.dubois@hotmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
43 42 Girard Wyatt Bordeaux 9, Place Louis Barthou France +33 05 56 96 96 96 wyatt.girard@yahoo.fr $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
44 43 Mercier Isabelle Dijon 68, Rue Jouvence France +33 03 80 73 66 99 isabelle_mercier@apple.fr $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
45 44 Hämäläinen Terhi Helsinki Porthaninkatu 9 Finland +358 09 870 2000 terhi.hamalainen@apple.fi $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
46 45 Kovács Ladislav Budapest Erzsébet krt. 58. Hungary ladislav_kovacs@apple.hu $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
47 46 O'Reilly Hugh Dublin 3 Chatham Street Ireland +353 01 6792424 hughoreilly@apple.ie $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
48 47 Mancini Lucas Rome Via Degli Scipioni, 43 Italy +39 06 39733434 lucas.mancini@yahoo.it $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
49 48 Van der Berg Johannes Amsterdam Lijnbaansgracht 120bg Netherlands +31 020 6223130 johavanderberg@yahoo.nl $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
50 49 Wójcik Stanisław Warsaw Ordynacka 10 Poland +48 22 828 37 39 stanisław.wójcik@wp.pl $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
51 50 Muñoz Enrique Madrid C/ San Bernardo 85 Spain +34 914 454 454 enrique_munoz@yahoo.es $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
52 51 Johansson Joakim Stockholm Celsiusg. 9 Sweden +46 08-651 52 52 joakim.johansson@yahoo.se $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
53 52 Jones Emma London 202 Hoxton Street United Kingdom +44 020 7707 0707 emma_jones@hotmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
54 53 Hughes Phil London 113 Lupus St United Kingdom +44 020 7976 5722 phil.hughes@gmail.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
55 54 Murray Steve Edinburgh 110 Raeburn Pl United Kingdom +44 0131 315 3300 steve.murray@yahoo.uk $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
56 55 Taylor Mark Sidney 421 Bourke Street Australia +61 (02) 9332 3633 mark.taylor@yahoo.au $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
57 56 Gutiérrez Diego Buenos Aires 307 Macacha Güemes Argentina +54 (0)11 4311 4333 diego.gutierrez@yahoo.ar $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 4
58 57 Rojas Luis Santiago Calle Lira, 198 Chile +56 (0)2 635 4444 luisrojas@yahoo.cl $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 5
59 58 Pareek Manoj Delhi 12,Community Centre India +91 0124 39883988 manoj.pareek@rediff.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3
60 59 Srivastava Puja Bangalore 3,Raj Bhavan Road India +91 080 22289999 puja_srivastava@yahoo.in $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC 3

View File

@@ -0,0 +1,9 @@
ID,lastName,firstName,city,address,country,phone,email,password,title,birthDate,hireDate,reportsTo_ID
1,Adams,Andrew,Edmonton,11120 Jasper Ave NW,Canada,+1 (780) 428-9482,andrew@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,General Manager,1962-02-18 00:00:00,2002-08-14 00:00:00,
2,Edwards,Nancy,Calgary,825 8 Ave SW,Canada,+1 (403) 262-3443,nancy@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,Sales Manager,1958-12-08 00:00:00,2002-05-01 00:00:00,1
3,Peacock,Jane,Calgary,1111 6 Ave SW,Canada,+1 (403) 262-3443,jane@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,Sales Support Agent,1973-08-29 00:00:00,2002-04-01 00:00:00,2
4,Park,Margaret,Calgary,683 10 Street SW,Canada,+1 (403) 263-4423,margaret@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,Sales Support Agent,1947-09-19 00:00:00,2003-05-03 00:00:00,2
5,Johnson,Steve,Calgary,7727B 41 Ave,Canada,1 (780) 836-9987,steve@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,Sales Support Agent,1965-03-03 00:00:00,2003-10-17 00:00:00,2
6,Mitchell,Michael,Calgary,5827 Bowness Road NW,Canada,+1 (403) 246-9887,michael@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,IT Manager,1973-07-01 00:00:00,2003-10-17 00:00:00,1
7,King,Robert,Lethbridge,590 Columbia Boulevard West,Canada,+1 (403) 456-9986,robert@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,IT Staff,1970-05-29 00:00:00,2004-01-02 00:00:00,6
8,Callahan,Laura,Lethbridge,923 7 ST NW,Canada,+1 (403) 467-3351,laura@chinookcorp.com,$2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC,IT Staff,1968-01-09 00:00:00,2004-03-04 00:00:00,6
1 ID lastName firstName city address country phone email password title birthDate hireDate reportsTo_ID
2 1 Adams Andrew Edmonton 11120 Jasper Ave NW Canada +1 (780) 428-9482 andrew@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC General Manager 1962-02-18 00:00:00 2002-08-14 00:00:00
3 2 Edwards Nancy Calgary 825 8 Ave SW Canada +1 (403) 262-3443 nancy@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC Sales Manager 1958-12-08 00:00:00 2002-05-01 00:00:00 1
4 3 Peacock Jane Calgary 1111 6 Ave SW Canada +1 (403) 262-3443 jane@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC Sales Support Agent 1973-08-29 00:00:00 2002-04-01 00:00:00 2
5 4 Park Margaret Calgary 683 10 Street SW Canada +1 (403) 263-4423 margaret@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC Sales Support Agent 1947-09-19 00:00:00 2003-05-03 00:00:00 2
6 5 Johnson Steve Calgary 7727B 41 Ave Canada 1 (780) 836-9987 steve@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC Sales Support Agent 1965-03-03 00:00:00 2003-10-17 00:00:00 2
7 6 Mitchell Michael Calgary 5827 Bowness Road NW Canada +1 (403) 246-9887 michael@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC IT Manager 1973-07-01 00:00:00 2003-10-17 00:00:00 1
8 7 King Robert Lethbridge 590 Columbia Boulevard West Canada +1 (403) 456-9986 robert@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC IT Staff 1970-05-29 00:00:00 2004-01-02 00:00:00 6
9 8 Callahan Laura Lethbridge 923 7 ST NW Canada +1 (403) 467-3351 laura@chinookcorp.com $2b$10$8f5ztDxjf/oz225jNdNB0uCtJSMeVwkchFayUrTS6xEcRVpoyIooC IT Staff 1968-01-09 00:00:00 2004-03-04 00:00:00 6

View File

@@ -0,0 +1,26 @@
ID,name
1,Rock
2,Jazz
3,Metal
4,Alternative & Punk
5,Rock And Roll
6,Blues
7,Latin
8,Reggae
9,Pop
10,Soundtrack
11,Bossa Nova
12,Easy Listening
13,Heavy Metal
14,R&B/Soul
15,Electronica/Dance
16,World
17,Hip Hop/Rap
18,Science Fiction
19,TV Shows
20,Sci Fi & Fantasy
21,Drama
22,Comedy
23,Alternative
24,Classical
25,Opera
1 ID name
2 1 Rock
3 2 Jazz
4 3 Metal
5 4 Alternative & Punk
6 5 Rock And Roll
7 6 Blues
8 7 Latin
9 8 Reggae
10 9 Pop
11 10 Soundtrack
12 11 Bossa Nova
13 12 Easy Listening
14 13 Heavy Metal
15 14 R&B/Soul
16 15 Electronica/Dance
17 16 World
18 17 Hip Hop/Rap
19 18 Science Fiction
20 19 TV Shows
21 20 Sci Fi & Fantasy
22 21 Drama
23 22 Comedy
24 23 Alternative
25 24 Classical
26 25 Opera

View File

@@ -0,0 +1,76 @@
ID;locale;name
1;ru;Рок
1;fr;Roche
1;de;Felsen
2;ru;Джаз
2;fr;le jazz
2;de;Jazz
3;ru;Металл
3;fr;Métal
3;de;Metall
4;ru;Альтернатива и панк
4;fr;Alternatif et punk
4;de;Alternative & Punk
5;ru;Рок-н-ролл
5;fr;Rock and roll
5;de;Rock'n'Roll
6;ru;Блюз
6;fr;Blues
6;de;Blues
7;ru;Латинский
7;fr;Latine
7;de;Latein
8;ru;Регги
8;fr;Reggae
8;de;Reggae
9;ru;Поп
9;fr;Pop
9;de;Pop
10;ru;Саундтрек
10;fr;Bande sonore
10;de;Soundtrack
11;ru;Босса-нова
11;fr;Bossa Nova
11;de;Bossa Nova
12;ru;Легко слушать
12;fr;Écoute facile
12;de;Einfaches Zuhören
13;ru;Тяжелый металл
13;fr;Heavy métal
13;de;Einfaches Zuhören
14;ru;R&B / Соул
14;fr;R&B / Soul
14;de;R&B / Soul
15;ru;Электроника / Танцы
15;fr;Électronique / danse
15;de;Elektronisch / Tanz
16;ru;Мир
16;fr;Monde
16;de;Welt
17;ru;Хип-хоп / рэп
17;fr;Hip-hop / Rap
17;de;Hip Hop / Rap
18;ru;Научная фантастика
18;fr;Science fiction
18;de;Science-Fiction
19;ru;ТВ шоу
19;fr;Émissions de télévision
19;de;Fernsehshows
20;ru;Научная фантастика и фэнтези
20;fr;Science-fiction et fantastique
20;de;Sci Fi & Fantasy
21;ru;Драма
21;fr;Drame
21;de;Theater
22;ru;Комедия
22;fr;La comédie
22;de;Komödie
23;ru;Альтернатива
23;fr;Alternative
23;de;Alternative
24;ru;Классический
24;fr;Classique
24;de;Klassik
25;ru;Опера
25;fr;Opéra
25;de;Oper
1 ID locale name
2 1 ru Рок
3 1 fr Roche
4 1 de Felsen
5 2 ru Джаз
6 2 fr le jazz
7 2 de Jazz
8 3 ru Металл
9 3 fr Métal
10 3 de Metall
11 4 ru Альтернатива и панк
12 4 fr Alternatif et punk
13 4 de Alternative & Punk
14 5 ru Рок-н-ролл
15 5 fr Rock and roll
16 5 de Rock'n'Roll
17 6 ru Блюз
18 6 fr Blues
19 6 de Blues
20 7 ru Латинский
21 7 fr Latine
22 7 de Latein
23 8 ru Регги
24 8 fr Reggae
25 8 de Reggae
26 9 ru Поп
27 9 fr Pop
28 9 de Pop
29 10 ru Саундтрек
30 10 fr Bande sonore
31 10 de Soundtrack
32 11 ru Босса-нова
33 11 fr Bossa Nova
34 11 de Bossa Nova
35 12 ru Легко слушать
36 12 fr Écoute facile
37 12 de Einfaches Zuhören
38 13 ru Тяжелый металл
39 13 fr Heavy métal
40 13 de Einfaches Zuhören
41 14 ru R&B / Соул
42 14 fr R&B / Soul
43 14 de R&B / Soul
44 15 ru Электроника / Танцы
45 15 fr Électronique / danse
46 15 de Elektronisch / Tanz
47 16 ru Мир
48 16 fr Monde
49 16 de Welt
50 17 ru Хип-хоп / рэп
51 17 fr Hip-hop / Rap
52 17 de Hip Hop / Rap
53 18 ru Научная фантастика
54 18 fr Science fiction
55 18 de Science-Fiction
56 19 ru ТВ шоу
57 19 fr Émissions de télévision
58 19 de Fernsehshows
59 20 ru Научная фантастика и фэнтези
60 20 fr Science-fiction et fantastique
61 20 de Sci Fi & Fantasy
62 21 ru Драма
63 21 fr Drame
64 21 de Theater
65 22 ru Комедия
66 22 fr La comédie
67 22 de Komödie
68 23 ru Альтернатива
69 23 fr Alternative
70 23 de Alternative
71 24 ru Классический
72 24 fr Classique
73 24 de Klassik
74 25 ru Опера
75 25 fr Opéra
76 25 de Oper

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,413 @@
ID,invoiceDate,total,status,customer_ID
1,2009-01-01T11:45:32Z,1.98,1,2
2,2009-01-02T11:45:32Z,3.96,1,4
3,2009-01-03T11:45:32Z,5.94,1,8
4,2009-01-06T11:45:32Z,8.91,1,14
5,2009-01-11T11:45:32Z,13.86,1,23
6,2009-01-19T11:45:32Z,0.99,1,37
7,2009-02-01T11:45:32Z,1.98,1,38
8,2009-02-01T11:45:32Z,1.98,1,40
9,2009-02-02T11:45:32Z,3.96,1,42
10,2009-02-03T11:45:32Z,5.94,1,46
11,2009-02-06T11:45:32Z,8.91,1,52
12,2009-02-11T11:45:32Z,13.86,1,2
13,2009-02-19T11:45:32Z,0.99,1,16
14,2009-03-04T11:45:32Z,1.98,1,17
15,2009-03-04T11:45:32Z,1.98,1,19
16,2009-03-05T11:45:32Z,3.96,1,21
17,2009-03-06T11:45:32Z,5.94,1,25
18,2009-03-09T11:45:32Z,8.91,1,31
19,2009-03-14T11:45:32Z,13.86,1,40
20,2009-03-22T11:45:32Z,0.99,1,54
21,2009-04-04T11:45:32Z,1.98,1,55
22,2009-04-04T11:45:32Z,1.98,1,57
23,2009-04-05T11:45:32Z,3.96,1,59
24,2009-04-06T11:45:32Z,5.94,1,4
25,2009-04-09T11:45:32Z,8.91,1,10
26,2009-04-14T11:45:32Z,13.86,1,19
27,2009-04-22T11:45:32Z,0.99,1,33
28,2009-05-05T11:45:32Z,1.98,1,34
29,2009-05-05T11:45:32Z,1.98,1,36
30,2009-05-06T11:45:32Z,3.96,1,38
31,2009-05-07T11:45:32Z,5.94,1,42
32,2009-05-10T11:45:32Z,8.91,1,48
33,2009-05-15T11:45:32Z,13.86,1,57
34,2009-05-23T11:45:32Z,0.99,1,12
35,2009-06-05T11:45:32Z,1.98,1,13
36,2009-06-05T11:45:32Z,1.98,1,15
37,2009-06-06T11:45:32Z,3.96,1,17
38,2009-06-07T11:45:32Z,5.94,1,21
39,2009-06-10T11:45:32Z,8.91,1,27
40,2009-06-15T11:45:32Z,13.86,1,36
41,2009-06-23T11:45:32Z,0.99,1,50
42,2009-07-06T11:45:32Z,1.98,1,51
43,2009-07-06T11:45:32Z,1.98,1,53
44,2009-07-07T11:45:32Z,3.96,1,55
45,2009-07-08T11:45:32Z,5.94,1,59
46,2009-07-11T11:45:32Z,8.91,1,6
47,2009-07-16T11:45:32Z,13.86,1,15
48,2009-07-24T11:45:32Z,0.99,1,29
49,2009-08-06T11:45:32Z,1.98,1,30
50,2009-08-06T11:45:32Z,1.98,1,32
51,2009-08-07T11:45:32Z,3.96,1,34
52,2009-08-08T11:45:32Z,5.94,1,38
53,2009-08-11T11:45:32Z,8.91,1,44
54,2009-08-16T11:45:32Z,13.86,1,53
55,2009-08-24T11:45:32Z,0.99,1,8
56,2009-09-06T11:45:32Z,1.98,1,9
57,2009-09-06T11:45:32Z,1.98,1,11
58,2009-09-07T11:45:32Z,3.96,1,13
59,2009-09-08T11:45:32Z,5.94,1,17
60,2009-09-11T11:45:32Z,8.91,1,23
61,2009-09-16T11:45:32Z,13.86,1,32
62,2009-09-24T11:45:32Z,0.99,1,46
63,2009-10-07T11:45:32Z,1.98,1,47
64,2009-10-07T11:45:32Z,1.98,1,49
65,2009-10-08T11:45:32Z,3.96,1,51
66,2009-10-09T11:45:32Z,5.94,1,55
67,2009-10-12T11:45:32Z,8.91,1,2
68,2009-10-17T11:45:32Z,13.86,1,11
69,2009-10-25T11:45:32Z,0.99,1,25
70,2009-11-07T11:45:32Z,1.98,1,26
71,2009-11-07T11:45:32Z,1.98,1,28
72,2009-11-08T11:45:32Z,3.96,1,30
73,2009-11-09T11:45:32Z,5.94,1,34
74,2009-11-12T11:45:32Z,8.91,1,40
75,2009-11-17T11:45:32Z,13.86,1,49
76,2009-11-25T11:45:32Z,0.99,1,4
77,2009-12-08T11:45:32Z,1.98,1,5
78,2009-12-08T11:45:32Z,1.98,1,7
79,2009-12-09T11:45:32Z,3.96,1,9
80,2009-12-10T11:45:32Z,5.94,1,13
81,2009-12-13T11:45:32Z,8.91,1,19
82,2009-12-18T11:45:32Z,13.86,1,28
83,2009-12-26T11:45:32Z,0.99,1,42
84,2010-01-08T11:45:32Z,1.98,1,43
85,2010-01-08T11:45:32Z,1.98,1,45
86,2010-01-09T11:45:32Z,3.96,1,47
87,2010-01-10T11:45:32Z,6.94,1,51
88,2010-01-13T11:45:32Z,17.91,1,57
89,2010-01-18T11:45:32Z,18.86,1,7
90,2010-01-26T11:45:32Z,0.99,1,21
91,2010-02-08T11:45:32Z,1.98,1,22
92,2010-02-08T11:45:32Z,1.98,1,24
93,2010-02-09T11:45:32Z,3.96,1,26
94,2010-02-10T11:45:32Z,5.94,1,30
95,2010-02-13T11:45:32Z,8.91,1,36
96,2010-02-18T11:45:32Z,21.86,1,45
97,2010-02-26T11:45:32Z,1.99,1,59
98,2010-03-11T11:45:32Z,3.98,1,1
99,2010-03-11T11:45:32Z,3.98,1,3
100,2010-03-12T11:45:32Z,3.96,1,5
101,2010-03-13T11:45:32Z,5.94,1,9
102,2010-03-16T11:45:32Z,9.91,1,15
103,2010-03-21T11:45:32Z,15.86,1,24
104,2010-03-29T11:45:32Z,0.99,1,38
105,2010-04-11T11:45:32Z,1.98,1,39
106,2010-04-11T11:45:32Z,1.98,1,41
107,2010-04-12T11:45:32Z,3.96,1,43
108,2010-04-13T11:45:32Z,5.94,1,47
109,2010-04-16T11:45:32Z,8.91,1,53
110,2010-04-21T11:45:32Z,13.86,1,3
111,2010-04-29T11:45:32Z,0.99,1,17
112,2010-05-12T11:45:32Z,1.98,1,18
113,2010-05-12T11:45:32Z,1.98,1,20
114,2010-05-13T11:45:32Z,3.96,1,22
115,2010-05-14T11:45:32Z,5.94,1,26
116,2010-05-17T11:45:32Z,8.91,1,32
117,2010-05-22T11:45:32Z,13.86,1,41
118,2010-05-30T11:45:32Z,0.99,1,55
119,2010-06-12T11:45:32Z,1.98,1,56
120,2010-06-12T11:45:32Z,1.98,1,58
121,2010-06-13T11:45:32Z,3.96,1,1
122,2010-06-14T11:45:32Z,5.94,1,5
123,2010-06-17T11:45:32Z,8.91,1,11
124,2010-06-22T11:45:32Z,13.86,1,20
125,2010-06-30T11:45:32Z,0.99,1,34
126,2010-07-13T11:45:32Z,1.98,1,35
127,2010-07-13T11:45:32Z,1.98,1,37
128,2010-07-14T11:45:32Z,3.96,1,39
129,2010-07-15T11:45:32Z,5.94,1,43
130,2010-07-18T11:45:32Z,8.91,1,49
131,2010-07-23T11:45:32Z,13.86,1,58
132,2010-07-31T11:45:32Z,0.99,1,13
133,2010-08-13T11:45:32Z,1.98,1,14
134,2010-08-13T11:45:32Z,1.98,1,16
135,2010-08-14T11:45:32Z,3.96,1,18
136,2010-08-15T11:45:32Z,5.94,1,22
137,2010-08-18T11:45:32Z,8.91,1,28
138,2010-08-23T11:45:32Z,13.86,1,37
139,2010-08-31T11:45:32Z,0.99,1,51
140,2010-09-13T11:45:32Z,1.98,1,52
141,2010-09-13T11:45:32Z,1.98,1,54
142,2010-09-14T11:45:32Z,3.96,1,56
143,2010-09-15T11:45:32Z,5.94,1,1
144,2010-09-18T11:45:32Z,8.91,1,7
145,2010-09-23T11:45:32Z,13.86,1,16
146,2010-10-01T11:45:32Z,0.99,1,30
147,2010-10-14T11:45:32Z,1.98,1,31
148,2010-10-14T11:45:32Z,1.98,1,33
149,2010-10-15T11:45:32Z,3.96,1,35
150,2010-10-16T11:45:32Z,5.94,1,39
151,2010-10-19T11:45:32Z,8.91,1,45
152,2010-10-24T11:45:32Z,13.86,1,54
153,2010-11-01T11:45:32Z,0.99,1,9
154,2010-11-14T11:45:32Z,1.98,1,10
155,2010-11-14T11:45:32Z,1.98,1,12
156,2010-11-15T11:45:32Z,3.96,1,14
157,2010-11-16T11:45:32Z,5.94,1,18
158,2010-11-19T11:45:32Z,8.91,1,24
159,2010-11-24T11:45:32Z,13.86,1,33
160,2010-12-02T11:45:32Z,0.99,1,47
161,2010-12-15T11:45:32Z,1.98,1,48
162,2010-12-15T11:45:32Z,1.98,1,50
163,2010-12-16T11:45:32Z,3.96,1,52
164,2010-12-17T11:45:32Z,5.94,1,56
165,2010-12-20T11:45:32Z,8.91,1,3
166,2010-12-25T11:45:32Z,13.86,1,12
167,2011-01-02T11:45:32Z,0.99,1,26
168,2011-01-15T11:45:32Z,1.98,1,27
169,2011-01-15T11:45:32Z,1.98,1,29
170,2011-01-16T11:45:32Z,3.96,1,31
171,2011-01-17T11:45:32Z,5.94,1,35
172,2011-01-20T11:45:32Z,8.91,1,41
173,2011-01-25T11:45:32Z,13.86,1,50
174,2011-02-02T11:45:32Z,0.99,1,5
175,2011-02-15T11:45:32Z,1.98,1,6
176,2011-02-15T11:45:32Z,1.98,1,8
177,2011-02-16T11:45:32Z,3.96,1,10
178,2011-02-17T11:45:32Z,5.94,1,14
179,2011-02-20T11:45:32Z,8.91,1,20
180,2011-02-25T11:45:32Z,13.86,1,29
181,2011-03-05T11:45:32Z,0.99,1,43
182,2011-03-18T11:45:32Z,1.98,1,44
183,2011-03-18T11:45:32Z,1.98,1,46
184,2011-03-19T11:45:32Z,3.96,1,48
185,2011-03-20T11:45:32Z,5.94,1,52
186,2011-03-23T11:45:32Z,8.91,1,58
187,2011-03-28T11:45:32Z,13.86,1,8
188,2011-04-05T11:45:32Z,0.99,1,22
189,2011-04-18T11:45:32Z,1.98,1,23
190,2011-04-18T11:45:32Z,1.98,1,25
191,2011-04-19T11:45:32Z,3.96,1,27
192,2011-04-20T11:45:32Z,5.94,1,31
193,2011-04-23T11:45:32Z,14.91,1,37
194,2011-04-28T11:45:32Z,21.86,1,46
195,2011-05-06T11:45:32Z,0.99,1,1
196,2011-05-19T11:45:32Z,1.98,1,2
197,2011-05-19T11:45:32Z,1.98,1,4
198,2011-05-20T11:45:32Z,3.96,1,6
199,2011-05-21T11:45:32Z,5.94,1,10
200,2011-05-24T11:45:32Z,8.91,1,16
201,2011-05-29T11:45:32Z,18.86,1,25
202,2011-06-06T11:45:32Z,1.99,1,39
203,2011-06-19T11:45:32Z,2.98,1,40
204,2011-06-19T11:45:32Z,3.98,1,42
205,2011-06-20T11:45:32Z,7.96,1,44
206,2011-06-21T11:45:32Z,8.94,1,48
207,2011-06-24T11:45:32Z,8.91,1,54
208,2011-06-29T11:45:32Z,15.86,1,4
209,2011-07-07T11:45:32Z,0.99,1,18
210,2011-07-20T11:45:32Z,1.98,1,19
211,2011-07-20T11:45:32Z,1.98,1,21
212,2011-07-21T11:45:32Z,3.96,1,23
213,2011-07-22T11:45:32Z,5.94,1,27
214,2011-07-25T11:45:32Z,8.91,1,33
215,2011-07-30T11:45:32Z,13.86,1,42
216,2011-08-07T11:45:32Z,0.99,1,56
217,2011-08-20T11:45:32Z,1.98,1,57
218,2011-08-20T11:45:32Z,1.98,1,59
219,2011-08-21T11:45:32Z,3.96,1,2
220,2011-08-22T11:45:32Z,5.94,1,6
221,2011-08-25T11:45:32Z,8.91,1,12
222,2011-08-30T11:45:32Z,13.86,1,21
223,2011-09-07T11:45:32Z,0.99,1,35
224,2011-09-20T11:45:32Z,1.98,1,36
225,2011-09-20T11:45:32Z,1.98,1,38
226,2011-09-21T11:45:32Z,3.96,1,40
227,2011-09-22T11:45:32Z,5.94,1,44
228,2011-09-25T11:45:32Z,8.91,1,50
229,2011-09-30T11:45:32Z,13.86,1,59
230,2011-10-08T11:45:32Z,0.99,1,14
231,2011-10-21T11:45:32Z,1.98,1,15
232,2011-10-21T11:45:32Z,1.98,1,17
233,2011-10-22T11:45:32Z,3.96,1,19
234,2011-10-23T11:45:32Z,5.94,1,23
235,2011-10-26T11:45:32Z,8.91,1,29
236,2011-10-31T11:45:32Z,13.86,1,38
237,2011-11-08T11:45:32Z,0.99,1,52
238,2011-11-21T11:45:32Z,1.98,1,53
239,2011-11-21T11:45:32Z,1.98,1,55
240,2011-11-22T11:45:32Z,3.96,1,57
241,2011-11-23T11:45:32Z,5.94,1,2
242,2011-11-26T11:45:32Z,8.91,1,8
243,2011-12-01T11:45:32Z,13.86,1,17
244,2011-12-09T11:45:32Z,0.99,1,31
245,2011-12-22T11:45:32Z,1.98,1,32
246,2011-12-22T11:45:32Z,1.98,1,34
247,2011-12-23T11:45:32Z,3.96,1,36
248,2011-12-24T11:45:32Z,5.94,1,40
249,2011-12-27T11:45:32Z,8.91,1,46
250,2012-01-01T11:45:32Z,13.86,1,55
251,2012-01-09T11:45:32Z,0.99,1,10
252,2012-01-22T11:45:32Z,1.98,1,11
253,2012-01-22T11:45:32Z,1.98,1,13
254,2012-01-23T11:45:32Z,3.96,1,15
255,2012-01-24T11:45:32Z,5.94,1,19
256,2012-01-27T11:45:32Z,8.91,1,25
257,2012-02-01T11:45:32Z,13.86,1,34
258,2012-02-09T11:45:32Z,0.99,1,48
259,2012-02-22T11:45:32Z,1.98,1,49
260,2012-02-22T11:45:32Z,1.98,1,51
261,2012-02-23T11:45:32Z,3.96,1,53
262,2012-02-24T11:45:32Z,5.94,1,57
263,2012-02-27T11:45:32Z,8.91,1,4
264,2012-03-03T11:45:32Z,13.86,1,13
265,2012-03-11T11:45:32Z,0.99,1,27
266,2012-03-24T11:45:32Z,1.98,1,28
267,2012-03-24T11:45:32Z,1.98,1,30
268,2012-03-25T11:45:32Z,3.96,1,32
269,2012-03-26T11:45:32Z,5.94,1,36
270,2012-03-29T11:45:32Z,8.91,1,42
271,2012-04-03T11:45:32Z,13.86,1,51
272,2012-04-11T11:45:32Z,0.99,1,6
273,2012-04-24T11:45:32Z,1.98,1,7
274,2012-04-24T11:45:32Z,1.98,1,9
275,2012-04-25T11:45:32Z,3.96,1,11
276,2012-04-26T11:45:32Z,5.94,1,15
277,2012-04-29T11:45:32Z,8.91,1,21
278,2012-05-04T11:45:32Z,13.86,1,30
279,2012-05-12T11:45:32Z,0.99,1,44
280,2012-05-25T11:45:32Z,1.98,1,45
281,2012-05-25T11:45:32Z,1.98,1,47
282,2012-05-26T11:45:32Z,3.96,1,49
283,2012-05-27T11:45:32Z,5.94,1,53
284,2012-05-30T11:45:32Z,8.91,1,59
285,2012-06-04T11:45:32Z,13.86,1,9
286,2012-06-12T11:45:32Z,0.99,1,23
287,2012-06-25T11:45:32Z,1.98,1,24
288,2012-06-25T11:45:32Z,1.98,1,26
289,2012-06-26T11:45:32Z,3.96,1,28
290,2012-06-27T11:45:32Z,5.94,1,32
291,2012-06-30T11:45:32Z,8.91,1,38
292,2012-07-05T11:45:32Z,13.86,1,47
293,2012-07-13T11:45:32Z,0.99,1,2
294,2012-07-26T11:45:32Z,1.98,1,3
295,2012-07-26T11:45:32Z,1.98,1,5
296,2012-07-27T11:45:32Z,3.96,1,7
297,2012-07-28T11:45:32Z,5.94,1,11
298,2012-07-31T11:45:32Z,10.91,1,17
299,2012-08-05T11:45:32Z,23.86,1,26
300,2012-08-13T11:45:32Z,0.99,1,40
301,2012-08-26T11:45:32Z,1.98,1,41
302,2012-08-26T11:45:32Z,1.98,1,43
303,2012-08-27T11:45:32Z,3.96,1,45
304,2012-08-28T11:45:32Z,5.94,1,49
305,2012-08-31T11:45:32Z,8.91,1,55
306,2012-09-05T11:45:32Z,16.86,1,5
307,2012-09-13T11:45:32Z,1.99,1,19
308,2012-09-26T11:45:32Z,3.98,1,20
309,2012-09-26T11:45:32Z,3.98,1,22
310,2012-09-27T11:45:32Z,7.96,1,24
311,2012-09-28T11:45:32Z,11.94,1,28
312,2012-10-01T11:45:32Z,10.91,1,34
313,2012-10-06T11:45:32Z,16.86,1,43
314,2012-10-14T11:45:32Z,0.99,1,57
315,2012-10-27T11:45:32Z,1.98,1,58
316,2012-10-27T11:45:32Z,1.98,1,1
317,2012-10-28T11:45:32Z,3.96,1,3
318,2012-10-29T11:45:32Z,5.94,1,7
319,2012-11-01T11:45:32Z,8.91,1,13
320,2012-11-06T11:45:32Z,13.86,1,22
321,2012-11-14T11:45:32Z,0.99,1,36
322,2012-11-27T11:45:32Z,1.98,1,37
323,2012-11-27T11:45:32Z,1.98,1,39
324,2012-11-28T11:45:32Z,3.96,1,41
325,2012-11-29T11:45:32Z,5.94,1,45
326,2012-12-02T11:45:32Z,8.91,1,51
327,2012-12-07T11:45:32Z,13.86,1,1
328,2012-12-15T11:45:32Z,0.99,1,15
329,2012-12-28T11:45:32Z,1.98,1,16
330,2012-12-28T11:45:32Z,1.98,1,18
331,2012-12-29T11:45:32Z,3.96,1,20
332,2012-12-30T11:45:32Z,5.94,1,24
333,2013-01-02T11:45:32Z,8.91,1,30
334,2013-01-07T11:45:32Z,13.86,1,39
335,2013-01-15T11:45:32Z,0.99,1,53
336,2013-01-28T11:45:32Z,1.98,1,54
337,2013-01-28T11:45:32Z,1.98,1,56
338,2013-01-29T11:45:32Z,3.96,1,58
339,2013-01-30T11:45:32Z,5.94,1,3
340,2013-02-02T11:45:32Z,8.91,1,9
341,2013-02-07T11:45:32Z,13.86,1,18
342,2013-02-15T11:45:32Z,0.99,1,32
343,2013-02-28T11:45:32Z,1.98,1,33
344,2013-02-28T11:45:32Z,1.98,1,35
345,2013-03-01T11:45:32Z,3.96,1,37
346,2013-03-02T11:45:32Z,5.94,1,41
347,2013-03-05T11:45:32Z,8.91,1,47
348,2013-03-10T11:45:32Z,13.86,1,56
349,2013-03-18T11:45:32Z,0.99,1,11
350,2013-03-31T11:45:32Z,1.98,1,12
351,2013-03-31T11:45:32Z,1.98,1,14
352,2013-04-01T11:45:32Z,3.96,1,16
353,2013-04-02T11:45:32Z,5.94,1,20
354,2013-04-05T11:45:32Z,8.91,1,26
355,2013-04-10T11:45:32Z,13.86,1,35
356,2013-04-18T11:45:32Z,0.99,1,49
357,2013-05-01T11:45:32Z,1.98,1,50
358,2013-05-01T11:45:32Z,1.98,1,52
359,2013-05-02T11:45:32Z,3.96,1,54
360,2013-05-03T11:45:32Z,5.94,1,58
361,2013-05-06T11:45:32Z,8.91,1,5
362,2013-05-11T11:45:32Z,13.86,1,14
363,2013-05-19T11:45:32Z,0.99,1,28
364,2013-06-01T11:45:32Z,1.98,1,29
365,2013-06-01T11:45:32Z,1.98,1,31
366,2013-06-02T11:45:32Z,3.96,1,33
367,2013-06-03T11:45:32Z,5.94,1,37
368,2013-06-06T11:45:32Z,8.91,1,43
369,2013-06-11T11:45:32Z,13.86,1,52
370,2013-06-19T11:45:32Z,0.99,1,7
371,2013-07-02T11:45:32Z,1.98,1,8
372,2013-07-02T11:45:32Z,1.98,1,10
373,2013-07-03T11:45:32Z,3.96,1,12
374,2013-07-04T11:45:32Z,5.94,1,16
375,2013-07-07T11:45:32Z,8.91,1,22
376,2013-07-12T11:45:32Z,13.86,1,31
377,2013-07-20T11:45:32Z,0.99,1,45
378,2013-08-02T11:45:32Z,1.98,1,46
379,2013-08-02T11:45:32Z,1.98,1,48
380,2013-08-03T11:45:32Z,3.96,1,50
381,2013-08-04T11:45:32Z,5.94,1,54
382,2013-08-07T11:45:32Z,8.91,1,1
383,2013-08-12T11:45:32Z,13.86,1,10
384,2013-08-20T11:45:32Z,0.99,1,24
385,2013-09-02T11:45:32Z,1.98,1,25
386,2013-09-02T11:45:32Z,1.98,1,27
387,2013-09-03T11:45:32Z,3.96,1,29
388,2013-09-04T11:45:32Z,5.94,1,33
389,2013-09-07T11:45:32Z,8.91,1,39
390,2013-09-12T11:45:32Z,13.86,1,48
391,2013-09-20T11:45:32Z,0.99,1,3
392,2013-10-03T11:45:32Z,1.98,1,4
393,2013-10-03T11:45:32Z,1.98,1,6
394,2013-10-04T11:45:32Z,3.96,1,8
395,2013-10-05T11:45:32Z,5.94,1,12
396,2013-10-08T11:45:32Z,8.91,1,18
397,2013-10-13T11:45:32Z,13.86,1,27
398,2013-10-21T11:45:32Z,0.99,1,41
399,2013-11-03T11:45:32Z,1.98,1,42
400,2013-11-03T11:45:32Z,1.98,1,44
401,2013-11-04T11:45:32Z,3.96,1,46
402,2013-11-05T11:45:32Z,5.94,1,50
403,2013-11-08T11:45:32Z,8.91,1,56
404,2013-11-13T11:45:32Z,25.86,1,6
405,2013-11-21T11:45:32Z,0.99,1,20
406,2013-12-04T11:45:32Z,1.98,1,21
407,2013-12-04T11:45:32Z,1.98,1,23
408,2013-12-05T11:45:32Z,3.96,1,25
409,2013-12-06T11:45:32Z,5.94,1,29
410,2013-12-09T11:45:32Z,8.91,1,35
411,2013-12-14T11:45:32Z,13.86,1,44
412,2013-12-22T11:45:32Z,1.99,1,58
1 ID invoiceDate total status customer_ID
2 1 2009-01-01T11:45:32Z 1.98 1 2
3 2 2009-01-02T11:45:32Z 3.96 1 4
4 3 2009-01-03T11:45:32Z 5.94 1 8
5 4 2009-01-06T11:45:32Z 8.91 1 14
6 5 2009-01-11T11:45:32Z 13.86 1 23
7 6 2009-01-19T11:45:32Z 0.99 1 37
8 7 2009-02-01T11:45:32Z 1.98 1 38
9 8 2009-02-01T11:45:32Z 1.98 1 40
10 9 2009-02-02T11:45:32Z 3.96 1 42
11 10 2009-02-03T11:45:32Z 5.94 1 46
12 11 2009-02-06T11:45:32Z 8.91 1 52
13 12 2009-02-11T11:45:32Z 13.86 1 2
14 13 2009-02-19T11:45:32Z 0.99 1 16
15 14 2009-03-04T11:45:32Z 1.98 1 17
16 15 2009-03-04T11:45:32Z 1.98 1 19
17 16 2009-03-05T11:45:32Z 3.96 1 21
18 17 2009-03-06T11:45:32Z 5.94 1 25
19 18 2009-03-09T11:45:32Z 8.91 1 31
20 19 2009-03-14T11:45:32Z 13.86 1 40
21 20 2009-03-22T11:45:32Z 0.99 1 54
22 21 2009-04-04T11:45:32Z 1.98 1 55
23 22 2009-04-04T11:45:32Z 1.98 1 57
24 23 2009-04-05T11:45:32Z 3.96 1 59
25 24 2009-04-06T11:45:32Z 5.94 1 4
26 25 2009-04-09T11:45:32Z 8.91 1 10
27 26 2009-04-14T11:45:32Z 13.86 1 19
28 27 2009-04-22T11:45:32Z 0.99 1 33
29 28 2009-05-05T11:45:32Z 1.98 1 34
30 29 2009-05-05T11:45:32Z 1.98 1 36
31 30 2009-05-06T11:45:32Z 3.96 1 38
32 31 2009-05-07T11:45:32Z 5.94 1 42
33 32 2009-05-10T11:45:32Z 8.91 1 48
34 33 2009-05-15T11:45:32Z 13.86 1 57
35 34 2009-05-23T11:45:32Z 0.99 1 12
36 35 2009-06-05T11:45:32Z 1.98 1 13
37 36 2009-06-05T11:45:32Z 1.98 1 15
38 37 2009-06-06T11:45:32Z 3.96 1 17
39 38 2009-06-07T11:45:32Z 5.94 1 21
40 39 2009-06-10T11:45:32Z 8.91 1 27
41 40 2009-06-15T11:45:32Z 13.86 1 36
42 41 2009-06-23T11:45:32Z 0.99 1 50
43 42 2009-07-06T11:45:32Z 1.98 1 51
44 43 2009-07-06T11:45:32Z 1.98 1 53
45 44 2009-07-07T11:45:32Z 3.96 1 55
46 45 2009-07-08T11:45:32Z 5.94 1 59
47 46 2009-07-11T11:45:32Z 8.91 1 6
48 47 2009-07-16T11:45:32Z 13.86 1 15
49 48 2009-07-24T11:45:32Z 0.99 1 29
50 49 2009-08-06T11:45:32Z 1.98 1 30
51 50 2009-08-06T11:45:32Z 1.98 1 32
52 51 2009-08-07T11:45:32Z 3.96 1 34
53 52 2009-08-08T11:45:32Z 5.94 1 38
54 53 2009-08-11T11:45:32Z 8.91 1 44
55 54 2009-08-16T11:45:32Z 13.86 1 53
56 55 2009-08-24T11:45:32Z 0.99 1 8
57 56 2009-09-06T11:45:32Z 1.98 1 9
58 57 2009-09-06T11:45:32Z 1.98 1 11
59 58 2009-09-07T11:45:32Z 3.96 1 13
60 59 2009-09-08T11:45:32Z 5.94 1 17
61 60 2009-09-11T11:45:32Z 8.91 1 23
62 61 2009-09-16T11:45:32Z 13.86 1 32
63 62 2009-09-24T11:45:32Z 0.99 1 46
64 63 2009-10-07T11:45:32Z 1.98 1 47
65 64 2009-10-07T11:45:32Z 1.98 1 49
66 65 2009-10-08T11:45:32Z 3.96 1 51
67 66 2009-10-09T11:45:32Z 5.94 1 55
68 67 2009-10-12T11:45:32Z 8.91 1 2
69 68 2009-10-17T11:45:32Z 13.86 1 11
70 69 2009-10-25T11:45:32Z 0.99 1 25
71 70 2009-11-07T11:45:32Z 1.98 1 26
72 71 2009-11-07T11:45:32Z 1.98 1 28
73 72 2009-11-08T11:45:32Z 3.96 1 30
74 73 2009-11-09T11:45:32Z 5.94 1 34
75 74 2009-11-12T11:45:32Z 8.91 1 40
76 75 2009-11-17T11:45:32Z 13.86 1 49
77 76 2009-11-25T11:45:32Z 0.99 1 4
78 77 2009-12-08T11:45:32Z 1.98 1 5
79 78 2009-12-08T11:45:32Z 1.98 1 7
80 79 2009-12-09T11:45:32Z 3.96 1 9
81 80 2009-12-10T11:45:32Z 5.94 1 13
82 81 2009-12-13T11:45:32Z 8.91 1 19
83 82 2009-12-18T11:45:32Z 13.86 1 28
84 83 2009-12-26T11:45:32Z 0.99 1 42
85 84 2010-01-08T11:45:32Z 1.98 1 43
86 85 2010-01-08T11:45:32Z 1.98 1 45
87 86 2010-01-09T11:45:32Z 3.96 1 47
88 87 2010-01-10T11:45:32Z 6.94 1 51
89 88 2010-01-13T11:45:32Z 17.91 1 57
90 89 2010-01-18T11:45:32Z 18.86 1 7
91 90 2010-01-26T11:45:32Z 0.99 1 21
92 91 2010-02-08T11:45:32Z 1.98 1 22
93 92 2010-02-08T11:45:32Z 1.98 1 24
94 93 2010-02-09T11:45:32Z 3.96 1 26
95 94 2010-02-10T11:45:32Z 5.94 1 30
96 95 2010-02-13T11:45:32Z 8.91 1 36
97 96 2010-02-18T11:45:32Z 21.86 1 45
98 97 2010-02-26T11:45:32Z 1.99 1 59
99 98 2010-03-11T11:45:32Z 3.98 1 1
100 99 2010-03-11T11:45:32Z 3.98 1 3
101 100 2010-03-12T11:45:32Z 3.96 1 5
102 101 2010-03-13T11:45:32Z 5.94 1 9
103 102 2010-03-16T11:45:32Z 9.91 1 15
104 103 2010-03-21T11:45:32Z 15.86 1 24
105 104 2010-03-29T11:45:32Z 0.99 1 38
106 105 2010-04-11T11:45:32Z 1.98 1 39
107 106 2010-04-11T11:45:32Z 1.98 1 41
108 107 2010-04-12T11:45:32Z 3.96 1 43
109 108 2010-04-13T11:45:32Z 5.94 1 47
110 109 2010-04-16T11:45:32Z 8.91 1 53
111 110 2010-04-21T11:45:32Z 13.86 1 3
112 111 2010-04-29T11:45:32Z 0.99 1 17
113 112 2010-05-12T11:45:32Z 1.98 1 18
114 113 2010-05-12T11:45:32Z 1.98 1 20
115 114 2010-05-13T11:45:32Z 3.96 1 22
116 115 2010-05-14T11:45:32Z 5.94 1 26
117 116 2010-05-17T11:45:32Z 8.91 1 32
118 117 2010-05-22T11:45:32Z 13.86 1 41
119 118 2010-05-30T11:45:32Z 0.99 1 55
120 119 2010-06-12T11:45:32Z 1.98 1 56
121 120 2010-06-12T11:45:32Z 1.98 1 58
122 121 2010-06-13T11:45:32Z 3.96 1 1
123 122 2010-06-14T11:45:32Z 5.94 1 5
124 123 2010-06-17T11:45:32Z 8.91 1 11
125 124 2010-06-22T11:45:32Z 13.86 1 20
126 125 2010-06-30T11:45:32Z 0.99 1 34
127 126 2010-07-13T11:45:32Z 1.98 1 35
128 127 2010-07-13T11:45:32Z 1.98 1 37
129 128 2010-07-14T11:45:32Z 3.96 1 39
130 129 2010-07-15T11:45:32Z 5.94 1 43
131 130 2010-07-18T11:45:32Z 8.91 1 49
132 131 2010-07-23T11:45:32Z 13.86 1 58
133 132 2010-07-31T11:45:32Z 0.99 1 13
134 133 2010-08-13T11:45:32Z 1.98 1 14
135 134 2010-08-13T11:45:32Z 1.98 1 16
136 135 2010-08-14T11:45:32Z 3.96 1 18
137 136 2010-08-15T11:45:32Z 5.94 1 22
138 137 2010-08-18T11:45:32Z 8.91 1 28
139 138 2010-08-23T11:45:32Z 13.86 1 37
140 139 2010-08-31T11:45:32Z 0.99 1 51
141 140 2010-09-13T11:45:32Z 1.98 1 52
142 141 2010-09-13T11:45:32Z 1.98 1 54
143 142 2010-09-14T11:45:32Z 3.96 1 56
144 143 2010-09-15T11:45:32Z 5.94 1 1
145 144 2010-09-18T11:45:32Z 8.91 1 7
146 145 2010-09-23T11:45:32Z 13.86 1 16
147 146 2010-10-01T11:45:32Z 0.99 1 30
148 147 2010-10-14T11:45:32Z 1.98 1 31
149 148 2010-10-14T11:45:32Z 1.98 1 33
150 149 2010-10-15T11:45:32Z 3.96 1 35
151 150 2010-10-16T11:45:32Z 5.94 1 39
152 151 2010-10-19T11:45:32Z 8.91 1 45
153 152 2010-10-24T11:45:32Z 13.86 1 54
154 153 2010-11-01T11:45:32Z 0.99 1 9
155 154 2010-11-14T11:45:32Z 1.98 1 10
156 155 2010-11-14T11:45:32Z 1.98 1 12
157 156 2010-11-15T11:45:32Z 3.96 1 14
158 157 2010-11-16T11:45:32Z 5.94 1 18
159 158 2010-11-19T11:45:32Z 8.91 1 24
160 159 2010-11-24T11:45:32Z 13.86 1 33
161 160 2010-12-02T11:45:32Z 0.99 1 47
162 161 2010-12-15T11:45:32Z 1.98 1 48
163 162 2010-12-15T11:45:32Z 1.98 1 50
164 163 2010-12-16T11:45:32Z 3.96 1 52
165 164 2010-12-17T11:45:32Z 5.94 1 56
166 165 2010-12-20T11:45:32Z 8.91 1 3
167 166 2010-12-25T11:45:32Z 13.86 1 12
168 167 2011-01-02T11:45:32Z 0.99 1 26
169 168 2011-01-15T11:45:32Z 1.98 1 27
170 169 2011-01-15T11:45:32Z 1.98 1 29
171 170 2011-01-16T11:45:32Z 3.96 1 31
172 171 2011-01-17T11:45:32Z 5.94 1 35
173 172 2011-01-20T11:45:32Z 8.91 1 41
174 173 2011-01-25T11:45:32Z 13.86 1 50
175 174 2011-02-02T11:45:32Z 0.99 1 5
176 175 2011-02-15T11:45:32Z 1.98 1 6
177 176 2011-02-15T11:45:32Z 1.98 1 8
178 177 2011-02-16T11:45:32Z 3.96 1 10
179 178 2011-02-17T11:45:32Z 5.94 1 14
180 179 2011-02-20T11:45:32Z 8.91 1 20
181 180 2011-02-25T11:45:32Z 13.86 1 29
182 181 2011-03-05T11:45:32Z 0.99 1 43
183 182 2011-03-18T11:45:32Z 1.98 1 44
184 183 2011-03-18T11:45:32Z 1.98 1 46
185 184 2011-03-19T11:45:32Z 3.96 1 48
186 185 2011-03-20T11:45:32Z 5.94 1 52
187 186 2011-03-23T11:45:32Z 8.91 1 58
188 187 2011-03-28T11:45:32Z 13.86 1 8
189 188 2011-04-05T11:45:32Z 0.99 1 22
190 189 2011-04-18T11:45:32Z 1.98 1 23
191 190 2011-04-18T11:45:32Z 1.98 1 25
192 191 2011-04-19T11:45:32Z 3.96 1 27
193 192 2011-04-20T11:45:32Z 5.94 1 31
194 193 2011-04-23T11:45:32Z 14.91 1 37
195 194 2011-04-28T11:45:32Z 21.86 1 46
196 195 2011-05-06T11:45:32Z 0.99 1 1
197 196 2011-05-19T11:45:32Z 1.98 1 2
198 197 2011-05-19T11:45:32Z 1.98 1 4
199 198 2011-05-20T11:45:32Z 3.96 1 6
200 199 2011-05-21T11:45:32Z 5.94 1 10
201 200 2011-05-24T11:45:32Z 8.91 1 16
202 201 2011-05-29T11:45:32Z 18.86 1 25
203 202 2011-06-06T11:45:32Z 1.99 1 39
204 203 2011-06-19T11:45:32Z 2.98 1 40
205 204 2011-06-19T11:45:32Z 3.98 1 42
206 205 2011-06-20T11:45:32Z 7.96 1 44
207 206 2011-06-21T11:45:32Z 8.94 1 48
208 207 2011-06-24T11:45:32Z 8.91 1 54
209 208 2011-06-29T11:45:32Z 15.86 1 4
210 209 2011-07-07T11:45:32Z 0.99 1 18
211 210 2011-07-20T11:45:32Z 1.98 1 19
212 211 2011-07-20T11:45:32Z 1.98 1 21
213 212 2011-07-21T11:45:32Z 3.96 1 23
214 213 2011-07-22T11:45:32Z 5.94 1 27
215 214 2011-07-25T11:45:32Z 8.91 1 33
216 215 2011-07-30T11:45:32Z 13.86 1 42
217 216 2011-08-07T11:45:32Z 0.99 1 56
218 217 2011-08-20T11:45:32Z 1.98 1 57
219 218 2011-08-20T11:45:32Z 1.98 1 59
220 219 2011-08-21T11:45:32Z 3.96 1 2
221 220 2011-08-22T11:45:32Z 5.94 1 6
222 221 2011-08-25T11:45:32Z 8.91 1 12
223 222 2011-08-30T11:45:32Z 13.86 1 21
224 223 2011-09-07T11:45:32Z 0.99 1 35
225 224 2011-09-20T11:45:32Z 1.98 1 36
226 225 2011-09-20T11:45:32Z 1.98 1 38
227 226 2011-09-21T11:45:32Z 3.96 1 40
228 227 2011-09-22T11:45:32Z 5.94 1 44
229 228 2011-09-25T11:45:32Z 8.91 1 50
230 229 2011-09-30T11:45:32Z 13.86 1 59
231 230 2011-10-08T11:45:32Z 0.99 1 14
232 231 2011-10-21T11:45:32Z 1.98 1 15
233 232 2011-10-21T11:45:32Z 1.98 1 17
234 233 2011-10-22T11:45:32Z 3.96 1 19
235 234 2011-10-23T11:45:32Z 5.94 1 23
236 235 2011-10-26T11:45:32Z 8.91 1 29
237 236 2011-10-31T11:45:32Z 13.86 1 38
238 237 2011-11-08T11:45:32Z 0.99 1 52
239 238 2011-11-21T11:45:32Z 1.98 1 53
240 239 2011-11-21T11:45:32Z 1.98 1 55
241 240 2011-11-22T11:45:32Z 3.96 1 57
242 241 2011-11-23T11:45:32Z 5.94 1 2
243 242 2011-11-26T11:45:32Z 8.91 1 8
244 243 2011-12-01T11:45:32Z 13.86 1 17
245 244 2011-12-09T11:45:32Z 0.99 1 31
246 245 2011-12-22T11:45:32Z 1.98 1 32
247 246 2011-12-22T11:45:32Z 1.98 1 34
248 247 2011-12-23T11:45:32Z 3.96 1 36
249 248 2011-12-24T11:45:32Z 5.94 1 40
250 249 2011-12-27T11:45:32Z 8.91 1 46
251 250 2012-01-01T11:45:32Z 13.86 1 55
252 251 2012-01-09T11:45:32Z 0.99 1 10
253 252 2012-01-22T11:45:32Z 1.98 1 11
254 253 2012-01-22T11:45:32Z 1.98 1 13
255 254 2012-01-23T11:45:32Z 3.96 1 15
256 255 2012-01-24T11:45:32Z 5.94 1 19
257 256 2012-01-27T11:45:32Z 8.91 1 25
258 257 2012-02-01T11:45:32Z 13.86 1 34
259 258 2012-02-09T11:45:32Z 0.99 1 48
260 259 2012-02-22T11:45:32Z 1.98 1 49
261 260 2012-02-22T11:45:32Z 1.98 1 51
262 261 2012-02-23T11:45:32Z 3.96 1 53
263 262 2012-02-24T11:45:32Z 5.94 1 57
264 263 2012-02-27T11:45:32Z 8.91 1 4
265 264 2012-03-03T11:45:32Z 13.86 1 13
266 265 2012-03-11T11:45:32Z 0.99 1 27
267 266 2012-03-24T11:45:32Z 1.98 1 28
268 267 2012-03-24T11:45:32Z 1.98 1 30
269 268 2012-03-25T11:45:32Z 3.96 1 32
270 269 2012-03-26T11:45:32Z 5.94 1 36
271 270 2012-03-29T11:45:32Z 8.91 1 42
272 271 2012-04-03T11:45:32Z 13.86 1 51
273 272 2012-04-11T11:45:32Z 0.99 1 6
274 273 2012-04-24T11:45:32Z 1.98 1 7
275 274 2012-04-24T11:45:32Z 1.98 1 9
276 275 2012-04-25T11:45:32Z 3.96 1 11
277 276 2012-04-26T11:45:32Z 5.94 1 15
278 277 2012-04-29T11:45:32Z 8.91 1 21
279 278 2012-05-04T11:45:32Z 13.86 1 30
280 279 2012-05-12T11:45:32Z 0.99 1 44
281 280 2012-05-25T11:45:32Z 1.98 1 45
282 281 2012-05-25T11:45:32Z 1.98 1 47
283 282 2012-05-26T11:45:32Z 3.96 1 49
284 283 2012-05-27T11:45:32Z 5.94 1 53
285 284 2012-05-30T11:45:32Z 8.91 1 59
286 285 2012-06-04T11:45:32Z 13.86 1 9
287 286 2012-06-12T11:45:32Z 0.99 1 23
288 287 2012-06-25T11:45:32Z 1.98 1 24
289 288 2012-06-25T11:45:32Z 1.98 1 26
290 289 2012-06-26T11:45:32Z 3.96 1 28
291 290 2012-06-27T11:45:32Z 5.94 1 32
292 291 2012-06-30T11:45:32Z 8.91 1 38
293 292 2012-07-05T11:45:32Z 13.86 1 47
294 293 2012-07-13T11:45:32Z 0.99 1 2
295 294 2012-07-26T11:45:32Z 1.98 1 3
296 295 2012-07-26T11:45:32Z 1.98 1 5
297 296 2012-07-27T11:45:32Z 3.96 1 7
298 297 2012-07-28T11:45:32Z 5.94 1 11
299 298 2012-07-31T11:45:32Z 10.91 1 17
300 299 2012-08-05T11:45:32Z 23.86 1 26
301 300 2012-08-13T11:45:32Z 0.99 1 40
302 301 2012-08-26T11:45:32Z 1.98 1 41
303 302 2012-08-26T11:45:32Z 1.98 1 43
304 303 2012-08-27T11:45:32Z 3.96 1 45
305 304 2012-08-28T11:45:32Z 5.94 1 49
306 305 2012-08-31T11:45:32Z 8.91 1 55
307 306 2012-09-05T11:45:32Z 16.86 1 5
308 307 2012-09-13T11:45:32Z 1.99 1 19
309 308 2012-09-26T11:45:32Z 3.98 1 20
310 309 2012-09-26T11:45:32Z 3.98 1 22
311 310 2012-09-27T11:45:32Z 7.96 1 24
312 311 2012-09-28T11:45:32Z 11.94 1 28
313 312 2012-10-01T11:45:32Z 10.91 1 34
314 313 2012-10-06T11:45:32Z 16.86 1 43
315 314 2012-10-14T11:45:32Z 0.99 1 57
316 315 2012-10-27T11:45:32Z 1.98 1 58
317 316 2012-10-27T11:45:32Z 1.98 1 1
318 317 2012-10-28T11:45:32Z 3.96 1 3
319 318 2012-10-29T11:45:32Z 5.94 1 7
320 319 2012-11-01T11:45:32Z 8.91 1 13
321 320 2012-11-06T11:45:32Z 13.86 1 22
322 321 2012-11-14T11:45:32Z 0.99 1 36
323 322 2012-11-27T11:45:32Z 1.98 1 37
324 323 2012-11-27T11:45:32Z 1.98 1 39
325 324 2012-11-28T11:45:32Z 3.96 1 41
326 325 2012-11-29T11:45:32Z 5.94 1 45
327 326 2012-12-02T11:45:32Z 8.91 1 51
328 327 2012-12-07T11:45:32Z 13.86 1 1
329 328 2012-12-15T11:45:32Z 0.99 1 15
330 329 2012-12-28T11:45:32Z 1.98 1 16
331 330 2012-12-28T11:45:32Z 1.98 1 18
332 331 2012-12-29T11:45:32Z 3.96 1 20
333 332 2012-12-30T11:45:32Z 5.94 1 24
334 333 2013-01-02T11:45:32Z 8.91 1 30
335 334 2013-01-07T11:45:32Z 13.86 1 39
336 335 2013-01-15T11:45:32Z 0.99 1 53
337 336 2013-01-28T11:45:32Z 1.98 1 54
338 337 2013-01-28T11:45:32Z 1.98 1 56
339 338 2013-01-29T11:45:32Z 3.96 1 58
340 339 2013-01-30T11:45:32Z 5.94 1 3
341 340 2013-02-02T11:45:32Z 8.91 1 9
342 341 2013-02-07T11:45:32Z 13.86 1 18
343 342 2013-02-15T11:45:32Z 0.99 1 32
344 343 2013-02-28T11:45:32Z 1.98 1 33
345 344 2013-02-28T11:45:32Z 1.98 1 35
346 345 2013-03-01T11:45:32Z 3.96 1 37
347 346 2013-03-02T11:45:32Z 5.94 1 41
348 347 2013-03-05T11:45:32Z 8.91 1 47
349 348 2013-03-10T11:45:32Z 13.86 1 56
350 349 2013-03-18T11:45:32Z 0.99 1 11
351 350 2013-03-31T11:45:32Z 1.98 1 12
352 351 2013-03-31T11:45:32Z 1.98 1 14
353 352 2013-04-01T11:45:32Z 3.96 1 16
354 353 2013-04-02T11:45:32Z 5.94 1 20
355 354 2013-04-05T11:45:32Z 8.91 1 26
356 355 2013-04-10T11:45:32Z 13.86 1 35
357 356 2013-04-18T11:45:32Z 0.99 1 49
358 357 2013-05-01T11:45:32Z 1.98 1 50
359 358 2013-05-01T11:45:32Z 1.98 1 52
360 359 2013-05-02T11:45:32Z 3.96 1 54
361 360 2013-05-03T11:45:32Z 5.94 1 58
362 361 2013-05-06T11:45:32Z 8.91 1 5
363 362 2013-05-11T11:45:32Z 13.86 1 14
364 363 2013-05-19T11:45:32Z 0.99 1 28
365 364 2013-06-01T11:45:32Z 1.98 1 29
366 365 2013-06-01T11:45:32Z 1.98 1 31
367 366 2013-06-02T11:45:32Z 3.96 1 33
368 367 2013-06-03T11:45:32Z 5.94 1 37
369 368 2013-06-06T11:45:32Z 8.91 1 43
370 369 2013-06-11T11:45:32Z 13.86 1 52
371 370 2013-06-19T11:45:32Z 0.99 1 7
372 371 2013-07-02T11:45:32Z 1.98 1 8
373 372 2013-07-02T11:45:32Z 1.98 1 10
374 373 2013-07-03T11:45:32Z 3.96 1 12
375 374 2013-07-04T11:45:32Z 5.94 1 16
376 375 2013-07-07T11:45:32Z 8.91 1 22
377 376 2013-07-12T11:45:32Z 13.86 1 31
378 377 2013-07-20T11:45:32Z 0.99 1 45
379 378 2013-08-02T11:45:32Z 1.98 1 46
380 379 2013-08-02T11:45:32Z 1.98 1 48
381 380 2013-08-03T11:45:32Z 3.96 1 50
382 381 2013-08-04T11:45:32Z 5.94 1 54
383 382 2013-08-07T11:45:32Z 8.91 1 1
384 383 2013-08-12T11:45:32Z 13.86 1 10
385 384 2013-08-20T11:45:32Z 0.99 1 24
386 385 2013-09-02T11:45:32Z 1.98 1 25
387 386 2013-09-02T11:45:32Z 1.98 1 27
388 387 2013-09-03T11:45:32Z 3.96 1 29
389 388 2013-09-04T11:45:32Z 5.94 1 33
390 389 2013-09-07T11:45:32Z 8.91 1 39
391 390 2013-09-12T11:45:32Z 13.86 1 48
392 391 2013-09-20T11:45:32Z 0.99 1 3
393 392 2013-10-03T11:45:32Z 1.98 1 4
394 393 2013-10-03T11:45:32Z 1.98 1 6
395 394 2013-10-04T11:45:32Z 3.96 1 8
396 395 2013-10-05T11:45:32Z 5.94 1 12
397 396 2013-10-08T11:45:32Z 8.91 1 18
398 397 2013-10-13T11:45:32Z 13.86 1 27
399 398 2013-10-21T11:45:32Z 0.99 1 41
400 399 2013-11-03T11:45:32Z 1.98 1 42
401 400 2013-11-03T11:45:32Z 1.98 1 44
402 401 2013-11-04T11:45:32Z 3.96 1 46
403 402 2013-11-05T11:45:32Z 5.94 1 50
404 403 2013-11-08T11:45:32Z 8.91 1 56
405 404 2013-11-13T11:45:32Z 25.86 1 6
406 405 2013-11-21T11:45:32Z 0.99 1 20
407 406 2013-12-04T11:45:32Z 1.98 1 21
408 407 2013-12-04T11:45:32Z 1.98 1 23
409 408 2013-12-05T11:45:32Z 3.96 1 25
410 409 2013-12-06T11:45:32Z 5.94 1 29
411 410 2013-12-09T11:45:32Z 8.91 1 35
412 411 2013-12-14T11:45:32Z 13.86 1 44
413 412 2013-12-22T11:45:32Z 1.99 1 58

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
ID,name
1,Music
2,Movies
3,TV Shows
4,Audiobooks
5,90s Music
6,Audiobooks
7,Movies
8,Music
9,Music Videos
10,TV Shows
11,Brazilian Music
12,Classical
13,Classical 101 - Deep Cuts
14,Classical 101 - Next Steps
15,Classical 101 - The Basics
16,Grunge
17,Heavy Metal Classic
18,On-The-Go 1
1 ID name
2 1 Music
3 2 Movies
4 3 TV Shows
5 4 Audiobooks
6 5 90’s Music
7 6 Audiobooks
8 7 Movies
9 8 Music
10 9 Music Videos
11 10 TV Shows
12 11 Brazilian Music
13 12 Classical
14 13 Classical 101 - Deep Cuts
15 14 Classical 101 - Next Steps
16 15 Classical 101 - The Basics
17 16 Grunge
18 17 Heavy Metal Classic
19 18 On-The-Go 1

File diff suppressed because it is too large Load Diff

94
chinook/db/schema.cds Normal file
View File

@@ -0,0 +1,94 @@
namespace sap.capire.media.store;
aspect Named {
key ID : Integer;
name : String(120);
}
aspect Person {
key ID : Integer;
lastName : String(20) default 'dummy';
firstName : String(40) default 'dummy';
city : String(40) default 'dummy';
address : String(70) default 'dummy';
country : String(40) default 'dummy';
phone : String(24) default 'dummy';
email : String(60) default 'dummy@email.com';
password : String(500) default 'dummy';
}
entity Genres {
key ID : Integer;
name : localized String;
tracks : Association to many Tracks
on tracks.genre = $self;
}
entity Playlists : Named {}
entity PlaylistTrack {
key playlist : Association to Playlists;
key track : Association to Tracks;
}
entity Artists : Named {}
entity Albums {
key ID : Integer;
title : String(120);
artist : Association to Artists;
tracks : Association to many Tracks
on tracks.album = $self;
}
entity Employees : Person {
reportsTo : Association to Employees;
title : String(20);
birthDate : DateTime;
hireDate : DateTime;
}
entity Customers : Person {
supportRep : Association to Employees;
invoices : Association to many Invoices
on invoices.customer = $self;
}
entity Invoices {
key ID : Integer;
customer : Association to Customers;
invoiceDate : DateTime;
total : Decimal(10, 2);
invoiceItems : Composition of many InvoiceItems
on invoiceItems.invoice = $self;
status : Integer enum {
submitted = 1;
canceled = -1;
} default 1;
}
entity InvoiceItems {
key ID : Integer;
invoice : Association to Invoices;
track : Association to Tracks;
unitPrice : Decimal(10, 2);
quantity : Integer default 1;
}
entity Tracks {
key ID : Integer;
name : String(200);
album : Association to Albums;
genre : Association to Genres;
composer : String(220);
unitPrice : Decimal(10, 2);
virtual alreadyOrdered : Boolean;
// Two compositions below needed for cascade delete track
invoiceItems : Composition of many InvoiceItems
on invoiceItems.track = $self;
playlistTracks : Composition of many {
key playlist : Association to Playlists;
key track : Association to Tracks;
};
};

128
chinook/mta.yaml Normal file
View File

@@ -0,0 +1,128 @@
## Generated mta.yaml based on template version 0.2.0
## appName = media-store-documentation-test
## language=nodejs; multiTenant=false
## approuter=
_schema-version: "3.1"
ID: media-store
version: 1.0.0
description: "A simple CAP project."
parameters:
enable-parallel-deployments: true
deploy_mode: html5-repo
build-parameters:
before-all:
- builder: custom
commands:
- npm install
- npx @sap/cds-dk build
modules:
# --------------------- SERVER MODULE ------------------------
- name: media-store-srv
# ------------------------------------------------------------
type: nodejs
path: gen/srv
properties:
EXIT: 1 # required by deploy.js task to terminate
# REVISIT: This is not a good practice -> don't do it that way, we just did it to save some time :)
ACCESS_TOKEN_SECRET: secret
REFRESH_TOKEN_SECRET: refresh-secret
requires:
# Resources extracted from CAP configuration
- name: media-store-hdi
provides:
- name: srv-binding # required by consumers of CAP services (e.g. approuter)
properties:
srv-url: ${default-url}
# -------------------- SIDECAR MODULE ------------------------
- name: media-store-db
# ------------------------------------------------------------
type: hdb
path: gen/db
parameters:
app-name: media-store-hdi
requires:
# 'hana' and 'xsuaa' resources extracted from CAP configuration
- name: media-store-hdi
# --------------------- HTML5DEPLOYER MODULE -----------------
- name: media-store-hmtl5-deployer
# ------------------------------------------------------------
type: com.sap.html5.application-content
path: app/deployers/html5Deployer
requires:
- name: media-store-html5-host
build-parameters:
requires:
- name: media-store-html5-app
artifacts:
- "./*"
target-path: resources/app
# --------------------- FRONTEND APP MODULE ---------------------
- name: media-store-html5-app
# ------------------------------------------------------------
type: html5
path: app/build
build-parameters:
supported-platforms: []
build-result: /
# --------------------- APPROUTER MODULE ---------------------
- name: media-store-approuter
# ------------------------------------------------------------
type: approuter.nodejs
path: app/deployers/approuter
requires:
- name: media-store-html5-runtime
- name: media-store-xsuaa
- name: srv-binding
group: destinations
properties:
name: srv-binding
url: ~{srv-url}
forwardAuthToken: true
resources:
# services extracted from CAP configuration
# 'service-plan' can be configured via 'cds.requires.<name>.vcap.plan'
# ------------------------------------------------------------
- name: media-store-hdi
# ------------------------------------------------------------
type: com.sap.xs.hdi-container
parameters:
service: hanatrial # or 'hanatrial' on trial landscapes
service-plan: hdi-shared
properties:
hdi-service-name: ${service-name}
# --------------------- HTML5 Runtime ----------------------
- name: media-store-html5-runtime
# ------------------------------------------------------------
parameters:
service-name: media-store-html5-runtime
service-plan: app-runtime
service: html5-apps-repo
type: org.cloudfoundry.managed-service
# --------------------- HTML5 Host -------------------------
- name: media-store-html5-host
# ------------------------------------------------------------
parameters:
service-name: media-store-html5-host
service-plan: app-host
service: html5-apps-repo
config:
sizeLimit: 2
type: org.cloudfoundry.managed-service
# --------------------- XSUAA Service ---------------------
- name: media-store-xsuaa
# ------------------------------------------------------------
parameters:
path: app/deployers/xs-security.json
service-plan: application
service: xsuaa
type: org.cloudfoundry.managed-service

47
chinook/package.json Normal file
View File

@@ -0,0 +1,47 @@
{
"name": "@capire/chinook",
"version": "1.0.0",
"description": "A simple CAP project.",
"repository": "<Add your repository here>",
"license": "UNLICENSED",
"private": true,
"files": [
"db", "srv", "server.js"
],
"dependencies": {
"@sap/cds": "^4.2.8",
"bcryptjs": "^2.4.3",
"express": "^4",
"hdb": "^0.18.2",
"jsonwebtoken": "^8.5.1",
"moment": "^2.29.1",
"passport": "^0.4.1"
},
"devDependencies": {
"sqlite3": "^5"
},
"scripts": {
"start": "npx cds run",
"deploy": "npx cds deploy",
"test": "npx mocha ../test/chinook.test.js --verbose --timeout 10000"
},
"cds": {
"folders": {
"app": "app/build"
},
"requires": {
"db": {
"kind": "sql",
"[development]": {
"model": "*",
"credentials": {
"database": "chinook.db"
}
}
},
"auth": {
"impl": "srv/auth.js"
}
}
}
}

21
chinook/server.js Normal file
View File

@@ -0,0 +1,21 @@
const cds = require("@sap/cds")
// Allow X-origin requests for React app during evelopment
if (cds.env.env === "development") {
cds.on("bootstrap", (app) => app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*")
res.header(
"Access-Control-Allow-Methods",
"GET, PUT, PATCH, POST, DELETE, OPTIONS"
)
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization, Accept-Language"
)
//intercept OPTIONS method
if (req.method === 'OPTIONS') res.sendStatus(200)
else next()
}))
}
module.exports = cds.server

28
chinook/srv/auth.js Normal file
View File

@@ -0,0 +1,28 @@
const cds = require("@sap/cds");
const jwt = require("jsonwebtoken");
const { ACCESS_TOKEN_SECRET } = process.env;
class MyUser extends cds.User {
constructor(attr, roles, id) {
super({ attr, _roles: [...roles], id });
}
}
module.exports = (req, res, next) => {
const { authorization: authHeader } = req.headers;
const token = authHeader && authHeader.split(" ")[1];
try {
const decodedUser = jwt.verify(token, ACCESS_TOKEN_SECRET);
req.user = new MyUser(
{ ID: decodedUser.ID },
[decodedUser.roles, "authenticated-user"],
decodedUser.email
);
} catch (error) {
req.user = new cds.User();
} finally {
next();
}
};

View File

@@ -0,0 +1,40 @@
using {sap.capire.media.store as my} from '../db/schema';
using {BrowseTracks.Tracks} from './browse-tracks-service';
service BrowseInvoices @(requires : 'customer') {
/**
* Invoices entity also restricted programmatically Only owned
* invoices youser can access
*/
@readonly
entity Invoices as projection on my.Invoices;
action invoice(tracks : array of {
ID : Integer;
});
action cancelInvoice(ID : Integer);
/**
* Below entities exposed due to 'navigation property errors'
* when expanding with odata
*/
@readonly
entity Tracks as projection on my.Tracks excluding {
alreadyOrdered
};
@readonly
entity Genres as projection on my.Genres {
* , tracks : redirected to Tracks
};
@readonly
entity Albums as projection on my.Albums {
* , tracks : redirected to Tracks
};
@readonly
entity Artists as projection on my.Artists;
}

View File

@@ -0,0 +1,124 @@
const cds = require("@sap/cds");
const moment = require("moment");
const LEVERAGE_DURATION = 1; // in hours. should be the same in the frontend
const CANCEL_STATUS = -1;
const SHIPPED_STATUS = 1;
const UTC_DATE_TIME_FORMAT = "YYYY-MM-DDThh:mm:ssZ";
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(
moment(utcNowTimestamp).diff(moment(invoiceDate).valueOf())
);
return duration.asHours() > LEVERAGE_DURATION;
};
module.exports = async function () {
const db = await cds.connect.to("db"); // connect to database service
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 }));
});
this.on("invoice", async (req) => {
const { tracks } = req.data;
const newInvoicedTrackIds = tracks.map(({ ID }) => ID);
const customerId = req.user.attr.ID;
const utcNowDateTime = moment().utc().format(UTC_DATE_TIME_FORMAT);
const transaction = await db.tx(req);
// check if already exists
const invoicedTracks = await transaction.run(
SELECT.from(InvoiceItems)
.columns("track_ID")
.where(
"invoice_ID in",
SELECT("ID").from(Invoices).where({
customer_ID: req.user.attr.ID,
status: SHIPPED_STATUS,
})
)
);
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" })
);
let { ID: lastInvoiceItemId } = await transaction.run(
SELECT.one(InvoiceItems).columns("ID").orderBy({ ID: "desc" })
);
// creating invoice
const newInvoiceId = ++lastInvoiceId;
await transaction.run(
INSERT.into(Invoices)
.columns("ID", "customer_ID", "total", "invoiceDate")
.values(newInvoiceId, customerId, total, utcNowDateTime)
);
// creating invoice items
await transaction.run(
INSERT.into(InvoiceItems)
.columns("ID", "invoice_ID", "track_ID", "unitPrice")
.rows(
newInvoicedTracks.map(({ ID: trackID, unitPrice }, index) => [
lastInvoiceItemId + index + 1,
newInvoiceId,
trackID,
unitPrice,
])
)
);
await transaction.commit();
});
this.on("cancelInvoice", async (req) => {
const { ID } = req.data;
const currentInvoice = await db.run(
SELECT.one(Invoices)
.where({
ID,
customer_ID: req.user.attr.ID,
})
.columns("ID", "invoiceDate", "customer_ID")
);
if (!currentInvoice) {
req.reject(
404,
"Seems like you are not owning this invoice or it is not exists"
);
}
const utcNowTimestamp = moment(
moment().utc().format(UTC_DATE_TIME_FORMAT)
).valueOf();
if (isLeverageTimeExpired(utcNowTimestamp, currentInvoice.invoiceDate)) {
req.reject(400, "Leverage time was expired");
}
return await db.run(
UPDATE(Invoices).set({ status: CANCEL_STATUS }).where({ ID })
);
});
};

View File

@@ -0,0 +1,31 @@
using {sap.capire.media.store as my} from '../db/schema';
service BrowseTracks {
@readonly
entity Tracks as projection on my.Tracks excluding {
alreadyOrdered
};
@readonly
entity MarkedTracks @(restrict : [{
grant : ['*'],
to : 'customer'
}]) as projection on my.Tracks;
/**
* Below entities exposed due to 'navigation property errors'
* when expanding with odata
*/
@readonly
entity Genres as projection on my.Genres {
* , tracks : redirected to Tracks
};
@readonly
entity Albums as projection on my.Albums {
* , tracks : redirected to Tracks
};
@readonly
entity Artists as projection on my.Artists;
}

View File

@@ -0,0 +1,33 @@
const cds = require("@sap/cds");
const SHIPPED_STATUS = 1;
module.exports = async function () {
const db = await cds.connect.to("db"); // connect to database service
const { Invoices, InvoiceItems } = db.entities;
this.on("READ", "MarkedTracks", async (req) => {
const invoiceItemEntries = await db.run(
SELECT.from(InvoiceItems)
.columns("track_ID")
.where(
"invoice_ID in",
SELECT("ID").from(Invoices).where({
customer_ID: req.user.attr.ID,
status: SHIPPED_STATUS,
})
)
);
const trackIds = invoiceItemEntries.map(({ track_ID }) => track_ID);
const result = [];
await db.foreach(req.query, (track) => {
result.push({
...track,
alreadyOrdered: trackIds.includes(track.ID),
});
});
return result;
});
};

View File

@@ -0,0 +1,12 @@
using {sap.capire.media.store as my} from '../db/schema';
service ManageStore @(requires : 'employee') {
entity Tracks as projection on my.Tracks;
entity Albums as projection on my.Albums;
entity Artists as projection on my.Artists;
/**
* Below entities exposed due to errors when creating
* Tracks/Albums/Artists
*/
entity Genres as projection on my.Genres;
}

View File

@@ -0,0 +1,24 @@
const cds = require("@sap/cds");
module.exports = async function () {
const db = await cds.connect.to("db"); // connect to database service
this.on("CREATE", "*", async (req) => {
const selectLastQuery = SELECT.one(req.entity).orderBy({ ID: "desc" });
const transaction = await db.tx(req);
let { ID: lastEntityID } = await transaction.run(selectLastQuery);
const columns = ["ID", ...Object.keys(req.data)];
const values = [++lastEntityID, ...Object.values(req.data)];
const insertQuery = INSERT.into(req.entity).columns(columns).values(values);
await transaction.run(insertQuery);
const result = await transaction.run(selectLastQuery);
await transaction.commit();
return result;
});
};

View File

@@ -0,0 +1,47 @@
using {sap.capire.media.store as my} from '../db/schema';
service Users {
/**
* Below entities also restricted programmatically. Only User
* can only access to yours record
*/
entity Customers as projection on my.Customers excluding {
password,
supportRep
};
entity Employees as projection on my.Employees excluding {
password,
reportsTo,
title,
birthDate,
hireDate
};
type AuthData {
accessToken : String(500);
refreshToken : String(500);
ID : Integer;
email : String(500);
roles : array of String(111);
};
action login(email : String(111), password : String(200)) returns AuthData;
action refreshTokens(refreshToken : String(500)) returns AuthData;
}
annotate Users.Customers with @(restrict : [{
grant : [
'READ',
'UPDATE'
],
to : 'customer'
}]);
annotate Users.Employees with @(restrict : [{
grant : [
'READ',
'UPDATE'
],
to : 'employee'
}]);

113
chinook/srv/user-service.js Normal file
View File

@@ -0,0 +1,113 @@
const cds = require("@sap/cds");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const { ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET } = process.env;
const ACCESS_TOKEN_EXP_IN = "10m";
const REFRESH_TOKEN_EXPIRES_IN = "20m";
const comparePasswords = async (password, hashedPassword) => {
return new Promise((resolve, reject) =>
bcrypt.compare(password, hashedPassword, (err, res) => {
if (err || res === false) {
reject(err);
} else {
resolve(res);
}
})
);
};
const createTokens = (email, ID, roles) => {
const accessToken = jwt.sign({ email, ID, roles }, ACCESS_TOKEN_SECRET, {
expiresIn: ACCESS_TOKEN_EXP_IN,
});
const refreshToken = jwt.sign({ email, ID, roles }, REFRESH_TOKEN_SECRET, {
expiresIn: REFRESH_TOKEN_EXPIRES_IN,
});
return [accessToken, refreshToken];
};
module.exports = async function () {
const db = await cds.connect.to("db");
const { Employees, Customers } = db.entities;
async function getUser(email) {
let userFromDb = await db.run(SELECT.one(Employees).where({ email }));
let roles = ["employee"];
if (!userFromDb) {
userFromDb = await db.run(SELECT.one(Customers).where({ email }));
roles = ["customer"];
}
return Object.assign({}, userFromDb, { roles });
}
/**
* User can only update and read his data
*/
this.before("UPDATE", "*", async (req) => {
req.query = req.query.where({ ID: req.user.attr.ID });
});
this.before("READ", "*", async (req) => {
req.query = req.query.where({ ID: req.user.attr.ID });
});
this.on("login", async (req) => {
const { email, password } = req.data;
const userFromDb = await getUser(email);
if (!userFromDb) {
req.reject(401);
}
try {
await comparePasswords(password, userFromDb.password);
} catch (error) {
req.reject(401);
}
const [accessToken, refreshToken] = createTokens(
userFromDb.email,
userFromDb.ID,
userFromDb.roles
);
return {
accessToken,
refreshToken,
ID: userFromDb.ID,
email: userFromDb.email,
roles: userFromDb.roles,
};
});
this.on("refreshTokens", async (req) => {
let decodedUser;
try {
const { refreshToken } = req.data;
decodedUser = jwt.verify(refreshToken, REFRESH_TOKEN_SECRET);
} catch (error) {
req.reject(401);
}
const userFromDb = await getUser(decodedUser.email);
if (!userFromDb) {
req.reject(401);
}
const [accessToken, refreshToken] = createTokens(
userFromDb.email,
userFromDb.ID,
userFromDb.roles
);
return {
accessToken,
refreshToken,
ID: userFromDb.ID,
email: userFromDb.email,
roles: userFromDb.roles,
};
});
};

View File

@@ -0,0 +1,96 @@
@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
### ------------------------------------------------------------------------
## Users service
### ------------------------------------------------------------------------
### ------------------------------------------------------------------------
# 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(1)
Authorization: Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imxlb25la29obGVyQHN1cmZldS5kZSIsIklEIjoyLCJyb2xlcyI6WyJjdXN0b21lciJdLCJpYXQiOjE2MDc5NTE2NDgsImV4cCI6MTYwNzk1MjI0OH0.4YqMxfY0KjOEA0iPvrZU5vfnsLcbFimxcamxgVxY4Ug
### ------------------------------------------------------------------------
# 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.eyJlbWFpbCI6Imxlb25la29obGVyQHN1cmZldS5kZSIsIklEIjoyLCJyb2xlcyI6WyJjdXN0b21lciJdLCJpYXQiOjE2MDc5Njg5ODIsImV4cCI6MTYwNzk2OTU4Mn0.Mq78megbpHa8ZyxhBPj7mwNs8Ttag6TeVekBKFDGR3w
### ------------------------------------------------------------------------
## Manage store service
### ------------------------------------------------------------------------
### ------------------------------------------------------------------------
# Crete new Album
POST {{manage-store-service}}/Artists
Content-Type: application/json
Authorization: Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFuZHJld0BjaGlub29rY29ycC5jb20iLCJJRCI6MSwicm9sZXMiOlsiZW1wbG95ZWUiXSwiaWF0IjoxNjA3NDQxMzQwLCJleHAiOjE2MDc0NDE5NDB9._JQzhqUwbutccoSWWeCZ2R16gLzzMD7b21bZ5wxN1gU
{
"name": "some"
}
### ------------------------------------------------------------------------
## Browse Tracks service
### ------------------------------------------------------------------------
### ------------------------------------------------------------------------
# Get browse-tracks-service
GET {{browse-tracks-service}}
### ------------------------------------------------------------------------
# Get $metadata document of browse-tracks-service
GET {{browse-tracks-service}}/$metadata
### ------------------------------------------------------------------------
# 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 Marked Trakcs
GET {{browse-tracks-service}}/MarkedTracks
Authorization: Basic eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imxlb25la29obGVyQHN1cmZldS5kZSIsIklEIjoyLCJyb2xlcyI6WyJjdXN0b21lciJdLCJpYXQiOjE2MDc5NjYxMTAsImV4cCI6MTYwNzk2NjcxMH0.VkxdhQth--kpxjb-X88N3H43kTtu5Uy0uVPsrQMx-ms

View File

@@ -6,6 +6,7 @@
"author": "daniel.hutzel@sap.com",
"dependencies": {
"@capire/bookshop": "./bookshop",
"@capire/chinook": "./chinook",
"@capire/common": "./common",
"@capire/fiori": "./fiori",
"@capire/hello": "./hello",
@@ -32,7 +33,8 @@
"parallel": true
},
"jest": {
"testEnvironment": "node"
"testEnvironment": "node",
"testTimeout": 10000
},
"license": "SAP SAMPLE CODE LICENSE",
"private": true

440
test/chinook.test.js Normal file
View File

@@ -0,0 +1,440 @@
const { GET, POST, expect } = require("../test").run("chinook");
const cds = require("@sap/cds/lib");
const {
FIRST_TRACK,
SECOND_CUSTOMER,
FOURTH_MARKED_TRACK_FOR_SECOND_CUSTOMER,
SECOND_CUSTOMER_INVOICES,
} = require("./data/chinook.mock");
const DEFAULT_AXIOS_CONFIG = {
headers: { "content-type": "application/json" },
};
async function resetStore() {
const targetCSNModel = await cds.load(["db", "srv"]);
const model = cds.reflect(targetCSNModel);
cds.db = await cds.deploy(model).to("db");
}
describe("Media Store services", () => {
const CURRENT_CUSTOMER_DATA = {
ID: 2,
email: "leonekohler@surfeu.de",
password: "some",
roles: ["customer"],
};
const CURRENT_EMPLOYEE_DATA = {
ID: 4,
email: "margaret@chinookcorp.com",
password: "some",
roles: ["employee"],
};
let customerAccessToken;
let employeeAccessToken;
before("login user", async () => {
customerLoginResponse = await POST(
"/users/login",
{
email: CURRENT_CUSTOMER_DATA.email,
password: CURRENT_CUSTOMER_DATA.password,
},
DEFAULT_AXIOS_CONFIG
);
customerAccessToken = customerLoginResponse.data.accessToken;
employeeLoginResponse = await POST(
"/users/login",
{
email: CURRENT_EMPLOYEE_DATA.email,
password: CURRENT_EMPLOYEE_DATA.password,
},
DEFAULT_AXIOS_CONFIG
);
employeeAccessToken = employeeLoginResponse.data.accessToken;
});
it("should bootstrap services successfully", () => {
const {
BrowseTracks,
BrowseInvoices,
ManageStore,
Users,
db,
} = cds.services;
const { Tracks } = BrowseTracks.entities;
expect(BrowseTracks).not.to.be.undefined;
expect(BrowseInvoices).not.to.be.undefined;
expect(ManageStore).not.to.be.undefined;
expect(Users).not.to.be.undefined;
expect(db).not.to.be.undefined;
expect(Tracks).not.to.be.undefined;
});
describe("Users", () => {
function compareAuthData(actualAuthData) {
expect(actualAuthData).not.to.be.undefined;
expect(actualAuthData).to.have.own.property("accessToken");
expect(actualAuthData).to.have.own.property("refreshToken");
expect(actualAuthData).to.have.own.property("ID");
expect(actualAuthData).to.have.own.property("email");
expect(actualAuthData).to.have.own.property("roles");
expect(actualAuthData.ID).to.equal(CURRENT_CUSTOMER_DATA.ID);
expect(actualAuthData.email).to.equal(CURRENT_CUSTOMER_DATA.email);
expect(actualAuthData.roles).to.deep.equal(CURRENT_CUSTOMER_DATA.roles);
}
it("should login user successfully", async () => {
const { data: actualData } = customerLoginResponse;
compareAuthData(actualData);
});
it("shouldn't login customer with invalid credentials", async () => {
try {
await POST(
"/users/login",
{
email: CURRENT_CUSTOMER_DATA.email,
password: "some-invalid-password",
},
DEFAULT_AXIOS_CONFIG
);
} catch (error) {
expect(error.message).to.equal("401 - Unauthorized");
}
});
it("should refresh tokens successfully", async () => {
const {
data: { refreshToken },
} = customerLoginResponse;
const { data: actualRefreshTokensData } = await POST(
"/users/refreshTokens",
{
refreshToken,
},
DEFAULT_AXIOS_CONFIG
);
compareAuthData(actualRefreshTokensData);
});
it("shouldn't refresh tokens due to invalid provided one", async () => {
try {
await POST(
"/users/refreshTokens",
{
refreshToken: "some-invalid-refresh-token",
},
DEFAULT_AXIOS_CONFIG
);
} catch (error) {
expect(error.message).to.equal("401 - Unauthorized");
}
});
it("current customer should retrieve his data", async () => {
const { data: actualData } = await GET(
`/users/Customers(${CURRENT_CUSTOMER_DATA.ID})`,
{
headers: {
authorization: "Basic " + customerAccessToken,
},
}
);
expect(actualData).not.to.be.undefined;
expect(actualData).to.deep.equal(SECOND_CUSTOMER);
});
it("current employee shouldn't have access to customer data", async () => {
const someCustomerID = 15;
try {
await GET(`/users/Customers(${someCustomerID})`, {
headers: {
authorization: "Basic " + employeeAccessToken,
},
});
} catch (error) {
expect(error.message).to.equal("403 - Forbidden");
}
});
it("current customer shouldn't retrieve his data without provided access token", async () => {
try {
await GET(`/users/Customers(11)`, DEFAULT_AXIOS_CONFIG);
} catch (error) {
expect(error.message).to.equal("401 - Unauthorized");
}
});
it("current customer shouldn't retrieve another customer data", async () => {
try {
await GET(`/users/Customers(11)`, {
headers: {
authorization: "Basic " + customerAccessToken,
},
});
} catch (error) {
expect(error.message).to.equal("404 - Not Found");
}
});
});
describe("BrowseTracks", () => {
it("should return track with ID eq 1", async () => {
const { data } = await GET(
"/browse-tracks/Tracks(1)?$expand=genre,album($expand=artist)"
);
expect(data).to.eql(FIRST_TRACK);
});
it("should return track with ID eq 4 for second customer", async () => {
const { data } = await GET("/browse-tracks/MarkedTracks(4)", {
headers: {
authorization: "Basic " + customerAccessToken,
},
});
expect(data).to.eql(FOURTH_MARKED_TRACK_FOR_SECOND_CUSTOMER);
});
});
describe("BrowseInvoices", () => {
const NEW_INVOICE_ID = 413;
const CANCELLED_STATUS = -1;
async function getAllCustomerInvoices() {
const { data } = await GET("/browse-invoices/Invoices", {
headers: {
authorization: "Basic " + customerAccessToken,
},
});
return data;
}
async function createInvoice(tracks) {
await POST(
"/browse-invoices/invoice",
{ tracks },
{
headers: {
...DEFAULT_AXIOS_CONFIG.headers,
authorization: "Basic " + customerAccessToken,
},
}
);
}
it("should return all invoices only for current customer", async () => {
const data = await getAllCustomerInvoices();
expect(data).to.eql(SECOND_CUSTOMER_INVOICES);
});
it("should create invoice for current customer", async () => {
const beforeData = await getAllCustomerInvoices();
expect(beforeData.value.length).to.equal(
SECOND_CUSTOMER_INVOICES.value.length
);
await createInvoice([{ ID: 3 }]);
const afterData = await getAllCustomerInvoices();
expect(afterData.value.length).to.equal(
SECOND_CUSTOMER_INVOICES.value.length + 1
);
});
it("should not create invoice due to current customer already owns some of provided tracks", async () => {
const ALREADY_OWNED_TRACK_ID = 4;
try {
await createInvoice([{ ID: ALREADY_OWNED_TRACK_ID }]);
} catch (error) {
expect(error.message).to.equal(
"400 - Invoice contains already owned values"
);
}
});
it("should cancel invoice for current customer", async () => {
await resetStore();
await createInvoice([{ ID: 3 }]);
const beforeData = await getAllCustomerInvoices();
expect(beforeData.value.length).to.equal(
SECOND_CUSTOMER_INVOICES.value.length + 1
);
await POST(
"/browse-invoices/cancelInvoice",
{ ID: NEW_INVOICE_ID },
{
headers: {
...DEFAULT_AXIOS_CONFIG.headers,
authorization: "Basic " + customerAccessToken,
},
}
);
const afterData = await getAllCustomerInvoices();
expect(afterData.value[afterData.value.length - 1].status).to.equal(
CANCELLED_STATUS
);
});
it("should not cancel invoice due to leverage time has expired", async () => {
await resetStore();
const beforeData = await getAllCustomerInvoices();
expect(beforeData.value.length).to.equal(
SECOND_CUSTOMER_INVOICES.value.length
);
try {
await POST(
"/browse-invoices/cancelInvoice",
{ ID: 12 },
{
headers: {
...DEFAULT_AXIOS_CONFIG.headers,
authorization: "Basic " + customerAccessToken,
},
}
);
} catch (error) {
expect(error.message).to.equal("400 - Leverage time was expired");
}
});
it("should not cancel invoice due to invoice with such ID si not belongs to current customer", async () => {
const NOT_OWNED_INVOICE_ID = 146;
try {
await POST(
"/browse-invoices/cancelInvoice",
{ ID: NOT_OWNED_INVOICE_ID },
{
headers: {
...DEFAULT_AXIOS_CONFIG.headers,
authorization: "Basic " + customerAccessToken,
},
}
);
} catch (error) {
expect(error.message).to.equal(
"404 - Seems like you are not owning this invoice or it is not exists"
);
}
});
});
describe("ManageStore", () => {
const NEW_TRACK_ID = 3504;
const newTrack = {
name: "Some track",
composer: "Some composer",
album: { ID: 14 },
genre: { ID: 15 },
unitPrice: "18.33",
};
async function createTrack(newTrack) {
await POST("/manage-store/Tracks", newTrack, {
headers: {
authorization: "Basic " + employeeAccessToken,
"content-type": "application/json;IEEE754Compatible=true",
},
});
}
async function getTrack(ID) {
return await GET(`/browse-tracks/Tracks(${ID})`);
}
it("should create new track", async () => {
await createTrack(newTrack);
const { data: createdTrack } = await getTrack(NEW_TRACK_ID);
expect(createdTrack).to.deep.equal({
"@odata.context": "$metadata#Tracks/$entity",
ID: NEW_TRACK_ID,
name: "Some track",
composer: "Some composer",
unitPrice: 18.33,
album_ID: 14,
genre_ID: 15,
});
});
it("customer should can create track", async () => {
try {
await POST("/manage-store/Tracks", newTrack, {
headers: {
authorization: "Basic " + customerAccessToken,
"content-type": "application/json;IEEE754Compatible=true",
},
});
} catch (error) {
expect(error.message).to.equal("403 - Forbidden");
}
});
it("should create new artist", async () => {
const NEW_ARTIST_ID = 276;
await POST(
"/manage-store/Artists",
{ name: "some" },
{
headers: {
authorization: "Basic " + employeeAccessToken,
...DEFAULT_AXIOS_CONFIG.headers,
},
}
);
const { data } = await GET(`/manage-store/Artists(${NEW_ARTIST_ID})`, {
headers: {
authorization: "Basic " + employeeAccessToken,
},
});
expect({
ID: NEW_ARTIST_ID,
name: "some",
"@odata.context": "$metadata#Artists/$entity",
}).to.deep.equal(data);
});
it("should create new artist", async () => {
const NEW_ALBUM_ID = 349;
await POST(
"/manage-store/Albums",
{ title: "some", artist: { ID: 235 } },
{
headers: {
authorization: "Basic " + employeeAccessToken,
...DEFAULT_AXIOS_CONFIG.headers,
},
}
);
const { data } = await GET(`/manage-store/Albums(${NEW_ALBUM_ID})`, {
headers: {
authorization: "Basic " + employeeAccessToken,
},
});
expect({
ID: NEW_ALBUM_ID,
title: "some",
artist_ID: 235,
"@odata.context": "$metadata#Albums/$entity",
}).to.deep.equal(data);
});
});
});

108
test/data/chinook.mock.js Normal file
View File

@@ -0,0 +1,108 @@
const FIRST_TRACK = {
"@odata.context": "$metadata#Tracks(genre(),album(artist()))/$entity",
ID: 1,
name: "For Those About To Rock (We Salute You)",
composer: "Angus Young, Malcolm Young, Brian Johnson",
unitPrice: 0.99,
album_ID: 1,
genre_ID: 1,
album: {
ID: 1,
title: "For Those About To Rock We Salute You",
artist_ID: 1,
artist: {
ID: 1,
name: "AC/DC",
},
},
genre: {
ID: 1,
name: "Rock",
},
};
const SECOND_CUSTOMER = {
"@odata.context": "$metadata#Customers/$entity",
ID: 2,
lastName: "Köhler",
firstName: "Leonie",
city: "Stuttgart",
address: "Theodor-Heuss-Straße 34",
country: "Germany",
phone: "+49 0711 2842222",
email: "leonekohler@surfeu.de",
};
const FOURTH_MARKED_TRACK_FOR_SECOND_CUSTOMER = {
"@odata.context": "$metadata#MarkedTracks/$entity",
ID: 4,
name: "Restless and Wild",
composer:
"F. Baltes, R.A. Smith-Diesel, S. Kaufman, U. Dirkscneider & W. Hoffman",
unitPrice: 0.99,
alreadyOrdered: true,
album_ID: 3,
genre_ID: 1,
};
const SECOND_CUSTOMER_INVOICES = {
"@odata.context": "$metadata#Invoices",
value: [
{
ID: 1,
invoiceDate: "2009-01-01T11:45:32Z",
total: 1.98,
status: 1,
customer_ID: 2,
},
{
ID: 12,
invoiceDate: "2009-02-11T11:45:32Z",
total: 13.86,
status: 1,
customer_ID: 2,
},
{
ID: 67,
invoiceDate: "2009-10-12T11:45:32Z",
total: 8.91,
status: 1,
customer_ID: 2,
},
{
ID: 196,
invoiceDate: "2011-05-19T11:45:32Z",
total: 1.98,
status: 1,
customer_ID: 2,
},
{
ID: 219,
invoiceDate: "2011-08-21T11:45:32Z",
total: 3.96,
status: 1,
customer_ID: 2,
},
{
ID: 241,
invoiceDate: "2011-11-23T11:45:32Z",
total: 5.94,
status: 1,
customer_ID: 2,
},
{
ID: 293,
invoiceDate: "2012-07-13T11:45:32Z",
total: 0.99,
status: 1,
customer_ID: 2,
},
],
};
module.exports = {
FIRST_TRACK,
SECOND_CUSTOMER,
FOURTH_MARKED_TRACK_FOR_SECOND_CUSTOMER,
SECOND_CUSTOMER_INVOICES,
};