Compare commits
187 Commits
d622fda7a9
...
v.0.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 409fa835a3 | |||
| 9506156070 | |||
| f98eb098f5 | |||
| d730280876 | |||
| 2fd045c590 | |||
| 57a036bea2 | |||
| cde3c84bc7 | |||
| 052bb7694a | |||
| 3c13bb88e1 | |||
| b69c63ea53 | |||
| 48c59936ec | |||
| d75e91481f | |||
| a4780d2545 | |||
| 0023313266 | |||
| e68d49abd5 | |||
| 0fdac7b2cf | |||
| ade0f8cd88 | |||
| 7884f5c57a | |||
| 4215bbf9c2 | |||
| 9df62d037d | |||
| c08645a294 | |||
| 1d49f210c5 | |||
| 878c21be4f | |||
| fb9e85552a | |||
| ea2fc7f6f4 | |||
| feca0934a3 | |||
| fd2a2dd345 | |||
| ba700eb050 | |||
| 7baa8e2b25 | |||
| 1f6ebf5b2e | |||
| 1a4b438191 | |||
| 51a2599545 | |||
| fe6f2840e9 | |||
| 17907a51be | |||
| bfc4b6681b | |||
| 85f34dd087 | |||
| f7cdaacceb | |||
| 628c1f97c0 | |||
| 836c5c5d1c | |||
| 4b4517cc4b | |||
| 5eab947b8c | |||
| cfc466b92e | |||
| 9b04e0ce69 | |||
| f923b34b77 | |||
| b347df7c6e | |||
| 9fa2b753ec | |||
| 22684fea44 | |||
| 24561fba23 | |||
| e2c45c6be0 | |||
| 9fbd9a1375 | |||
| d92533ab6a | |||
| c61a628ed4 | |||
| f4d5f54846 | |||
| c2fe6ee3d2 | |||
| 20b9a59257 | |||
| cb86720b13 | |||
| 5124ec4e6d | |||
| 860432ead3 | |||
| af26e64ad8 | |||
| b7291577b7 | |||
| 9875b99631 | |||
| 47992b92c9 | |||
| c5d18218c0 | |||
| 18235c4e4c | |||
| d762a9d8ab | |||
| 2c9b2cc509 | |||
| fbae1b9f3a | |||
| c2a337e051 | |||
| 33880069a0 | |||
| b892d93379 | |||
| b06b81f140 | |||
| 1088060580 | |||
| 07c4b7ba80 | |||
| 7e649240ca | |||
| a776431738 | |||
| 0844b3d5b5 | |||
| aeb28fe5a7 | |||
| 201714403c | |||
| 1f9987ba22 | |||
| 159b4434df | |||
| 98e2a3c3db | |||
| 370278fddc | |||
| 0c70e43c7d | |||
| e464b52394 | |||
| f8e1a191b3 | |||
| 57819f5a2f | |||
| 76a98b8a16 | |||
| db72a94468 | |||
| 37f9f50da3 | |||
| c5c5a2da8b | |||
| 093eba9af6 | |||
| 376a4ec385 | |||
| d0d4c469fb | |||
| 048a8c30ff | |||
| cf9a888a97 | |||
| 4b43757dd4 | |||
| 9140765772 | |||
| 780ab85a9e | |||
| b1b731f3d3 | |||
| 18a7814747 | |||
| ff36345382 | |||
| 7fc1f45574 | |||
| b490d058e4 | |||
| 60a9cea147 | |||
| 3204e4a658 | |||
| 60e217db03 | |||
| 7b991d2ff8 | |||
| 8f0de99634 | |||
| 8e7c9a949d | |||
| 461bc753e6 | |||
| ba752fa906 | |||
| 5f8adbaf10 | |||
| 8d78a9eeb7 | |||
| 23b9fa3dd2 | |||
| 48bfcc9c75 | |||
| f81e9be320 | |||
| 4a62c7a96b | |||
| c050560fba | |||
| c54e6f3743 | |||
| a676ce3d86 | |||
| 519fa210e9 | |||
| e0a8748184 | |||
| 0cf0c6be76 | |||
| ed4fa90f75 | |||
| c611cc04fc | |||
| 2b7e87a68d | |||
| 2cbee721c7 | |||
| 6c8d8dadaf | |||
| 142d574f78 | |||
| 04678f9913 | |||
| f898c0c5b9 | |||
| 422a5e1722 | |||
| 4bcc2b86d5 | |||
| 9b325c849e | |||
| 848e7abf92 | |||
| 2977c73a10 | |||
| e3863058a0 | |||
| 941fd711d5 | |||
| f5204578e4 | |||
| 787c5a61e5 | |||
| 080610cd7e | |||
| f41cf1ba90 | |||
| cbd01f6d59 | |||
| e00107ab6a | |||
| 14da64ecbe | |||
| 76c5e953a1 | |||
| 03ff8b402f | |||
| 22d3e8d177 | |||
| 0b34174ce9 | |||
| 8395b4427d | |||
| 87f3516b54 | |||
| 6aae064902 | |||
| a55248ecef | |||
| 98cc551f76 | |||
| eb84351231 | |||
| 1f48ec6ae1 | |||
| c27dc747b7 | |||
| 871f8cac7a | |||
| 88c43d62c6 | |||
| 58fcae647a | |||
| fbefa52e01 | |||
| ddc96bac13 | |||
| fbbfcdf365 | |||
| a254f99404 | |||
| 5134a9df31 | |||
| 0c20ef4366 | |||
| 463b49ba93 | |||
| ee07a5a5af | |||
| 628d1e7bee | |||
| c9a80a0a74 | |||
| 5a1b28dd60 | |||
| 40586f18b2 | |||
| dff6992db3 | |||
| b577ddf47d | |||
| ed99709afd | |||
| 3f061f0136 | |||
| b585ceb81b | |||
| 53d079c95f | |||
| c1208b5762 | |||
| 5fdf9315c9 | |||
| 4662a929a0 | |||
| 6464521b04 | |||
| 7ca15a66b3 | |||
| 955758ec4c | |||
| 64f3769953 | |||
| 4ec710e899 | |||
| 36c5e5b583 |
3
.vscode/settings.json
vendored
@@ -11,5 +11,6 @@
|
|||||||
"i18n-ally.enabledFrameworks": [
|
"i18n-ally.enabledFrameworks": [
|
||||||
"vue"
|
"vue"
|
||||||
],
|
],
|
||||||
"i18n-ally.keystyle": "nested"
|
"i18n-ally.keystyle": "nested",
|
||||||
|
"i18n-ally.extract.autoDetect": true,
|
||||||
}
|
}
|
||||||
51
CHANGELOG.md
@@ -1,4 +1,53 @@
|
|||||||
# v.0.1.0 (Alpha)
|
# v.0.4.0 MuC-Edition (2025-09-01)
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
|
||||||
|
- Exercise selection system
|
||||||
|
|
||||||
|
## 🌟 Enhancements
|
||||||
|
|
||||||
|
- Tooltips on append icons in toolbar
|
||||||
|
- Exercise page icon adds open exercises badge
|
||||||
|
- Finished english translation
|
||||||
|
- Icons on exercise groups on help page
|
||||||
|
- Welcome dialog: New page for look and feel, merge database and exercise creation in one step
|
||||||
|
- Add links to GitHub and project page on settings
|
||||||
|
- New section on homepage for popular genres
|
||||||
|
|
||||||
|
## 🐛 Bugfixes
|
||||||
|
|
||||||
|
- Filter on band page changes visible bands on homepage
|
||||||
|
- No startup after packaging
|
||||||
|
- Infinity loading on concert booking page is user comes from band page
|
||||||
|
|
||||||
|
# v.0.3.0 (2025-02-28)
|
||||||
|
## 🚀 Features
|
||||||
|
- Swagger Documentation
|
||||||
|
- RPM Image
|
||||||
|
|
||||||
|
## 🐛 Bugfixes
|
||||||
|
- Bugfix on search page for Band datasets
|
||||||
|
|
||||||
|
# v.0.2.0 (2024-12-05)
|
||||||
|
## 🚀 Features
|
||||||
|
- Adding "Test Environment" banner in the bottom right corner
|
||||||
|
- License handling system
|
||||||
|
- New SQL-Injection exercise 2.1
|
||||||
|
- Solution code based on Matrikelnummer and number of completed exercises
|
||||||
|
|
||||||
|
## 🌟 Enhancements
|
||||||
|
- Improve exercise solution of 2.1, 2.2, 2.3, 2.4 and 2.6
|
||||||
|
- Light mode improvements
|
||||||
|
- Global color schema
|
||||||
|
- More feedback through notifications
|
||||||
|
- More hints on text fields
|
||||||
|
- Redesign account pages, split payments and addresses, new dashboard
|
||||||
|
|
||||||
|
## 🐛 Bugfixes
|
||||||
|
- More server stability
|
||||||
|
- Bugfix file manager in Electron application
|
||||||
|
|
||||||
|
# v.0.1.0 (2024-11-21)
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
- Frontend
|
- Frontend
|
||||||
- VueJS frontend framework with Vuetify UI library
|
- VueJS frontend framework with Vuetify UI library
|
||||||
|
|||||||
539
README.md
@@ -1,30 +1,116 @@
|
|||||||
# HackMyCart
|
<p align="center">
|
||||||
|
<img src="misc/images/logo.png" width="300" />
|
||||||
|
</p>
|
||||||
|
|
||||||
The most hackable Web Shop!
|
<h1 align="center">EventMaster</h1>
|
||||||
|
<h2 align="center">The most hackable Ticket-Shop!</h2>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## About the project
|
||||||
|
|
||||||
|
EventMaster is a desktop application for simulating various hacking methods for web applications. It's the perfect tool to understand vulnerabilities in modern web applications.
|
||||||
|
|
||||||
|
The shop offers tickets for concerts of popular bands with a complete ordering system, account management and admin interface. The program has been deliberately provided with vulnerabilities for teaching purposes, e.g. SQL injections. If a task is solved and a vulnerability is identified, the system provides visual feedback. The progress can be viewed on an overview page.
|
||||||
|
|
||||||
|
This project is created during my Bachelor thesis. It will be used at Leibniz University Hannover for foundation lectures.
|
||||||
|
|
||||||
|
## Important notes!
|
||||||
|
- ⚠️ The project is under development, bugs may occur
|
||||||
|
- ⚠️ The software is developed for educational purposes, don't use the attack techniques without permission on other applications and systems!
|
||||||
|
- ⚠️ Works 100% offline, no data transmission to or from external services
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- 13 exercises
|
||||||
|
- 3 attack techniques
|
||||||
|
- SQL-Injections
|
||||||
|
- Cross-Site-Scripting
|
||||||
|
- Broken Access Control
|
||||||
|
- Exercise control system - check the progress and get feedback for solved exercises!
|
||||||
|
- Full working ticket shop:
|
||||||
|
- Vuetify design
|
||||||
|
- Account management system
|
||||||
|
- Order process system
|
||||||
|
- Event locations with seat plan and seat reservation system during ordering process
|
||||||
|
- Global search
|
||||||
|
- Admin panel
|
||||||
|
- Product pages
|
||||||
|
- ExpressJs-Backend server
|
||||||
|
- REST-API
|
||||||
|
- SQLite Database
|
||||||
|
- URL simulation bar for electron application
|
||||||
|
- Works 100% offline
|
||||||
|
- Multi language support (German, English)
|
||||||
|
- Dark/Light-Mode
|
||||||
|
- Exercise progress PDF generation
|
||||||
|
- API-Documentation with Swagger
|
||||||
|
- Desktop application packed with Electron
|
||||||
|
- Database can be resetted complete or partially
|
||||||
|
|
||||||
|
### Techstack
|
||||||
|
|
||||||
|
- Axios
|
||||||
|
- Electron
|
||||||
|
- Electron Builder
|
||||||
|
- Express.js
|
||||||
|
- JsonWebToken
|
||||||
|
- jspdf
|
||||||
|
- Moment.js
|
||||||
|
- NPM
|
||||||
|
- Nodemon
|
||||||
|
- Pinia
|
||||||
|
- Sequelize
|
||||||
|
- SQLite 3
|
||||||
|
- Swagger
|
||||||
|
- TypeScript
|
||||||
|
- Vite
|
||||||
|
- Vue i18n
|
||||||
|
- Vue.js
|
||||||
|
- VueRouter
|
||||||
|
- Vuetify
|
||||||
|
|
||||||
|
### Planned features
|
||||||
|
|
||||||
|
- More attack techniques
|
||||||
|
- More exercises
|
||||||
|
- More languages
|
||||||
|
- Customize the offered set of exercises (for trainer)
|
||||||
|
- Image license view system (currently only in admin panel)
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|
#### Band Detail page
|
||||||
|

|
||||||
|
|
||||||
|
#### Booking page
|
||||||
|

|
||||||
|
|
||||||
|
#### Admin panel
|
||||||
|

|
||||||
|
|
||||||
|
#### Global search
|
||||||
|

|
||||||
|
|
||||||
|
#### Feedback on exercise solution
|
||||||
|

|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
### Prepare development environment
|
### Download pre-builded images
|
||||||
|
|
||||||
1. Install node.js
|
Go to releases and download one of the pre-builded images for your operating system.
|
||||||
|
|
||||||
```bash
|
### Build yourself
|
||||||
sudo apt install npm
|
|
||||||
|
|
||||||
# If outdated version:
|
1. Download + extract the project
|
||||||
sudo npm install -g n
|
2. Open the root folder with VS Code (recommended)
|
||||||
sudo n stable
|
3. Open the bash inside VS Code and install all necessary packages:
|
||||||
```
|
|
||||||
|
|
||||||
2. Download + extract the project
|
|
||||||
3. Open the root folder with VS Code (recommended)
|
|
||||||
4. Open the bash inside VS Code and install all necessary packages:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm i
|
npm i
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test/development
|
#### Test/development
|
||||||
|
|
||||||
There are multiple commands to test parts or the whole project:
|
There are multiple commands to test parts or the whole project:
|
||||||
|
|
||||||
@@ -34,7 +120,7 @@ There are multiple commands to test parts or the whole project:
|
|||||||
|
|
||||||
The frontend runs on `http://localhost:5173/` and the backend on `http://localhost:3000/`
|
The frontend runs on `http://localhost:5173/` and the backend on `http://localhost:3000/`
|
||||||
|
|
||||||
### Build
|
#### Build
|
||||||
|
|
||||||
- `npm run vite:build`: Build Vue frontend only
|
- `npm run vite:build`: Build Vue frontend only
|
||||||
- `npm run server:build`: Build ExpressJs backend only
|
- `npm run server:build`: Build ExpressJs backend only
|
||||||
@@ -48,423 +134,6 @@ The frontend runs on `http://localhost:5173/` and the backend on `http://localho
|
|||||||
### Database
|
### Database
|
||||||

|

|
||||||
|
|
||||||
|
### Frontend-Backend-System
|
||||||
|
|
||||||
### Backend API endpoints
|

|
||||||
|
|
||||||
The application host it's data in a SQLite database. The access is managed by an [ExpressJs](https://expressjs.com/) server which offers many REST-API endpoints for the frontend. The REST-API server runs on port 3000.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Listing existing
|
|
||||||
|
|
||||||
<details open>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/accounts/</b></code> <code> (Get all Accounts)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Account + AccountRole>` |
|
|
||||||
|
|
||||||
##### Example Response
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 421,
|
|
||||||
"username": "hagemeister93",
|
|
||||||
"password": "Xjt3qb5t",
|
|
||||||
"email": "hagemeister93@gmail.com",
|
|
||||||
"firstName": "Laurin",
|
|
||||||
"lastName": "Hagemeister",
|
|
||||||
"accountRoleId": 2,
|
|
||||||
"accountRole": {
|
|
||||||
"id": 2,
|
|
||||||
"name": "Admin",
|
|
||||||
"privilegeBuy": true,
|
|
||||||
"privilegeAdminPanel": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details open>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/api/files</b></code> <code> (Get all public files)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<{folder: String, files: Array<{name: String, size: Number, url: String}> }>` |
|
|
||||||
|
|
||||||
##### Example Response
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"folder": "artists",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"name": "alex-turner.jpg",
|
|
||||||
"size": 56473,
|
|
||||||
"url": "http://localhost:3000/static/artists/alex-turner.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "andy-nicholson.jpg",
|
|
||||||
"size": 68983,
|
|
||||||
"url": "http://localhost:3000/static/artists/andy-nicholson.jpg"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details open>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/bands/</b></code> <code> (Get all bands)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | `sort` | optional | string | Sort by number of concerts ascending (asc) or descending (desc) |
|
|
||||||
> | `count` | optional | number | Number of items to responde |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<>` |
|
|
||||||
|
|
||||||
##### Example Response
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"folder": "artists",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"name": "alex-turner.jpg",
|
|
||||||
"size": 56473,
|
|
||||||
"url": "http://localhost:3000/static/artists/alex-turner.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "andy-nicholson.jpg",
|
|
||||||
"size": 68983,
|
|
||||||
"url": "http://localhost:3000/static/artists/andy-nicholson.jpg"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/events?city=cityName&genre=genreName&count=nrOfItems&sort=sortDirection</b></code> <code> (Get all events, filtered by city and genre)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | `cityName` | optional | string | Name of the city to filter for |
|
|
||||||
> | `genreName` | optional | string | Name of the genre to filter for |
|
|
||||||
> | `nrOfItems` | optional | number | Limits number of results |
|
|
||||||
> | `sortDirection` | optional | string | Sort by number of concerts, 'asc' or 'desc' |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Event + Array<Concert + Location + City> + Band & Genre>` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/locations?count=nrOfItems&sort=sortDirection</b></code> <code> (Get all locations)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | `nrOfItems` | optional | number | Limits number of results |
|
|
||||||
> | `sortDirection` | optional | string | Sort by number of concerts, 'asc' or 'desc' |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Location + City + Array<Concert + Event>>` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
Down here: todo!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/bands</b></code> <code> (Get all bands)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Band>` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/bands/:id</b></code> <code> (Get all information about one band)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | `id` | required | string | ID of product in the database |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Band` + `Array<Rating>` + `Array<Member>` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/shows/:id</b></code> <code> (Get all information about one show)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | `id` | required | string | ID of product in the database |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Show` + `Tour` + `Location` + `City` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/tours</b></code> <code> (Get all tours)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Tours>` + `Band` + `Show` + `Location` + `City` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/genres</b></code> <code> (Get all genres)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Genre>` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/orders/:id</b></code> <code> (Get all orders of an user)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | --- | --- | --- | --- |
|
|
||||||
> | `id` | required | string | ID of userAccount in the database |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `ProductModel` + `Order`, `OrderItem`, `Product` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/categories/</b></code> <code> (Get all Categories)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Categories>` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/brands</b></code> <code> (Get all Brands)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `Array<Brand>` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
#### Creating new
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#69CA92"><b>POST</b></span></code> <code><b>/accounts/</b></code> <code> (Create a new account)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | None | required | object (JSON) | Model of an Account |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `201` | `application/json` | `AccountModel` |
|
|
||||||
> | `400` | `application/json` | `{code: 400, message: "Username too short!"}` |
|
|
||||||
> | `400` | `application/json` | `{code: 400, message: "Password too short!"}` |
|
|
||||||
> | `409` | `application/json` | `{code: 409, message: "Username already in use"}` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#69CA92"><b>POST</b></span></code> <code><b>/orders/</b></code> <code> (Create a new order)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | None | required | object (JSON) | Model of an Order |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `201` | `application/json` | `OrderModel` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Updating existing
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#F3A63D"><b>PATCH</b></span></code> <code><b>/accounts/</b></code> <code> (Update data of an existing account)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | None | required | object (JSON) | Model of an Account |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | AccountModel |
|
|
||||||
> | `400` | `application/json` | `{code: 400, message: "..."}` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Delete existing
|
|
||||||
|
|
||||||
<!-- <details>
|
|
||||||
<summary><code><span style="color:#EB5246"><b>DELETE</b></span></code> <code><b>/product/:id</b></code> <code> (Delete a product)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | :---: | --- | --- | --- |
|
|
||||||
> | `id` | required | string | ID of product in the database |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `ProductModel` |
|
|
||||||
> | `400` | `application/json` | `{code: 400, message: "..."}` |
|
|
||||||
</details> -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Miscs
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/api/</b></code> <code> (Check if server runs)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | None | None |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#70AFFD"><b>GET</b></span></code> <code><b>/resetDatabase/</b></code> <code> (Reset the database to it's default values)</code>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> None
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | None | None |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
#### Validate
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><code><span style="color:#69CA92"><b>POST</b></span></code> <code><b>/accounts/login</b></code> <code> (Login for user)</code></summary>
|
|
||||||
|
|
||||||
##### Parameters
|
|
||||||
> | name | type | data type | description |
|
|
||||||
> | --- | --- | --- | --- |
|
|
||||||
> | username | required | string | Username of the account |
|
|
||||||
> | password | required | string | Password of the account |
|
|
||||||
|
|
||||||
##### Responses
|
|
||||||
> | http code | content-type | response |
|
|
||||||
> | :---: | --- | --- |
|
|
||||||
> | `200` | `application/json` | `AccountObject` + `Addresses`, `Payments`, `AccountRole` |
|
|
||||||
> | `400` | `application/json` | `{code: 400, message: "Bad Request"}` |
|
|
||||||
> | `401` | `application/json` | `{code: 401, message: "Unauthorized"}` |
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
@@ -3,127 +3,144 @@
|
|||||||
{
|
{
|
||||||
"nameDe": "Den Shop kennenlernen",
|
"nameDe": "Den Shop kennenlernen",
|
||||||
"nameEn": "Getting to know the shop",
|
"nameEn": "Getting to know the shop",
|
||||||
|
"icon": "mdi-human-greeting",
|
||||||
"groupNr": 0,
|
"groupNr": 0,
|
||||||
"descriptionDe": "Vor einem Angriff ist es wichtig zu verstehen, wie die Webseite aufgebaut ist. Wie sind die URLs strukturiert? Wo befinden sich Eingabefelder welche im Backend eine SQL Abfrage stellen?",
|
"descriptionDe": "Vor einem Angriff ist es wichtig zu verstehen, wie die Webseite aufgebaut ist. Wie sind die URLs strukturiert? Wo befinden sich Eingabefelder welche im Backend eine SQL Abfrage stellen?",
|
||||||
"descriptionEn": "todo",
|
"descriptionEn": "Before an attack, it's important to understand how the website is structured. How are the URLs structured? Where are input fields located that execute SQL queries in the backend?",
|
||||||
"exercises": [
|
"exercises": [
|
||||||
{
|
{
|
||||||
|
"uuid": "getting-known-register",
|
||||||
"nameDe": "Registrieren",
|
"nameDe": "Registrieren",
|
||||||
"nameEn": "Register",
|
"nameEn": "Register",
|
||||||
"exerciseNr": 1,
|
"exerciseNr": 1,
|
||||||
"descriptionDe": "Wir richten uns einen gewöhnlichen Account auf der Plattform ein. Navigiere hierzu auf die Account-Seite und registriere dich.",
|
"descriptionDe": "Wir richten uns einen gewöhnlichen Account auf der Plattform ein. Navigiere hierzu auf die Account-Seite und registriere dich.",
|
||||||
"descriptionEn": "Create a new account in the online shop"
|
"descriptionEn": "We'll set up a regular account on the platform. To do this, navigate to the account page and register."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "getting-known-profile",
|
||||||
"nameDe": "Profil vervollständigen",
|
"nameDe": "Profil vervollständigen",
|
||||||
"nameEn": "Complete profile",
|
"nameEn": "Complete profile",
|
||||||
"exerciseNr": 2,
|
"exerciseNr": 2,
|
||||||
"descriptionDe": "Bestellungen sind erst möglich, wenn das Account-Profil vervollständigt ist. Logge dich ein, navigiere zu den Account-Einstellungen, fülle den Namen aus und füge je eine Adresse und Bezahlart hinzu. Speichere alles zum Schluss ab.",
|
"descriptionDe": "Bestellungen sind erst möglich, wenn das Account-Profil vervollständigt ist. Logge dich ein, navigiere zu den Account-Einstellungen, fülle den Namen aus und füge je eine Adresse und Bezahlart hinzu. Speichere alles zum Schluss ab.",
|
||||||
"descriptionEn": "Search for an event of choice and buy a ticket for"
|
"descriptionEn": "Orders are only possible once your account profile is complete. Log in, navigate to your account settings, fill in your name, and add an address and payment method. Finally, save everything."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "getting-known-buy-ticket",
|
||||||
"nameDe": "Ein Ticket kaufen",
|
"nameDe": "Ein Ticket kaufen",
|
||||||
"nameEn": "Buy a ticket",
|
"nameEn": "Buy a ticket",
|
||||||
"exerciseNr": 3,
|
"exerciseNr": 3,
|
||||||
"descriptionDe": "Wir führen nun einen Bestellvorgang durch. Wähle hierzu ein Konzert deiner Wahl und lege Tickets in den Warenkorb. Öffne diesen und schließe die Bestellung ab. Beachte die Struktur der URL wenn du ein Konzert buchen willst. Sieh dir ruhig 2-3 Buchungsseiten an, wie sich die URL jeweils verändert.",
|
"descriptionDe": "Wir führen nun einen Bestellvorgang durch. Wähle hierzu ein Konzert deiner Wahl und lege Tickets in den Warenkorb. Öffne diesen und schließe die Bestellung ab. Beachte die Struktur der URL wenn du ein Konzert buchen willst. Sieh dir ruhig 2-3 Buchungsseiten an, wie sich die URL jeweils verändert.",
|
||||||
"descriptionEn": "Search for an event of choice and buy a ticket for"
|
"descriptionEn": "We'll now complete the order process. Select a concert of your choice and add tickets to your shopping cart. Open the shopping cart and complete your order. Pay attention to the URL structure when booking a concert. Feel free to look at two or three booking pages to see how the URL changes each time."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nameDe": "Broken Access Control",
|
"nameDe": "Broken Access Control",
|
||||||
"nameEn": "Broken Access Control",
|
"nameEn": "Broken Access Control",
|
||||||
|
"icon": "mdi-application-outline",
|
||||||
"groupNr": 1,
|
"groupNr": 1,
|
||||||
"descriptionDe": "Eine Webseite beinhaltet öffentlich einsehbare und einige geschützte Seiten. Letztere sind nur mit passenden Berechtigungen erreichbar. Beispiele hierfür sind ein Admin-Panel oder der persönliche Warenkorb. Der Zugriff wird oft über Cookies oder eine Authentifizierung an einem Backend-Server geregelt. Bei Broken Access Control ist dieser Sicherheits-Mechanismus nicht oder fehlerhaft implementiert. Somit lassen sich Seiten unberechtigterweise über die URL erreichen.",
|
"descriptionDe": "Eine Webseite beinhaltet öffentlich einsehbare und einige geschützte Seiten. Letztere sind nur mit passenden Berechtigungen erreichbar. Beispiele hierfür sind ein Admin-Panel oder der persönliche Warenkorb. Der Zugriff wird oft über Cookies oder eine Authentifizierung an einem Backend-Server geregelt. Bei Broken Access Control ist dieser Sicherheits-Mechanismus nicht oder fehlerhaft implementiert. Somit lassen sich Seiten unberechtigterweise über die URL erreichen.",
|
||||||
"descriptionEn": "todo",
|
"descriptionEn": "A website contains publicly visible pages and some protected pages. The latter can only be accessed with appropriate permissions. Examples include an admin panel or the personal shopping cart. Access is often controlled via cookies or authentication on a backend server. With broken access control, this security mechanism is either not implemented or is incorrectly implemented. This allows pages to be accessed without authorization via the URL.",
|
||||||
"exercises": [
|
"exercises": [
|
||||||
{
|
{
|
||||||
|
"uuid": "broken-access-control-exercise-page",
|
||||||
"nameDe": "Hilfe-Seite aufrufen",
|
"nameDe": "Hilfe-Seite aufrufen",
|
||||||
"nameEn": "Access Help Page",
|
"nameEn": "Access Help Page",
|
||||||
"exerciseNr": 1,
|
"exerciseNr": 1,
|
||||||
"descriptionDe": "Die Hilfe-Seite erlaubt dir einen Einblick auf den Bearbeitungszustand der Aufgaben. Sie ist dementsprechend nicht abgesichert, aber auch (noch) nicht in der Titel-Leiste als Button erreichbar. Erweitere die URL in der Adresszeile so, dass du auf die Hilfeseite gelangst.",
|
"descriptionDe": "Die Hilfe-Seite erlaubt dir einen Einblick auf den Bearbeitungszustand der Aufgaben. Sie ist dementsprechend nicht abgesichert, aber auch (noch) nicht in der Titel-Leiste als Button erreichbar. Erweitere die URL in der Adresszeile so, dass du auf die Hilfeseite gelangst.",
|
||||||
"descriptionEn": "Manipulate the URL and access the help page"
|
"descriptionEn": "The help page provides insight into the processing status of tasks. It's therefore not secure, but it's also not (yet) accessible as a button in the title bar. Expand the URL in the address bar to access the help page."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "broken-access-control-hidden-concert",
|
||||||
"nameDe": "Das versteckte Konzert buchen",
|
"nameDe": "Das versteckte Konzert buchen",
|
||||||
"nameEn": "Book the hidden concert",
|
"nameEn": "Book the hidden concert",
|
||||||
"exerciseNr": 2,
|
"exerciseNr": 2,
|
||||||
"descriptionDe": "Die Band >>Arctic Monkeys<< will auf ihrer >>European Tour<< drei Konzerte spielen. Im Shop finden sich allerdings nur zwei Einträge. Zwischen den beiden Tourdaten soll eine Show in der Lanxess Arena in Köln stattfinden, der Datensatz hierfür ist bereits angelegt, jedoch nicht freigeschaltet. Besuche die Seite der Band. Sieh dir den Zeitraum zwischen beiden Konzerten an, in denen das versteckte Event liegen könnte. Öffne eine Buchungsseite eines anderen Konzertes und ändere die URL so ab, dass du das versteckte Konzert buchen kannst. Reserviere dir mindestens ein Ticket und schließe den Bestellprozess ab.",
|
"descriptionDe": "Die Band >>Arctic Monkeys<< will auf ihrer >>European Tour<< drei Konzerte spielen. Im Shop finden sich allerdings nur zwei Einträge. Zwischen den beiden Tourdaten soll eine Show in der Lanxess Arena in Köln stattfinden, der Datensatz hierfür ist bereits angelegt, jedoch nicht freigeschaltet. Besuche die Seite der Band. Sieh dir den Zeitraum zwischen beiden Konzerten an, in denen das versteckte Event liegen könnte. Öffne eine Buchungsseite eines anderen Konzertes und ändere die URL so ab, dass du das versteckte Konzert buchen kannst. Reserviere dir mindestens ein Ticket und schließe den Bestellprozess ab.",
|
||||||
"descriptionEn": "Manipulate the URL and access the sold out concert and buy a ticket"
|
"descriptionEn": "The band >>Arctic Monkeys<< plans to play three shows on their >>European Tour<<. However, there are only two entries in the shop. A show at the Lanxess Arena in Cologne is scheduled to take place between the two tour dates. The dataset for this has already been created but is not yet activated. Visit the band's website. Look at the time period between the two concerts, where the hidden event could take place. Open a booking page for another concert and change the URL so that you can book the hidden concert. Reserve at least one ticket and complete the order process."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nameDe": "SQL Injections",
|
"nameDe": "SQL Injections",
|
||||||
"nameEn": "SQL Injections",
|
"nameEn": "SQL Injections",
|
||||||
|
"icon": "mdi-needle",
|
||||||
"groupNr": 2,
|
"groupNr": 2,
|
||||||
"descriptionDe": "Eine Datenbank arbeitet mit SQL Befehlen um Datensätze anzulegen, abzurufen, zu verändern und löschen. Ein Server wird über API-Schnittstellen angesprochen, führt die Befehle in der Datenbank aus und liefert das Ergebnis zurück. Der Client darf keinen direkten Zugriff auf die Datenbank haben. Bei SQL Injections wird versucht, diesen Sicherheitsmechanismus zu umgehen und über die API-Schnittstellen direkte SQL Befehle auszuführen.",
|
"descriptionDe": "Eine Datenbank arbeitet mit SQL Befehlen um Datensätze anzulegen, abzurufen, zu verändern und löschen. Ein Server wird über API-Schnittstellen angesprochen, führt die Befehle in der Datenbank aus und liefert das Ergebnis zurück. Der Client darf keinen direkten Zugriff auf die Datenbank haben. Bei SQL Injections wird versucht, diesen Sicherheitsmechanismus zu umgehen und über die API-Schnittstellen direkte SQL Befehle auszuführen.",
|
||||||
"descriptionEn": "todo",
|
"descriptionEn": "A database uses SQL commands to create, retrieve, modify, and delete records. A server is accessed via API interfaces, executes the commands in the database, and returns the results. The client must not have direct access to the database. SQL injection attempts to circumvent this security mechanism and execute SQL commands directly via the API interfaces.",
|
||||||
"exercises": [
|
"exercises": [
|
||||||
{
|
{
|
||||||
|
"uuid": "sql-injection-database-scheme",
|
||||||
"nameDe": "Wie sieht die Datenbank aus?",
|
"nameDe": "Wie sieht die Datenbank aus?",
|
||||||
"nameEn": "How does the database look like?",
|
"nameEn": "How does the database look like?",
|
||||||
"exerciseNr": 1,
|
"exerciseNr": 1,
|
||||||
"descriptionDe": "Wir versuchen nun die Datenbank im Hintergrund anzugreifen. Aktuell wissen wir aber noch nicht wie die Datenbank aussieht, also welche Tabellen sie beinhaltet. Wir können uns aber mit einem SQL-Befehl ausgeben. Gehe zur globalen Suchseite. Öffne mit der Tastenkombination >>Strg<< + >>D<< die >>Developer Tools<<. Klicke auf den Reiter >>Network<<. Hier siehst du, wie das Frontend mit dem Server kommuniziert. Schreibe nun eine SQL-Injection, welche den Suchbegriff ignoriert und dir stattdessen alle Datensätze der Tabelle >>sqlite_master<< zurück gibt, sofern die Bedingung >>type='table'<< erfüllt ist. Kopiere dir bei erfolgreicher Rückmeldung des Backends die Namen der Tabellen in eine Text-Datei, damit wir für die kommenden Aufgaben die richtigen Namen der Tabellen angeben können.",
|
"descriptionDe": "Wir versuchen nun die Datenbank im Hintergrund anzugreifen. Aktuell wissen wir aber noch nicht wie die Datenbank aussieht, also welche Tabellen sie beinhaltet. Wir können uns aber mit einem SQL-Befehl ausgeben. Gehe zur globalen Suchseite. Öffne mit der Tastenkombination >>Strg<< + >>D<< die >>Developer Tools<<. Klicke auf den Reiter >>Network<<. Hier siehst du, wie das Frontend mit dem Server kommuniziert. Schreibe nun eine SQL-Injection, welche den Suchbegriff ignoriert und dir stattdessen alle Datensätze der Tabelle >>sqlite_master<< zurück gibt, sofern die Bedingung >>type='table'<< erfüllt ist. Kopiere dir bei erfolgreicher Rückmeldung des Backends die Namen der Tabellen in eine Text-Datei, damit wir für die kommenden Aufgaben die richtigen Namen der Tabellen angeben können.",
|
||||||
"descriptionEn": "todo"
|
"descriptionEn": "We'll now attempt to attack the database in the background. Currently, we don't yet know what the database looks like, or which tables it contains. However, we can use an SQL command to inject it. Go to the global search page. Open the Developer Tools using the keyboard shortcut Ctrl + D. Click on the Network tab. Here you can see how the frontend communicates with the server. Now write an SQL injection that ignores the search term and instead returns all records in the sqlite_master table, provided the type='table' condition is met. If the backend responds successfully, copy the table names into a text file so that we can specify the correct table names for future tasks."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "sql-injection-all-accounts",
|
||||||
"nameDe": "Alle Accounts ausspähen",
|
"nameDe": "Alle Accounts ausspähen",
|
||||||
"nameEn": "Get all accounts",
|
"nameEn": "Get all accounts",
|
||||||
"exerciseNr": 2,
|
"exerciseNr": 2,
|
||||||
"descriptionDe": "Schreibe nun eine SQL-Injection, welche den Suchbegriff ignoriert und dir stattdessen alle Datensätze der Account-Tabelle zurück liefert. Führe den Angriff über das Suchfeld aus. Sieh dir die Rückmeldung des Servers an.",
|
"descriptionDe": "Schreibe nun eine SQL-Injection, welche den Suchbegriff ignoriert und dir stattdessen alle Datensätze der Account-Tabelle zurück liefert. Führe den Angriff über das Suchfeld aus. Sieh dir die Rückmeldung des Servers an.",
|
||||||
"descriptionEn": "Execute an SQL-Injection on the Search page to get all datasets from >>Accounts<< table."
|
"descriptionEn": "Now write an SQL injection that ignores the search term and instead returns all records in the account table. Execute the attack using the search field. Watch the server's response."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "sql-injection-account-roles",
|
||||||
"nameDe": "Alle Berechtigungsgruppen ausspähen",
|
"nameDe": "Alle Berechtigungsgruppen ausspähen",
|
||||||
"nameEn": "Get all account roles",
|
"nameEn": "Get all account roles",
|
||||||
"exerciseNr": 3,
|
"exerciseNr": 3,
|
||||||
"descriptionDe": "Wir sehen nun alle Accounts. Jeder hat eine Berechtigungs-ID (accountRoleId) mit der Berechtigungen wie der Zugriff aufs Admin-Panel geregelt werden. Wir wissen aber nicht, was die ID's bedeuten. Schreibe darum eine SQL-Injection, welche den Suchbegriff ignoriert und dir stattdessen alle Datensätze der Tabelle >>AccountRoles<< zurück liefert. Führe den Angriff über das Suchfeld aus. Beobachte die Rückmeldung des Servers über den >>Network<<-Tab.",
|
"descriptionDe": "Wir sehen nun alle Accounts. Jeder hat eine Berechtigungs-ID (accountRoleId) mit der Berechtigungen wie der Zugriff aufs Admin-Panel geregelt werden. Wir wissen aber nicht, was die ID's bedeuten. Schreibe darum eine SQL-Injection, welche den Suchbegriff ignoriert und dir stattdessen alle Datensätze der Tabelle >>AccountRoles<< zurück liefert. Führe den Angriff über das Suchfeld aus. Beobachte die Rückmeldung des Servers über den >>Network<<-Tab.",
|
||||||
"descriptionEn": "Execute an SQL-Injection on the Search page to get all datasets from >>AccountRoles<< table."
|
"descriptionEn": "We now see all the accounts. Each has an authorization ID (accountRoleId) that controls permissions such as access to the admin panel. However, we don't know what the IDs mean. Therefore, write an SQL injection that ignores the search term and instead returns all records in the >>AccountRoles<< table. Execute the attack via the search field. Observe the server's response via the >>Network<< tab."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "sql-injection-upgrade-privileges",
|
||||||
"nameDe": "Eigene Berechtigungen erhöhen",
|
"nameDe": "Eigene Berechtigungen erhöhen",
|
||||||
"nameEn": "Upgrade your privileges",
|
"nameEn": "Upgrade your privileges",
|
||||||
"exerciseNr": 4,
|
"exerciseNr": 4,
|
||||||
"descriptionDe": "Jetzt bearbeiten wir unseren eigenen Account. Schreibe hierfür einen >>UPDATE<<-SQL-Befehl, welcher die >>accountRoleId<< auf das Niveau eines >>Admin<< erhöht für deinen Account-Namen.",
|
"descriptionDe": "Jetzt bearbeiten wir unseren eigenen Account. Schreibe hierfür einen >>UPDATE<<-SQL-Befehl, welcher die >>accountRoleId<< auf das Niveau eines >>Admin<< erhöht für deinen Account-Namen.",
|
||||||
"descriptionEn": "Change the privileges of your account"
|
"descriptionEn": "Now we'll edit our own account. To do this, write an >>UPDATE<< SQL command that elevates the >>accountRoleId<< to the level of >>Admin<< for your account name."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "sql-injection-capture-account",
|
||||||
"nameDe": "Einen fremden Account übernehmen",
|
"nameDe": "Einen fremden Account übernehmen",
|
||||||
"nameEn": "Capture another account",
|
"nameEn": "Capture another account",
|
||||||
"exerciseNr": 5,
|
"exerciseNr": 5,
|
||||||
"descriptionDe": "Statt unsere eigenen Berechtigungen zu erhöhen, können wir auch einen Account übernehmen, welcher bereits ein >>Super-Admin<< ist. Suche dir dafür aus der Liste der in Aufgabe 2.1 erhaltenen Accounts einen aus, welcher die Rolle >>Super-Admin<< inne hat. Nur damit lässt sich die Dateiverwaltung öffnen, welche wir später brauchen. Hast du den Account-Namen gefunden, gehe ins Login-Menü (logge dich aus, falls du noch angemeldet bist). Führe nun einen SQL-Injektion durch um diesen Account zu übernehmen.",
|
"descriptionDe": "Statt unsere eigenen Berechtigungen zu erhöhen, können wir auch einen Account übernehmen, welcher bereits ein >>Super-Admin<< ist. Suche dir dafür aus der Liste der in Aufgabe 2.1 erhaltenen Accounts einen aus, welcher die Rolle >>Super-Admin<< inne hat. Nur damit lässt sich die Dateiverwaltung öffnen, welche wir später brauchen. Hast du den Account-Namen gefunden, gehe ins Login-Menü (logge dich aus, falls du noch angemeldet bist). Führe nun einen SQL-Injektion durch um diesen Account zu übernehmen.",
|
||||||
"descriptionEn": "todo"
|
"descriptionEn": "Instead of increasing our own permissions, we can also take over an account that is already a >>super admin<<. To do this, select one from the list of accounts obtained in Task 2.1 that has the >>super admin<< role. Only then can we open the file manager, which we'll need later. Once you've found the account name, go to the login menu (log out if you're still logged in). Now perform an SQL injection to take over this account."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "sql-injection-delete-rating",
|
||||||
"nameDe": "Bewertungen löschen",
|
"nameDe": "Bewertungen löschen",
|
||||||
"nameEn": "Delete ratings",
|
"nameEn": "Delete ratings",
|
||||||
"exerciseNr": 6,
|
"exerciseNr": 6,
|
||||||
"descriptionDe": "Jede Band hat Bewertungen auf einer Skala von eins bis fünf Sternen erhalten. Wir wollen alle Fünf-Sterne Bewertungen aus der Datenbank löschen. Schreibe eine SQL Injection, welche in der Bewertungs-Tabelle alle Einträge mit der Bedingung >>rating = 5<< entfernt. Führe die Injection über die globale Suche aus.",
|
"descriptionDe": "Jede Band hat Bewertungen auf einer Skala von eins bis fünf Sternen erhalten. Wir wollen alle Fünf-Sterne Bewertungen aus der Datenbank löschen. Schreibe eine SQL Injection, welche in der Bewertungs-Tabelle alle Einträge mit der Bedingung >>rating = 5<< entfernt. Führe die Injection über die globale Suche aus.",
|
||||||
"descriptionEn": "todo"
|
"descriptionEn": "Each band has received ratings on a scale of one to five stars. We want to delete all five-star ratings from the database. Write an SQL injection that removes all entries in the ratings table with the condition >>rating = 5<<. Execute the injection via the global search."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nameDe": "Cross-Site Scripting (XSS)",
|
"nameDe": "Cross-Site Scripting (XSS)",
|
||||||
"nameEn": "Cross-Site Scripting (XSS)",
|
"nameEn": "Cross-Site Scripting (XSS)",
|
||||||
|
"icon": "mdi-code-brackets",
|
||||||
"groupNr": 3,
|
"groupNr": 3,
|
||||||
"descriptionDe": "Als nächstes wollen wir Schadcode in die Web-Applikation einschleusen. Zunächst testen wir, ob die Webseite hierfür anfällig ist. Manipuliere die URL der Band-Seite so, dass du eine >>Hallo Welt!<<-Nachricht als >>alert<< siehst. Hinweis: Nutze einen image tag! Setze als >>src<< die Zahl >>1<<. Den Befehl kannst du im Tag >>onerror<< ausführen.",
|
"descriptionDe": "Als nächstes wollen wir Schadcode in die Web-Applikation einschleusen. Zunächst testen wir, ob die Webseite hierfür anfällig ist. Manipuliere die URL der Band-Seite so, dass du eine >>Hallo Welt!<<-Nachricht als >>alert<< siehst. Hinweis: Nutze einen image tag! Setze als >>src<< die Zahl >>1<<. Den Befehl kannst du im Tag >>onerror<< ausführen.",
|
||||||
"descriptionEn": "todo",
|
"descriptionEn": "Next, we want to inject malicious code into the web application. First, we'll test whether the website is vulnerable to this. Manipulate the URL of the band's page so that you see a >>Hello World!<< message as an >>alert<<. Note: Use an image tag! Set the number >>1<< as the >>src<<. You can execute the command in the >>onerror<< tag.",
|
||||||
"exercises": [
|
"exercises": [
|
||||||
{
|
{
|
||||||
|
"uuid": "cross-site-scripting-hello-world",
|
||||||
"nameDe": "Hallo Welt!",
|
"nameDe": "Hallo Welt!",
|
||||||
"nameEn": "Hello World!",
|
"nameEn": "Hello World!",
|
||||||
"exerciseNr": 1,
|
"exerciseNr": 1,
|
||||||
"descriptionDe": "Als nächstes wollen wir Schadcode in die Web-Applikation einschleusen. Zunächst testen wir, ob die Webseite hierfür anfällig ist. Gehe hierzu auf die Seite >>Alle Bands<< und filtere die Einträge nach einem beliebigen Genre deiner Wahl. In der URL-Leiste siehst du nun, dass hinter der URL und dem Ressourcen-Ziel ein Parameter angegeben ist (der Part hinter dem Fragezeichen). Wir tauschen diesen Parameter gegen einen HTML Tag aus. Der Trick hierbei: Als Quelle geben wir den Zahlenwert >>1<< an, wodurch automatisch das ausgeführt wird, was im >>onerror<<-Tag drinnen steht. Genau hier soll eine Alert-Meldung mit >>Hello World!<< als JavaScript Code eingefügt werden. Verändere die URL so, dass sie die Meldung ausgibt. Falls du nicht mit JavaScript vertraut bist, sieh dir die letzte Seite mit nützlichen Befehlen an.",
|
"descriptionDe": "Als nächstes wollen wir Schadcode in die Web-Applikation einschleusen. Zunächst testen wir, ob die Webseite hierfür anfällig ist. Gehe hierzu auf die Seite >>Alle Bands<< und filtere die Einträge nach einem beliebigen Genre deiner Wahl. In der URL-Leiste siehst du nun, dass hinter der URL und dem Ressourcen-Ziel ein Parameter angegeben ist (der Part hinter dem Fragezeichen). Wir tauschen diesen Parameter gegen einen HTML Tag aus. Der Trick hierbei: Als Quelle geben wir den Zahlenwert >>1<< an, wodurch automatisch das ausgeführt wird, was im >>onerror<<-Tag drinnen steht. Genau hier soll eine Alert-Meldung mit >>Hello World!<< als JavaScript Code eingefügt werden. Verändere die URL so, dass sie die Meldung ausgibt. Falls du nicht mit JavaScript vertraut bist, sieh dir die letzte Seite mit nützlichen Befehlen an.",
|
||||||
"descriptionEn": "Take an URL of the shop and extend it with JavaScript code so that a 'Hello World' message appears whent the link is opened"
|
"descriptionEn": "Next, we want to inject malicious code into the web application. First, we'll test whether the website is vulnerable to malicious code. To do this, go to the >>All Bands<< page and filter the entries by any genre of your choice. In the URL bar, you'll now see that a parameter is specified after the URL and the resource target (the part after the question mark). We'll replace this parameter with an HTML tag. The trick here: We specify the numeric value >>1<< as the source, which automatically executes what's contained in the >>onerror<< tag. This is exactly where we want to insert an alert message with >>Hello World!<< as JavaScript code. Change the URL so that it displays the message. If you're not familiar with JavaScript, check out the last page for useful commands."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"uuid": "cross-site-scripting-external-script",
|
||||||
"nameDe": "Ein externes Script aufrufen",
|
"nameDe": "Ein externes Script aufrufen",
|
||||||
"nameEn": "Run an external script",
|
"nameEn": "Run an external script",
|
||||||
"exerciseNr": 2,
|
"exerciseNr": 2,
|
||||||
"descriptionDe": "Wir haben festgestellt, dass die Seite für Cross-Site-Scripting durch Reflected XSS angreifbar ist! Im zweiten Schritt binden wir nun das Script ein. Es wurde bereits auf den Server hochgeladen. Logge dich wahlweise mit einem Admin-Account (Aufgabe 2.5) oder deinem eigenen nun berechtigten Account (Aufgabe 2.4) ein. Öffne nun das Admin-Panel über den Button rechts oben. Suche über die Dateiverwaltung im Admin-Panel nach dem Skript und notiere dir die darunter angezeigte Adresse auf dem Backend-Server. Logge dich aus. Wir wollen das Skript auf der nun sichtbaren Login-Seite über eine veränderte URL einbinden. Nutze hierfür das gleiche Prinzip wie in Aufgabe 3.1. Statt >>genreName<< kannst du einen beliebigen anderen Parameter-Namen verwenden. Nutze die Konsole mit der Tastenkombination Strg + D vor dem Abschicken der URL.Logge dich nach erfolgreicher Aufgabenlösung ein und sieh in der Konsole, wie deine Login-Daten abgegriffen werden.",
|
"descriptionDe": "Wir haben festgestellt, dass die Seite für Cross-Site-Scripting durch Reflected XSS angreifbar ist! Im zweiten Schritt binden wir nun das Script ein. Es wurde bereits auf den Server hochgeladen. Logge dich wahlweise mit einem Admin-Account (Aufgabe 2.5) oder deinem eigenen nun berechtigten Account (Aufgabe 2.4) ein. Öffne nun das Admin-Panel über den Button rechts oben. Suche über die Dateiverwaltung im Admin-Panel nach dem Skript und notiere dir die darunter angezeigte Adresse auf dem Backend-Server. Logge dich aus. Wir wollen das Skript auf der nun sichtbaren Login-Seite über eine veränderte URL einbinden. Nutze hierfür das gleiche Prinzip wie in Aufgabe 3.1. Statt >>genreName<< kannst du einen beliebigen anderen Parameter-Namen verwenden. Nutze die Konsole mit der Tastenkombination Strg + D vor dem Abschicken der URL.Logge dich nach erfolgreicher Aufgabenlösung ein und sieh in der Konsole, wie deine Login-Daten abgegriffen werden.",
|
||||||
"descriptionEn": "Create an URL of the shop, which calls the script"
|
"descriptionEn": "We've determined that the page is vulnerable to cross-site scripting through Reflected XSS! In the second step, we'll integrate the script. It's already been uploaded to the server. Log in either with an admin account (Task 2.5) or your own, now authorized account (Task 2.4). Now open the admin panel using the button in the top right. Search for the script using the file manager in the admin panel and note the address displayed below it on the backend server. Log out. We want to integrate the script into the now visible login page using a modified URL. Use the same principle as in Task 3.1. Instead of >>genreName<<, you can use any other parameter name. Use the console by pressing Ctrl + D before submitting the URL. After successfully completing the task, log in and watch in the console how your login data is being retrieved."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { ExerciseGroup } from "./exerciseGroup.model";
|
|||||||
|
|
||||||
@Table({ timestamps: false })
|
@Table({ timestamps: false })
|
||||||
export class Exercise extends Model {
|
export class Exercise extends Model {
|
||||||
|
@Column
|
||||||
|
uuid: string
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
nameDe: string
|
nameDe: string
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ export class ExerciseGroup extends Model {
|
|||||||
@Column
|
@Column
|
||||||
nameEn: string
|
nameEn: string
|
||||||
|
|
||||||
|
@Column
|
||||||
|
icon: string
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
groupNr: number
|
groupNr: number
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ export class AccountRole extends Model {
|
|||||||
@Column
|
@Column
|
||||||
privilegeAdminPanel: boolean
|
privilegeAdminPanel: boolean
|
||||||
|
|
||||||
@Column
|
|
||||||
privilegeFileAccess: boolean
|
|
||||||
|
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
@HasMany(() => Account)
|
@HasMany(() => Account)
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Account
|
||||||
|
* description: API to manage accounts
|
||||||
|
*/
|
||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import { Account } from "../models/user/account.model";
|
import { Account } from "../models/user/account.model";
|
||||||
import { validateString } from "../scripts/validateHelper";
|
import { validateString } from "../scripts/validateHelper";
|
||||||
@@ -11,22 +17,41 @@ import { encryptString } from "../scripts/encryptScripts";
|
|||||||
|
|
||||||
export const account = Router()
|
export const account = Router()
|
||||||
|
|
||||||
account.get("/", verifyToken, (req: Request, res: Response) => {
|
/**
|
||||||
Account.findAll({
|
* @swagger
|
||||||
include: [ AccountRole ]
|
* /accounts/login:
|
||||||
})
|
* get:
|
||||||
.then(accounts => {
|
* summary: Start login process
|
||||||
res.status(200).json(accounts)
|
* tags: [Account]
|
||||||
})
|
* parameters:
|
||||||
.catch(error => {
|
* - in: query
|
||||||
res.status(500).send()
|
* name: username
|
||||||
})
|
* schema:
|
||||||
})
|
* type: string
|
||||||
|
* required: true
|
||||||
// Login user
|
* description: Username
|
||||||
account.get("/account/login", async (req: Request, res: Response) => {
|
* - in: query
|
||||||
|
* name: password
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: true
|
||||||
|
* description: User password
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Login successful
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/loginResponse'
|
||||||
|
* 401:
|
||||||
|
* description: Wrong credentials
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
|
account.get("/login", async (req: Request, res: Response) => {
|
||||||
const encryptedPassword = encryptString(String(req.query.password))
|
const encryptedPassword = encryptString(String(req.query.password))
|
||||||
|
|
||||||
|
try {
|
||||||
// Using raw SQL code for SQL injections!
|
// Using raw SQL code for SQL injections!
|
||||||
const [results, metadata] =
|
const [results, metadata] =
|
||||||
await sequelize.query(
|
await sequelize.query(
|
||||||
@@ -46,20 +71,43 @@ account.get("/account/login", async (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Status: 401 Unauthorized
|
// Status: 401 Unauthorized
|
||||||
res.status(401).json({
|
res.status(401).send()
|
||||||
code: 401,
|
}
|
||||||
message: "Unauthorized"
|
} catch (e) {
|
||||||
})
|
res.status(500).send()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
account.get("/account/data", verifyToken, async(req: Request, res: Response) => {
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /accounts/account:
|
||||||
|
* get:
|
||||||
|
* summary: Get all data about an user account
|
||||||
|
* tags: [Account]
|
||||||
|
* security:
|
||||||
|
* - JWT: []
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Success
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/useraccount'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
|
account.get("/account", verifyToken, async(req: Request, res: Response) => {
|
||||||
Account.findOne({
|
Account.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: req["id"]
|
id: req["id"]
|
||||||
},
|
},
|
||||||
include: [ Address, AccountRole, Payment ]
|
include: [ Address, AccountRole, Payment ],
|
||||||
|
attributes: {
|
||||||
|
exclude: [ "accountRoleId" ]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(account => {
|
.then(account => {
|
||||||
res.status(200).json(account)
|
res.status(200).json(account)
|
||||||
@@ -70,7 +118,31 @@ account.get("/account/data", verifyToken, async(req: Request, res: Response) =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// Creating a new user
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /accounts/account:
|
||||||
|
* post:
|
||||||
|
* summary: Create a new user account
|
||||||
|
* tags: [Account]
|
||||||
|
* requestBody:
|
||||||
|
* description: Minimal user data body
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/minimalAccount'
|
||||||
|
* responses:
|
||||||
|
* 201:
|
||||||
|
* description: Created
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/useraccount'
|
||||||
|
* 400:
|
||||||
|
* description: Username/password too short
|
||||||
|
* 409:
|
||||||
|
* description: Username already in use
|
||||||
|
*/
|
||||||
account.post("/account", async (req: Request, res: Response) => {
|
account.post("/account", async (req: Request, res: Response) => {
|
||||||
// Check if username is valid
|
// Check if username is valid
|
||||||
if (!validateString(req.body.username, 4))
|
if (!validateString(req.body.username, 4))
|
||||||
@@ -117,38 +189,56 @@ account.post("/account", async (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /accounts/account:
|
||||||
|
* patch:
|
||||||
|
* summary: Update an user accounts data
|
||||||
|
* tags: [Account]
|
||||||
|
* security:
|
||||||
|
* - JWT: []
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Success
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/useraccount'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
account.patch("/account", verifyToken, (req: Request, res: Response) => {
|
account.patch("/account", verifyToken, (req: Request, res: Response) => {
|
||||||
Account.update(req.body,
|
Account.update(req.body,
|
||||||
{
|
{
|
||||||
where: { id: req.body.id }
|
where: { id: req.body.id }
|
||||||
})
|
})
|
||||||
.then(async result => {
|
.then(async result => {
|
||||||
|
Payment.destroy({
|
||||||
|
where: {
|
||||||
|
accountId: req.body.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Address.destroy({
|
||||||
|
where: {
|
||||||
|
accountId: req.body.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
for (let payment of req.body.payments) {
|
for (let payment of req.body.payments) {
|
||||||
if (payment.id == undefined) {
|
|
||||||
payment["accountId"] = req.body.id
|
payment["accountId"] = req.body.id
|
||||||
|
|
||||||
await Payment.create(payment)
|
await Payment.create(payment)
|
||||||
} else {
|
|
||||||
await Payment.update(payment,
|
|
||||||
{
|
|
||||||
where: { id: payment.id }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let address of req.body.addresses) {
|
for (let address of req.body.addresses) {
|
||||||
if (address.id == undefined) {
|
|
||||||
address["accountId"] = req.body.id
|
address["accountId"] = req.body.id
|
||||||
|
|
||||||
await Address.create(address)
|
await Address.create(address)
|
||||||
} else {
|
|
||||||
await Address.update(address,
|
|
||||||
{
|
|
||||||
where: { id: address.id }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status: 200 OK
|
// Status: 200 OK
|
||||||
@@ -164,7 +254,31 @@ account.patch("/account", verifyToken, (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
account.delete("/account/:id", (req: Request, res: Response) => {
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /accounts/account/{id}:
|
||||||
|
* delete:
|
||||||
|
* summary: Delete an user account
|
||||||
|
* tags: [Account]
|
||||||
|
* security:
|
||||||
|
* - JWT: []
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: true
|
||||||
|
* description: ID of user account
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Success
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
|
account.delete("/account/:id", verifyToken, (req: Request, res: Response) => {
|
||||||
Account.destroy({
|
Account.destroy({
|
||||||
where: {
|
where: {
|
||||||
id: req.params.id
|
id: req.params.id
|
||||||
@@ -177,3 +291,36 @@ account.delete("/account/:id", (req: Request, res: Response) => {
|
|||||||
res.status(500).send()
|
res.status(500).send()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /accounts/:
|
||||||
|
* get:
|
||||||
|
* summary: Request all user accounts
|
||||||
|
* tags: [Account]
|
||||||
|
* security:
|
||||||
|
* - JWT: []
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Success
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/useraccount'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
|
account.get("/", verifyToken, (req: Request, res: Response) => {
|
||||||
|
Account.findAll({
|
||||||
|
include: [ AccountRole ]
|
||||||
|
})
|
||||||
|
.then(accounts => {
|
||||||
|
res.status(200).json(accounts)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
res.status(500).send()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,18 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Api
|
||||||
|
* description: Main API access point for misc events
|
||||||
|
*/
|
||||||
import { Request, Response, NextFunction, Router } from 'express'
|
import { Request, Response, NextFunction, Router } from 'express'
|
||||||
import { deleteAllTables, deleteExerciseProgressTables, prepopulateDatabase, prepopulateExerciseDatabase } from '../scripts/databaseHelper'
|
import { deleteAllTables, deleteExerciseProgressTables, prepopulateDatabase, prepopulateExerciseDatabase } from '../scripts/databaseHelper'
|
||||||
|
|
||||||
export const api = Router()
|
export const api = Router()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status check endpoint
|
* @swagger
|
||||||
|
* /api:
|
||||||
|
* get:
|
||||||
|
* summary: Status check endpoint
|
||||||
|
* tags: [Api]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Server is up and running
|
||||||
*/
|
*/
|
||||||
api.get("/", (req: Request, res: Response, next: NextFunction) => {
|
api.get("/", (req: Request, res: Response, next: NextFunction) => {
|
||||||
res.status(200).send()
|
res.status(200).send()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the whole database to factory state
|
* @swagger
|
||||||
* Doesn't effect ExerciseTable and ExerciseGroupTable
|
* /api/resetdatabase:
|
||||||
|
* get:
|
||||||
|
* summary: Reset the database to factory state
|
||||||
|
* description: Doesn't effect ExerciseTable and ExerciseGroupTable
|
||||||
|
* tags: [Api]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Reset successful
|
||||||
*/
|
*/
|
||||||
api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction) => {
|
api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// Step 1: Delete all data tables
|
// Step 1: Delete all data tables
|
||||||
@@ -26,7 +46,15 @@ api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset ExerciseTable and ExerciseGroupTable to factory state
|
* @swagger
|
||||||
|
* /api/resetExerciseProgress:
|
||||||
|
* get:
|
||||||
|
* summary: Reset exercises to factory state
|
||||||
|
* description: Reset ExerciseTable and ExerciseGroupTable to factory state
|
||||||
|
* tags: [Api]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Reset successful
|
||||||
*/
|
*/
|
||||||
api.get("/resetExerciseProgress", async (req: Request, res: Response, next: NextFunction) => {
|
api.get("/resetExerciseProgress", async (req: Request, res: Response, next: NextFunction) => {
|
||||||
deleteExerciseProgressTables()
|
deleteExerciseProgressTables()
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Bands
|
||||||
|
* description: API to manage the bands
|
||||||
|
*/
|
||||||
import { Member } from "../models/acts/member.model";
|
import { Member } from "../models/acts/member.model";
|
||||||
import { Band } from "../models/acts/band.model";
|
import { Band } from "../models/acts/band.model";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
@@ -13,7 +19,33 @@ import { sequelize } from "../database";
|
|||||||
export const band = Router()
|
export const band = Router()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all bands
|
* @swagger
|
||||||
|
* /bands:
|
||||||
|
* get:
|
||||||
|
* summary: Download all available bands
|
||||||
|
* tags: [Bands]
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: sort
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: false
|
||||||
|
* description: Sort bands by number of concerts ascending (asc) or descending (desc)
|
||||||
|
* - in: query
|
||||||
|
* name: count
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: false
|
||||||
|
* description: Limit number of results
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of band objects
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/band'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
*/
|
*/
|
||||||
band.get("/", (req: Request, res: Response) => {
|
band.get("/", (req: Request, res: Response) => {
|
||||||
let sort = req.query.sort
|
let sort = req.query.sort
|
||||||
@@ -21,16 +53,14 @@ band.get("/", (req: Request, res: Response) => {
|
|||||||
|
|
||||||
Band.findAll({
|
Band.findAll({
|
||||||
include: [
|
include: [
|
||||||
{
|
|
||||||
model: Rating,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
model: Genre,
|
model: Genre,
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: [ "id" ]
|
exclude: [ "id" ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Concert
|
Concert,
|
||||||
|
Rating
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.then(bands => {
|
.then(bands => {
|
||||||
|
|||||||
@@ -1,8 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Cities
|
||||||
|
* description: API to manage the cities
|
||||||
|
*/
|
||||||
import { City } from "../models/locations/city.model";
|
import { City } from "../models/locations/city.model";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
export const city = Router()
|
export const city = Router()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /cities:
|
||||||
|
* get:
|
||||||
|
* summary: Download all cities
|
||||||
|
* tags: [Cities]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of all cities as objects
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/city'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
city.get("/", (req: Request, res: Response) => {
|
city.get("/", (req: Request, res: Response) => {
|
||||||
City.findAll()
|
City.findAll()
|
||||||
.then(cities => {
|
.then(cities => {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Concerts
|
||||||
|
* description: API to manage the concerts
|
||||||
|
*/
|
||||||
import { Location } from "../models/locations/location.model";
|
import { Location } from "../models/locations/location.model";
|
||||||
import { Concert } from "../models/acts/concert.model";
|
import { Concert } from "../models/acts/concert.model";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
@@ -11,42 +17,9 @@ import { Op } from "sequelize";
|
|||||||
|
|
||||||
export const concert = Router()
|
export const concert = Router()
|
||||||
|
|
||||||
|
const concertStructure = [
|
||||||
concert.get("/", (req: Request, res: Response) => {
|
|
||||||
let count = req.query.count
|
|
||||||
|
|
||||||
Concert.findAll({
|
|
||||||
include: [
|
|
||||||
{
|
{
|
||||||
model: Location,
|
model: Band
|
||||||
include: [ City ]
|
|
||||||
},
|
|
||||||
Band
|
|
||||||
],
|
|
||||||
order: [
|
|
||||||
[ 'date', 'ASC' ]
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.then(concerts => {
|
|
||||||
// Limit number of items
|
|
||||||
if (count != undefined) {
|
|
||||||
concerts.splice(Number(count))
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json(concerts)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
res.status(500).send()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Get all available data about a band by it's ID
|
|
||||||
concert.get("/concert/:id", (req: Request, res: Response) => {
|
|
||||||
Concert.findByPk(req.params.id, {
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Band,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: Location,
|
model: Location,
|
||||||
@@ -77,11 +50,78 @@ concert.get("/concert/:id", (req: Request, res: Response) => {
|
|||||||
exclude: [ "cityId" ]
|
exclude: [ "cityId" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
attributes: {
|
|
||||||
exclude: [ "locationId", "tourId" ]
|
|
||||||
}
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /concerts:
|
||||||
|
* get:
|
||||||
|
* summary: Get all available concerts
|
||||||
|
* tags: [Concerts]
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: count
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: false
|
||||||
|
* description: Limit number of results
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/concert'
|
||||||
|
*/
|
||||||
|
concert.get("/", (req: Request, res: Response) => {
|
||||||
|
let count = req.query.count
|
||||||
|
|
||||||
|
Concert.findAll({
|
||||||
|
include: concertStructure,
|
||||||
|
order: [
|
||||||
|
[ 'date', 'ASC' ]
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
.then(concerts => {
|
||||||
|
// Limit number of items
|
||||||
|
if (count != undefined) {
|
||||||
|
concerts.splice(Number(count))
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(concerts)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
res.status(500).send()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /concerts/concert/{id}:
|
||||||
|
* get:
|
||||||
|
* summary: Download all available informations about a specific concert
|
||||||
|
* tags: [Concerts]
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: true
|
||||||
|
* description: ID of concert in database
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Single concert object
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/concert'
|
||||||
|
* 404:
|
||||||
|
* description: Not found
|
||||||
|
*/
|
||||||
|
concert.get("/concert/:id", (req: Request, res: Response) => {
|
||||||
|
Concert.findByPk(req.params.id, { include: concertStructure })
|
||||||
.then(concert => {
|
.then(concert => {
|
||||||
concert.dataValues["capacity"] = 0
|
concert.dataValues["capacity"] = 0
|
||||||
|
|
||||||
@@ -119,7 +159,29 @@ concert.get("/concert/:id", (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// Concert search
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /concerts/search:
|
||||||
|
* get:
|
||||||
|
* summary: Search for concerts
|
||||||
|
* tags: [Concerts]
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: value
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: true
|
||||||
|
* description: Search term
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of concert objects
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/concert'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
concert.get("/search", (req: Request, res: Response) => {
|
concert.get("/search", (req: Request, res: Response) => {
|
||||||
Concert.findAll({
|
Concert.findAll({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Exercises
|
||||||
|
* description: API to manage the exercise progress
|
||||||
|
*/
|
||||||
import { Op } from "sequelize";
|
import { Op } from "sequelize";
|
||||||
import { Exercise } from "../models/exercises/exercise.model";
|
import { Exercise } from "../models/exercises/exercise.model";
|
||||||
import { ExerciseGroup } from "../models/exercises/exerciseGroup.model";
|
import { ExerciseGroup } from "../models/exercises/exerciseGroup.model";
|
||||||
@@ -6,11 +12,28 @@ import { Request, Response, Router } from "express";
|
|||||||
export const exercises = Router()
|
export const exercises = Router()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all Exercises grouped in ExerciseGroups
|
* @swagger
|
||||||
|
* /exercises:
|
||||||
|
* get:
|
||||||
|
* summary: Download all exercises
|
||||||
|
* tags: [Exercises]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Array of all exercises
|
||||||
|
* type: array
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/exercise'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
*/
|
*/
|
||||||
exercises.get("/", (req: Request, res: Response) => {
|
exercises.get("/", (req: Request, res: Response) => {
|
||||||
Exercise.findAll({
|
Exercise.findAll({
|
||||||
include: [ ExerciseGroup ]
|
include: [ ExerciseGroup ],
|
||||||
|
attributes: {
|
||||||
|
exclude: [ "exerciseGroupId" ]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
result.sort((a, b) => {
|
result.sort((a, b) => {
|
||||||
@@ -25,11 +48,39 @@ exercises.get("/", (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update state of an Exercise
|
* @swagger
|
||||||
*
|
* /exercises/{groupNr}/{exerciseNr}/{state}:
|
||||||
* @param groupNr Number of exercise group (not ID)
|
* post:
|
||||||
* @param exerciseNr Number of exercise (not ID)
|
* summary: Update an exercise solved state
|
||||||
* @param state New state boolean
|
* tags: [Exercises]
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: groupNr
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: true
|
||||||
|
* description: Number of exercise group (not ID)
|
||||||
|
* - in: path
|
||||||
|
* name: exerciseNr
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: true
|
||||||
|
* description: Number of exercise (not ID)
|
||||||
|
* - in: path
|
||||||
|
* name: state
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: true
|
||||||
|
* description: 1 = Solved, 0 = Unsolved
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Edited exercise
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/exercise'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
*/
|
*/
|
||||||
exercises.post("/:groupNr/:exerciseNr/:state", (req: Request, res: Response) => {
|
exercises.post("/:groupNr/:exerciseNr/:state", (req: Request, res: Response) => {
|
||||||
Exercise.findOne({
|
Exercise.findOne({
|
||||||
@@ -43,7 +94,10 @@ exercises.post("/:groupNr/:exerciseNr/:state", (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
include: [ ExerciseGroup ]
|
include: [ ExerciseGroup ],
|
||||||
|
attributes: {
|
||||||
|
exclude: [ "exerciseGroupId" ]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(async exercise => {
|
.then(async exercise => {
|
||||||
let changed = false
|
let changed = false
|
||||||
|
|||||||
@@ -1,22 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Files
|
||||||
|
* description: API for handling static files
|
||||||
|
*/
|
||||||
import { Request, Response, NextFunction, Router } from 'express'
|
import { Request, Response, NextFunction, Router } from 'express'
|
||||||
import fs, { createReadStream } from "fs"
|
import fs from "fs"
|
||||||
import multer from "multer"
|
import multer from "multer"
|
||||||
const upload = multer({ dest: './backend/images/' })
|
const upload = multer({ dest: './backend/images/' })
|
||||||
import licenses from "../data/licenses.json"
|
import licenses from "../data/licenses.json"
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
export const files = Router()
|
export const files = Router()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all folders
|
* @swagger
|
||||||
|
* /files/folders:
|
||||||
|
* get:
|
||||||
|
* summary: Get all static folders
|
||||||
|
* tags: [Files]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Login successful
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/folder'
|
||||||
*/
|
*/
|
||||||
files.get("/folders", async (req: Request, res: Response) => {
|
files.get("/folders", async (req: Request, res: Response) => {
|
||||||
let dirNames = fs.readdirSync("./backend/images")
|
let dirNames = fs.readdirSync(path.resolve(__dirname, "../images"))
|
||||||
let result = []
|
let result = []
|
||||||
|
|
||||||
dirNames.forEach(dir => {
|
dirNames.forEach(dir => {
|
||||||
result.push({
|
result.push({
|
||||||
name: dir,
|
name: dir,
|
||||||
nrOfItems: fs.readdirSync("./backend/images/" + dir).length
|
nrOfItems: fs.readdirSync(path.resolve(__dirname, "../images/" + dir)).length
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -25,25 +43,42 @@ files.get("/folders", async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all uploaded file names by folder name
|
* @swagger
|
||||||
*
|
* /files/{folder}:
|
||||||
* @param folder Name of folder on server
|
* get:
|
||||||
|
* summary: Get all files in one folder
|
||||||
|
* tags: [Files]
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: folder
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: true
|
||||||
|
* description: Name of folder
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Login successful
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/file'
|
||||||
*/
|
*/
|
||||||
files.get("/:folder", async (req: Request, res: Response) => {
|
files.get("/:folder", async (req: Request, res: Response) => {
|
||||||
let result = []
|
let result = []
|
||||||
let fileNames = fs.readdirSync("./backend/images/" + req.params.folder + "/")
|
let fileNames = fs.readdirSync(path.resolve(__dirname, "../images/" + req.params.folder))
|
||||||
|
|
||||||
|
try {
|
||||||
fileNames.forEach(file => {
|
fileNames.forEach(file => {
|
||||||
let resData = ""
|
let resData = ""
|
||||||
let url = "http://localhost:3000/static/" + req.params.folder + "/" + file
|
let url = "http://localhost:3000/static/" + req.params.folder + "/" + file
|
||||||
|
|
||||||
if (file.endsWith("html") || file.endsWith("js")) {
|
if (file.endsWith("html") || file.endsWith("js")) {
|
||||||
resData = fs.readFileSync("./backend/images/" + req.params.folder + "/" + file, "utf8")
|
resData = fs.readFileSync(path.resolve(__dirname, "../images/" + req.params.folder + "/" + file), "utf8")
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
name: file,
|
name: file,
|
||||||
size: fs.statSync("./backend/images/" + req.params.folder + "/" + file).size,
|
size: fs.statSync(path.resolve(__dirname, "../images/" + req.params.folder + "/" + file)).size,
|
||||||
content: resData,
|
content: resData,
|
||||||
url: url,
|
url: url,
|
||||||
copyright: licenses.find(data => data.image == file)
|
copyright: licenses.find(data => data.image == file)
|
||||||
@@ -51,6 +86,9 @@ files.get("/:folder", async (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
res.status(200).json(result)
|
res.status(200).json(result)
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Genres
|
||||||
|
* description: API to manage the music genres
|
||||||
|
*/
|
||||||
import { Band } from "../models/acts/band.model";
|
import { Band } from "../models/acts/band.model";
|
||||||
import { Genre } from "../models/acts/genre.model";
|
import { Genre } from "../models/acts/genre.model";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
@@ -5,7 +11,20 @@ import { Request, Response, Router } from "express";
|
|||||||
export const genre = Router()
|
export const genre = Router()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all available Genres
|
* @swagger
|
||||||
|
* /genres:
|
||||||
|
* get:
|
||||||
|
* summary: Get all available genres
|
||||||
|
* tags: [Genres]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/genre'
|
||||||
|
* 500:
|
||||||
|
* description: Internal Server Error
|
||||||
*/
|
*/
|
||||||
genre.get("/", (req: Request, res: Response) => {
|
genre.get("/", (req: Request, res: Response) => {
|
||||||
Genre.findAll({
|
Genre.findAll({
|
||||||
@@ -19,8 +38,22 @@ genre.get("/", (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a Genre entry
|
* @swagger
|
||||||
|
* /genres:
|
||||||
|
* patch:
|
||||||
|
* summary: Update the dataset of a genre
|
||||||
|
* tags: [Genres]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/genre'
|
||||||
|
* 500:
|
||||||
|
* description: Internal Server Error
|
||||||
*/
|
*/
|
||||||
genre.patch("/", (req: Request, res: Response) => {
|
genre.patch("/", (req: Request, res: Response) => {
|
||||||
Genre.update(req.body, {
|
Genre.update(req.body, {
|
||||||
@@ -36,8 +69,22 @@ genre.patch("/", (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Genre entry
|
* @swagger
|
||||||
|
* /genres:
|
||||||
|
* post:
|
||||||
|
* summary: Add a new dataset of a genre
|
||||||
|
* tags: [Genres]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/genre'
|
||||||
|
* 500:
|
||||||
|
* description: Internal Server Error
|
||||||
*/
|
*/
|
||||||
genre.post("/", (req: Request, res: Response) => {
|
genre.post("/", (req: Request, res: Response) => {
|
||||||
Genre.create(req.body)
|
Genre.create(req.body)
|
||||||
@@ -49,8 +96,22 @@ genre.post("/", (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a Genre entry
|
* @swagger
|
||||||
|
* /genres:
|
||||||
|
* delete:
|
||||||
|
* summary: Delete the dataset of a genre
|
||||||
|
* tags: [Genres]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/genre'
|
||||||
|
* 500:
|
||||||
|
* description: Internal Server Error
|
||||||
*/
|
*/
|
||||||
genre.delete("/", (req: Request, res: Response) => {
|
genre.delete("/", (req: Request, res: Response) => {
|
||||||
Genre.destroy({
|
Genre.destroy({
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Locations
|
||||||
|
* description: API to manage the event locations
|
||||||
|
*/
|
||||||
import { Concert } from "../models/acts/concert.model";
|
import { Concert } from "../models/acts/concert.model";
|
||||||
import { City } from "../models/locations/city.model";
|
import { City } from "../models/locations/city.model";
|
||||||
import { Location } from "../models/locations/location.model";
|
import { Location } from "../models/locations/location.model";
|
||||||
@@ -10,24 +16,57 @@ import { Op } from "sequelize";
|
|||||||
|
|
||||||
export const location = Router()
|
export const location = Router()
|
||||||
|
|
||||||
|
// Response include rules
|
||||||
|
const locationStructure = [
|
||||||
|
City,
|
||||||
|
{
|
||||||
|
model: Concert,
|
||||||
|
include: [ Band ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: SeatGroup,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: SeatRow,
|
||||||
|
include: [ Seat ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all available Locations
|
* @swagger
|
||||||
*
|
* /locations:
|
||||||
* @query sort Sort results ascending (asc) or descending (desc)
|
* get:
|
||||||
* @query count Limit number of results
|
* summary: Get all available locations
|
||||||
|
* tags: [Locations]
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: sort
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: false
|
||||||
|
* description: Sort locations by number of concerts ascending (asc) or descending (desc)
|
||||||
|
* - in: query
|
||||||
|
* name: count
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* required: false
|
||||||
|
* description: Limit number of results
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/location'
|
||||||
*/
|
*/
|
||||||
location.get("/", (req: Request, res: Response) => {
|
location.get("/", (req: Request, res: Response) => {
|
||||||
let sort = req.query.sort
|
let sort = req.query.sort
|
||||||
let count = req.query.count
|
let count = req.query.count
|
||||||
|
|
||||||
Location.findAll({
|
Location.findAll({
|
||||||
include: [
|
include: locationStructure,
|
||||||
City,
|
|
||||||
{
|
|
||||||
model: Concert,
|
|
||||||
include: [ Band ],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: [ "cityId" ]
|
exclude: [ "cityId" ]
|
||||||
}
|
}
|
||||||
@@ -60,29 +99,32 @@ location.get("/", (req: Request, res: Response) => {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all data about a specific location
|
* @swagger
|
||||||
*
|
* /locations/{urlName}:
|
||||||
* @param urlName UrlName of the band (e.g. Red Hot Chili Peppers => red-hot-chili-peppers)
|
* get:
|
||||||
|
* summary: Download all available informations about a specific locations
|
||||||
|
* tags: [Locations]
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: urlName
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: true
|
||||||
|
* description: Url name of the location to request for
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Single of location objects
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/location'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
*/
|
*/
|
||||||
location.get("/location/:urlName", (req: Request, res: Response) => {
|
location.get("/location/:urlName", (req: Request, res: Response) => {
|
||||||
Location.findOne({
|
Location.findOne({
|
||||||
where: { urlName: req.params.urlName },
|
where: { urlName: req.params.urlName },
|
||||||
include: [
|
include: locationStructure,
|
||||||
City,
|
|
||||||
{
|
|
||||||
model: Concert,
|
|
||||||
include: [ Band ],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: SeatGroup,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: SeatRow,
|
|
||||||
include: [ Seat ]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: [ "cityId" ]
|
exclude: [ "cityId" ]
|
||||||
}
|
}
|
||||||
@@ -105,9 +147,27 @@ location.get("/location/:urlName", (req: Request, res: Response) => {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for Locations
|
* @swagger
|
||||||
*
|
* /locations/search:
|
||||||
* @query value Search term to look for
|
* get:
|
||||||
|
* summary: Search for locations
|
||||||
|
* tags: [Locations]
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: value
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: true
|
||||||
|
* description: Search term
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of band objects
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/location'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
*/
|
*/
|
||||||
location.get("/search", (req: Request, res: Response) => {
|
location.get("/search", (req: Request, res: Response) => {
|
||||||
Location.findAll({
|
Location.findAll({
|
||||||
@@ -128,7 +188,7 @@ location.get("/search", (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
include: [ City, Concert ]
|
include: locationStructure
|
||||||
})
|
})
|
||||||
.then(locations => {
|
.then(locations => {
|
||||||
res.status(200).json(locations)
|
res.status(200).json(locations)
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* tags:
|
||||||
|
* name: Orders
|
||||||
|
* description: API to manage orders
|
||||||
|
*/
|
||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import { Order } from "../models/ordering/order.model";
|
import { Order } from "../models/ordering/order.model";
|
||||||
import { Concert } from "../models/acts/concert.model";
|
import { Concert } from "../models/acts/concert.model";
|
||||||
@@ -15,8 +21,34 @@ import { Account } from "../models/user/account.model";
|
|||||||
|
|
||||||
export const order = Router()
|
export const order = Router()
|
||||||
|
|
||||||
// Get all orders
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /orders:
|
||||||
|
* get:
|
||||||
|
* summary: Get orders of an account or all available
|
||||||
|
* tags: [Orders]
|
||||||
|
* security:
|
||||||
|
* - JWT: []
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: id
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: false
|
||||||
|
* description: User account id to filter the orders
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/order'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
order.get("/", verifyToken, (req: Request, res: Response) => {
|
order.get("/", verifyToken, (req: Request, res: Response) => {
|
||||||
|
const accountId = req.query.id
|
||||||
|
|
||||||
Order.findAll({
|
Order.findAll({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
@@ -47,69 +79,56 @@ order.get("/", verifyToken, (req: Request, res: Response) => {
|
|||||||
},
|
},
|
||||||
Address,
|
Address,
|
||||||
Payment,
|
Payment,
|
||||||
Account
|
Account,
|
||||||
]
|
|
||||||
})
|
|
||||||
.then(orders => {
|
|
||||||
res.status(200).json(orders)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
res.status(500).send()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Get all orders of one account by it's user id
|
|
||||||
order.get("/:id", (req: Request, res: Response) => {
|
|
||||||
Order.findAll({
|
|
||||||
where: { accountId: req.params.id },
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Ticket,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Concert,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Band
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: Location,
|
|
||||||
include: [ City ]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: [
|
exclude: [ "accountId", "addressId", "paymentId" ]
|
||||||
"categoryId",
|
|
||||||
"brandId"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
model: Seat,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: SeatRow,
|
|
||||||
include: [ SeatGroup ]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
Payment,
|
|
||||||
Address
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
.then(orders => {
|
.then(orders => {
|
||||||
|
if (accountId != undefined) {
|
||||||
|
let filteredOrders = orders.filter(order => {
|
||||||
|
return order.id == accountId
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).json(filteredOrders)
|
||||||
|
} else {
|
||||||
res.status(200).json(orders)
|
res.status(200).json(orders)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
res.status(500).send()
|
res.status(500).send()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Place a new order
|
/**
|
||||||
order.post("/", (req: Request, res: Response) => {
|
* @swagger
|
||||||
|
* /orders:
|
||||||
|
* post:
|
||||||
|
* summary: Place a new order
|
||||||
|
* tags: [Orders]
|
||||||
|
* security:
|
||||||
|
* - JWT: []
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: id
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: false
|
||||||
|
* description: User account id to filter the orders
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/order'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
|
order.post("/", verifyToken, (req: Request, res: Response) => {
|
||||||
|
req.body["accountId"] = req["id"]
|
||||||
|
|
||||||
Order.create(req.body)
|
Order.create(req.body)
|
||||||
.then(async order => {
|
.then(async order => {
|
||||||
for (let ticket of req.body.tickets) {
|
for (let ticket of req.body.tickets) {
|
||||||
@@ -137,6 +156,30 @@ order.post("/", (req: Request, res: Response) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /orders:
|
||||||
|
* patch:
|
||||||
|
* summary: Update an order
|
||||||
|
* tags: [Orders]
|
||||||
|
* parameters:
|
||||||
|
* - in: body
|
||||||
|
* name: order
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required: true
|
||||||
|
* description: Updated order object
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/order'
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
order.patch("/", (req: Request, res: Response) => {
|
order.patch("/", (req: Request, res: Response) => {
|
||||||
Order.update(req.body, {
|
Order.update(req.body, {
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import bodyParser from 'body-parser'
|
import bodyParser from 'body-parser'
|
||||||
|
import swaggerJsdoc from "swagger-jsdoc"
|
||||||
|
import swaggerUi from "swagger-ui-express"
|
||||||
import { api } from './routes/api.routes'
|
import { api } from './routes/api.routes'
|
||||||
import { startDatabase } from './database'
|
import { startDatabase } from './database'
|
||||||
import { order } from './routes/order.routes'
|
import { order } from './routes/order.routes'
|
||||||
@@ -12,6 +14,7 @@ import { location } from './routes/location.routes'
|
|||||||
import { city } from './routes/city.routes'
|
import { city } from './routes/city.routes'
|
||||||
import { exercises } from './routes/exercise.routes'
|
import { exercises } from './routes/exercise.routes'
|
||||||
import { files } from './routes/files.routes'
|
import { files } from './routes/files.routes'
|
||||||
|
import swaggerFile from './swagger.json'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const port = 3000
|
const port = 3000
|
||||||
@@ -33,7 +36,7 @@ app.use("/files", files)
|
|||||||
|
|
||||||
// Add delay for more realistic response times
|
// Add delay for more realistic response times
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
setTimeout(next, Math.floor((Math.random() * 1000) + 100))
|
setTimeout(next, Math.floor((Math.random() * 500) + 100))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
@@ -46,6 +49,17 @@ app.use("/accounts", account)
|
|||||||
app.use("/cities", city)
|
app.use("/cities", city)
|
||||||
app.use("/concerts", concert)
|
app.use("/concerts", concert)
|
||||||
|
|
||||||
|
|
||||||
|
// Swagger API documentation
|
||||||
|
const specs = swaggerJsdoc(swaggerFile);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
"/api-docs",
|
||||||
|
swaggerUi.serve,
|
||||||
|
swaggerUi.setup(specs, { explorer: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
const server = app.listen(port, () => {
|
const server = app.listen(port, () => {
|
||||||
console.log(`Server is running and listening to port ${port}`)
|
console.log(`Server is running and listening to port ${port}`)
|
||||||
|
|||||||
796
backend/swagger.json
Normal file
@@ -0,0 +1,796 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"definition": {
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "EventMaster API",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"description": "Dokumentation über alle API-Endpunkte des Backends",
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://spdx.org/licenses/MIT.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "http://localhost:3000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"JWT": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"in": "header",
|
||||||
|
"name": "Json Web Token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"city": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the city"
|
||||||
|
},
|
||||||
|
"country": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of country of the city"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 2,
|
||||||
|
"name": "Hannover",
|
||||||
|
"country": "Germany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loginResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"success": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Login successful state"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Individual created access token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"success": true,
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjI2MiwiaWF0IjoxNzMzNzYwOTY3fQ.I3rR71c-k2Y2WB0dkd1QEgHxsIRGl4s69YprBNuhX7w"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimalAccount": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Account username"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Encrypted password"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "E-Mail address of user"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"username": "maxmustermann",
|
||||||
|
"password": "supersecret",
|
||||||
|
"email": "tijjji@didjhli.de"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimalAccountResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Account username"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Encrypted password"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "E-Mail address of user"
|
||||||
|
},
|
||||||
|
"accountRoleId": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "ID of account role"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 263,
|
||||||
|
"username": "maxmustermann",
|
||||||
|
"password": "8746fb88adbae61ffa68193ee0bb8050",
|
||||||
|
"email": "tijjji@didjhli.de",
|
||||||
|
"accountRoleId": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"placeOrderBody": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Account username"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Encrypted password"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "E-Mail address of user"
|
||||||
|
},
|
||||||
|
"accountRoleId": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "ID of account role"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 263,
|
||||||
|
"username": "maxmustermann",
|
||||||
|
"password": "8746fb88adbae61ffa68193ee0bb8050",
|
||||||
|
"email": "tijjji@didjhli.de",
|
||||||
|
"accountRoleId": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"genre": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the genre"
|
||||||
|
},
|
||||||
|
"bands": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Bands with this genre object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"id": 562,
|
||||||
|
"name": "Funk Rock",
|
||||||
|
"bands": [
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-1.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-2.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-3.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-4.jpg"
|
||||||
|
],
|
||||||
|
"id": 265,
|
||||||
|
"name": "Red Hot Chili Peppers",
|
||||||
|
"foundingYear": 1983,
|
||||||
|
"descriptionEn": "The Red Hot Chili Peppers are an American rock band formed in Los Angeles in 1983, comprising vocalist Anthony Kiedis, bassist Flea, drummer Chad Smith, and guitarist John Frusciante. Their music incorporates elements of alternative rock, funk, punk rock, hard rock, hip hop, and psychedelic rock. Their eclectic range has influenced genres such as funk metal, rap metal, rap rock, and nu metal. With over 120 million records sold worldwide, the Red Hot Chili Peppers are one of the top-selling bands of all time.",
|
||||||
|
"descriptionDe": "Red Hot Chili Peppers (Abkürzung: RHCP) ist eine 1983 gegründete US-amerikanische Funk- und Alternative-Rockband. Sie zählt zu den kommerziell erfolgreichsten Vertretern des Crossover. Ihr Album Blood Sugar Sex Magik gilt als eines der bedeutendsten dieses Genres.",
|
||||||
|
"imageMembers": "http://localhost:3000/static/bands/red-hot-chili-peppers-members.jpg",
|
||||||
|
"logo": "http://localhost:3000/static/bands/red-hot-chili-peppers-logo.png",
|
||||||
|
"BandGenre": {
|
||||||
|
"id": 793,
|
||||||
|
"genreId": 562,
|
||||||
|
"bandId": 265
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 563,
|
||||||
|
"name": "Alternative Rock",
|
||||||
|
"bands": [
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-1.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-2.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-3.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-4.jpg"
|
||||||
|
],
|
||||||
|
"id": 265,
|
||||||
|
"name": "Red Hot Chili Peppers",
|
||||||
|
"foundingYear": 1983,
|
||||||
|
"descriptionEn": "The Red Hot Chili Peppers are an American rock band formed in Los Angeles in 1983, comprising vocalist Anthony Kiedis, bassist Flea, drummer Chad Smith, and guitarist John Frusciante. Their music incorporates elements of alternative rock, funk, punk rock, hard rock, hip hop, and psychedelic rock. Their eclectic range has influenced genres such as funk metal, rap metal, rap rock, and nu metal. With over 120 million records sold worldwide, the Red Hot Chili Peppers are one of the top-selling bands of all time.",
|
||||||
|
"descriptionDe": "Red Hot Chili Peppers (Abkürzung: RHCP) ist eine 1983 gegründete US-amerikanische Funk- und Alternative-Rockband. Sie zählt zu den kommerziell erfolgreichsten Vertretern des Crossover. Ihr Album Blood Sugar Sex Magik gilt als eines der bedeutendsten dieses Genres.",
|
||||||
|
"imageMembers": "http://localhost:3000/static/bands/red-hot-chili-peppers-members.jpg",
|
||||||
|
"logo": "http://localhost:3000/static/bands/red-hot-chili-peppers-logo.png",
|
||||||
|
"BandGenre": {
|
||||||
|
"id": 794,
|
||||||
|
"genreId": 563,
|
||||||
|
"bandId": 265
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the genre"
|
||||||
|
},
|
||||||
|
"bands": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Bands with this genre object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 562,
|
||||||
|
"name": "Funk Rock",
|
||||||
|
"bands": [
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-1.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-2.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-3.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-4.jpg"
|
||||||
|
],
|
||||||
|
"id": 265,
|
||||||
|
"name": "Red Hot Chili Peppers",
|
||||||
|
"foundingYear": 1983,
|
||||||
|
"descriptionEn": "The Red Hot Chili Peppers are an American rock band formed in Los Angeles in 1983, comprising vocalist Anthony Kiedis, bassist Flea, drummer Chad Smith, and guitarist John Frusciante. Their music incorporates elements of alternative rock, funk, punk rock, hard rock, hip hop, and psychedelic rock. Their eclectic range has influenced genres such as funk metal, rap metal, rap rock, and nu metal. With over 120 million records sold worldwide, the Red Hot Chili Peppers are one of the top-selling bands of all time.",
|
||||||
|
"descriptionDe": "Red Hot Chili Peppers (Abkürzung: RHCP) ist eine 1983 gegründete US-amerikanische Funk- und Alternative-Rockband. Sie zählt zu den kommerziell erfolgreichsten Vertretern des Crossover. Ihr Album Blood Sugar Sex Magik gilt als eines der bedeutendsten dieses Genres.",
|
||||||
|
"imageMembers": "http://localhost:3000/static/bands/red-hot-chili-peppers-members.jpg",
|
||||||
|
"logo": "http://localhost:3000/static/bands/red-hot-chili-peppers-logo.png",
|
||||||
|
"BandGenre": {
|
||||||
|
"id": 793,
|
||||||
|
"genreId": 562,
|
||||||
|
"bandId": 265
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"orderedAt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Timestamp of order"
|
||||||
|
},
|
||||||
|
"tickets": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of Ticket objects"
|
||||||
|
},
|
||||||
|
"addresses": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Address object"
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Payment object"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Account object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 112,
|
||||||
|
"orderedAt": "2024-11-29T12:38:36.381Z",
|
||||||
|
"shipped": false,
|
||||||
|
"tickets": [
|
||||||
|
{
|
||||||
|
"id": 144,
|
||||||
|
"orderId": 112,
|
||||||
|
"orderPrice": 184,
|
||||||
|
"concertId": 892,
|
||||||
|
"seatId": 106331,
|
||||||
|
"concert": {
|
||||||
|
"id": 892,
|
||||||
|
"date": "2024-11-30",
|
||||||
|
"name": "Unlimited Love",
|
||||||
|
"price": 92,
|
||||||
|
"image": "http://localhost:3000/static/concerts/unlimited-love-tour.jpg",
|
||||||
|
"inStock": 170,
|
||||||
|
"offered": true,
|
||||||
|
"bandId": 265,
|
||||||
|
"locationId": 834,
|
||||||
|
"band": {
|
||||||
|
"images": [
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-1.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-2.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-3.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-4.jpg"
|
||||||
|
],
|
||||||
|
"id": 265,
|
||||||
|
"name": "Red Hot Chili Peppers",
|
||||||
|
"foundingYear": 1983,
|
||||||
|
"descriptionEn": "The Red Hot Chili Peppers are an American rock band formed in Los Angeles in 1983, comprising vocalist Anthony Kiedis, bassist Flea, drummer Chad Smith, and guitarist John Frusciante. Their music incorporates elements of alternative rock, funk, punk rock, hard rock, hip hop, and psychedelic rock. Their eclectic range has influenced genres such as funk metal, rap metal, rap rock, and nu metal. With over 120 million records sold worldwide, the Red Hot Chili Peppers are one of the top-selling bands of all time.",
|
||||||
|
"descriptionDe": "Red Hot Chili Peppers (Abkürzung: RHCP) ist eine 1983 gegründete US-amerikanische Funk- und Alternative-Rockband. Sie zählt zu den kommerziell erfolgreichsten Vertretern des Crossover. Ihr Album Blood Sugar Sex Magik gilt als eines der bedeutendsten dieses Genres.",
|
||||||
|
"imageMembers": "http://localhost:3000/static/bands/red-hot-chili-peppers-members.jpg",
|
||||||
|
"logo": "http://localhost:3000/static/bands/red-hot-chili-peppers-logo.png"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"id": 834,
|
||||||
|
"urlName": "swiss-life-hall",
|
||||||
|
"name": "Swiss Life Hall",
|
||||||
|
"address": "Ferdinand-Wilhelm-Fricke-Weg 8",
|
||||||
|
"cityId": 246,
|
||||||
|
"imageIndoor": "http://localhost:3000/static/locations/swiss-life-hall-indoor.jpg",
|
||||||
|
"imageOutdoor": "http://localhost:3000/static/locations/swiss-life-hall-outdoor.jpg",
|
||||||
|
"layout": 2,
|
||||||
|
"capacity": 180,
|
||||||
|
"city": {
|
||||||
|
"id": 246,
|
||||||
|
"name": "Hannover",
|
||||||
|
"country": "Germany"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"seat": {
|
||||||
|
"id": 106331,
|
||||||
|
"seatNr": 1,
|
||||||
|
"seatRowId": 14701,
|
||||||
|
"seatRow": {
|
||||||
|
"id": 14701,
|
||||||
|
"row": 0,
|
||||||
|
"seatGroupId": 3872,
|
||||||
|
"seatGroup": {
|
||||||
|
"id": 3872,
|
||||||
|
"name": "A",
|
||||||
|
"surcharge": 30,
|
||||||
|
"capacity": 40,
|
||||||
|
"standingArea": true,
|
||||||
|
"locationId": 834
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"address": {
|
||||||
|
"id": 342,
|
||||||
|
"accountId": 255,
|
||||||
|
"street": "Laportestraße",
|
||||||
|
"houseNumber": 22,
|
||||||
|
"postalCode": 30449,
|
||||||
|
"city": "Hannover"
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"id": 247,
|
||||||
|
"accountId": 255,
|
||||||
|
"bankName": "Deutsche Bank",
|
||||||
|
"iban": "DE92500105175721645777"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"id": 255,
|
||||||
|
"username": "hagemeister93",
|
||||||
|
"password": "e1e3981e5b0c009c018c5726a4be5eee",
|
||||||
|
"email": "hagemeister93@gmail.com",
|
||||||
|
"firstName": "Laurin",
|
||||||
|
"lastName": "Hagemeister",
|
||||||
|
"accountRoleId": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"useraccount": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Account username"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Encrypted password"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "E-Mail address of user"
|
||||||
|
},
|
||||||
|
"firstName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "First name of user"
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Last name of user"
|
||||||
|
},
|
||||||
|
"addresses": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of Address objects"
|
||||||
|
},
|
||||||
|
"accountRole": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Account role object"
|
||||||
|
},
|
||||||
|
"payments": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of Payments objects"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 262,
|
||||||
|
"username": "max",
|
||||||
|
"password": "06f7a5f329fed099ad36026f9623e6ce",
|
||||||
|
"email": "titi@didi.de",
|
||||||
|
"firstName": "Max",
|
||||||
|
"lastName": "Mustermann",
|
||||||
|
"accountRoleId": 1,
|
||||||
|
"addresses": [
|
||||||
|
{
|
||||||
|
"id": 352,
|
||||||
|
"accountId": 262,
|
||||||
|
"street": "Musterstraße",
|
||||||
|
"houseNumber": 21,
|
||||||
|
"postalCode": 30167,
|
||||||
|
"city": "Hannover"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accountRole": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "User",
|
||||||
|
"privilegeBuy": true,
|
||||||
|
"privilegeAdminPanel": false
|
||||||
|
},
|
||||||
|
"payments": [
|
||||||
|
{
|
||||||
|
"id": 254,
|
||||||
|
"accountId": 262,
|
||||||
|
"bankName": "Deutsche Bank",
|
||||||
|
"iban": "DE293948484738383829"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exercise": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"nameDe": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "German exercise name"
|
||||||
|
},
|
||||||
|
"nameEn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "English exercise name"
|
||||||
|
},
|
||||||
|
"exerciseNr": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of exercise in group"
|
||||||
|
},
|
||||||
|
"descriptionDe": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "German description text"
|
||||||
|
},
|
||||||
|
"descriptionEn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "English description text"
|
||||||
|
},
|
||||||
|
"solved": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "State of solved"
|
||||||
|
},
|
||||||
|
"exerciseGroup": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Exercise group object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"id": 350,
|
||||||
|
"nameDe": "Registrieren",
|
||||||
|
"nameEn": "Register",
|
||||||
|
"exerciseNr": 1,
|
||||||
|
"descriptionDe": "Wir richten uns einen gewöhnlichen Account auf der Plattform ein. Navigiere hierzu auf die Account-Seite und registriere dich.",
|
||||||
|
"descriptionEn": "Create a new account in the online shop",
|
||||||
|
"solved": true,
|
||||||
|
"exerciseGroup": {
|
||||||
|
"id": 113,
|
||||||
|
"nameDe": "Den Shop kennenlernen",
|
||||||
|
"nameEn": "Getting to know the shop",
|
||||||
|
"groupNr": 0,
|
||||||
|
"descriptionDe": "Vor einem Angriff ist es wichtig zu verstehen, wie die Webseite aufgebaut ist. Wie sind die URLs strukturiert? Wo befinden sich Eingabefelder welche im Backend eine SQL Abfrage stellen?",
|
||||||
|
"descriptionEn": "todo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of folder"
|
||||||
|
},
|
||||||
|
"nrOrItems": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of files in folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"name": "artists",
|
||||||
|
"description": 41
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of file"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "File size in Bytes"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Text content, only for Text/Code files"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Resource URL"
|
||||||
|
},
|
||||||
|
"copyright": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Copyright object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"name": "alex-turner.jpg",
|
||||||
|
"size": 551625,
|
||||||
|
"content": "",
|
||||||
|
"url": "http://localhost:3000/static/artists/alex-turner.jpg",
|
||||||
|
"copyright": {
|
||||||
|
"image": "alex-turner.jpg",
|
||||||
|
"license": "CC BY 2.0",
|
||||||
|
"creator": "Raph_PH",
|
||||||
|
"url": "https://upload.wikimedia.org/wikipedia/commons/9/95/Alex_Turner%2C_Way_Out_West_2018.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"concert": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Date of the concert"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of concert"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Lowest price of concert"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Concert image"
|
||||||
|
},
|
||||||
|
"inStock": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Amount of available tickets"
|
||||||
|
},
|
||||||
|
"offered": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Display concert in UI"
|
||||||
|
},
|
||||||
|
"band": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Band object"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Location object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"id": 892,
|
||||||
|
"date": "2024-11-30",
|
||||||
|
"name": "Unlimited Love",
|
||||||
|
"price": 92,
|
||||||
|
"image": "http://localhost:3000/static/concerts/unlimited-love-tour.jpg",
|
||||||
|
"inStock": 169,
|
||||||
|
"offered": true,
|
||||||
|
"bandId": 265,
|
||||||
|
"locationId": 834,
|
||||||
|
"band": {
|
||||||
|
"images": [
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-1.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-2.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-3.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-4.jpg"
|
||||||
|
],
|
||||||
|
"id": 265,
|
||||||
|
"name": "Red Hot Chili Peppers",
|
||||||
|
"foundingYear": 1983,
|
||||||
|
"descriptionEn": "The Red Hot Chili Peppers are an American rock band formed in Los Angeles in 1983, comprising vocalist Anthony Kiedis, bassist Flea, drummer Chad Smith, and guitarist John Frusciante. Their music incorporates elements of alternative rock, funk, punk rock, hard rock, hip hop, and psychedelic rock. Their eclectic range has influenced genres such as funk metal, rap metal, rap rock, and nu metal. With over 120 million records sold worldwide, the Red Hot Chili Peppers are one of the top-selling bands of all time.",
|
||||||
|
"descriptionDe": "Red Hot Chili Peppers (Abkürzung: RHCP) ist eine 1983 gegründete US-amerikanische Funk- und Alternative-Rockband. Sie zählt zu den kommerziell erfolgreichsten Vertretern des Crossover. Ihr Album Blood Sugar Sex Magik gilt als eines der bedeutendsten dieses Genres.",
|
||||||
|
"imageMembers": "http://localhost:3000/static/bands/red-hot-chili-peppers-members.jpg",
|
||||||
|
"logo": "http://localhost:3000/static/bands/red-hot-chili-peppers-logo.png"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"id": 834,
|
||||||
|
"urlName": "swiss-life-hall",
|
||||||
|
"name": "Swiss Life Hall",
|
||||||
|
"address": "Ferdinand-Wilhelm-Fricke-Weg 8",
|
||||||
|
"imageIndoor": "http://localhost:3000/static/locations/swiss-life-hall-indoor.jpg",
|
||||||
|
"imageOutdoor": "http://localhost:3000/static/locations/swiss-life-hall-outdoor.jpg",
|
||||||
|
"layout": 2,
|
||||||
|
"capacity": 180,
|
||||||
|
"city": {
|
||||||
|
"id": 246,
|
||||||
|
"name": "Hannover",
|
||||||
|
"country": "Germany"
|
||||||
|
},
|
||||||
|
"seatGroups": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"band": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The auto-generated id"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the band"
|
||||||
|
},
|
||||||
|
"foundingYear": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Founding year of the band"
|
||||||
|
},
|
||||||
|
"descriptionEn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "English description text"
|
||||||
|
},
|
||||||
|
"descriptionDe": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "German description text"
|
||||||
|
},
|
||||||
|
"imageMembers": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL to image of band members"
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL to image of band logo"
|
||||||
|
},
|
||||||
|
"genres": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of Genre objects which fits the bands music"
|
||||||
|
},
|
||||||
|
"concerts": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of Concert objects"
|
||||||
|
},
|
||||||
|
"nrOfConcerts": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of concerts"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Average rating of the band"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"images": [
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-1.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-2.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-3.jpg",
|
||||||
|
"http://localhost:3000/static/bands/red-hot-chili-peppers-4.jpg"
|
||||||
|
],
|
||||||
|
"id": 265,
|
||||||
|
"name": "Red Hot Chili Peppers",
|
||||||
|
"foundingYear": 1983,
|
||||||
|
"descriptionEn": "The Red Hot Chili Peppers are an American rock band formed in Los Angeles in 1983, comprising vocalist Anthony Kiedis, bassist Flea, drummer Chad Smith, and guitarist John Frusciante. Their music incorporates elements of alternative rock, funk, punk rock, hard rock, hip hop, and psychedelic rock. Their eclectic range has influenced genres such as funk metal, rap metal, rap rock, and nu metal. With over 120 million records sold worldwide, the Red Hot Chili Peppers are one of the top-selling bands of all time.",
|
||||||
|
"descriptionDe": "Red Hot Chili Peppers (Abkürzung: RHCP) ist eine 1983 gegründete US-amerikanische Funk- und Alternative-Rockband. Sie zählt zu den kommerziell erfolgreichsten Vertretern des Crossover. Ihr Album Blood Sugar Sex Magik gilt als eines der bedeutendsten dieses Genres.",
|
||||||
|
"imageMembers": "http://localhost:3000/static/bands/red-hot-chili-peppers-members.jpg",
|
||||||
|
"logo": "http://localhost:3000/static/bands/red-hot-chili-peppers-logo.png",
|
||||||
|
"genres": [
|
||||||
|
{
|
||||||
|
"name": "Funk Rock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Alternative Rock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Crossover"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concerts": [
|
||||||
|
{
|
||||||
|
"id": 892,
|
||||||
|
"date": "2024-11-30",
|
||||||
|
"name": "Unlimited Love",
|
||||||
|
"price": 92,
|
||||||
|
"image": "http://localhost:3000/static/concerts/unlimited-love-tour.jpg",
|
||||||
|
"inStock": 170,
|
||||||
|
"offered": true,
|
||||||
|
"bandId": 265,
|
||||||
|
"locationId": 834
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 893,
|
||||||
|
"date": "2024-12-07",
|
||||||
|
"name": "Unlimited Love",
|
||||||
|
"price": 92,
|
||||||
|
"image": "http://localhost:3000/static/concerts/unlimited-love-tour.jpg",
|
||||||
|
"inStock": 170,
|
||||||
|
"offered": true,
|
||||||
|
"bandId": 265,
|
||||||
|
"locationId": 834
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 894,
|
||||||
|
"date": "2024-12-11",
|
||||||
|
"name": "Unlimited Love",
|
||||||
|
"price": 119.9,
|
||||||
|
"image": "http://localhost:3000/static/concerts/unlimited-love-tour.jpg",
|
||||||
|
"inStock": 8736,
|
||||||
|
"offered": true,
|
||||||
|
"bandId": 265,
|
||||||
|
"locationId": 838
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 895,
|
||||||
|
"date": "2024-12-18",
|
||||||
|
"name": "Unlimited Love",
|
||||||
|
"price": 114.9,
|
||||||
|
"image": "http://localhost:3000/static/concerts/unlimited-love-tour.jpg",
|
||||||
|
"inStock": 2793,
|
||||||
|
"offered": true,
|
||||||
|
"bandId": 265,
|
||||||
|
"locationId": 842
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 896,
|
||||||
|
"date": "2024-12-30",
|
||||||
|
"name": "Unlimited Love",
|
||||||
|
"price": 124.9,
|
||||||
|
"image": "http://localhost:3000/static/concerts/unlimited-love-tour.jpg",
|
||||||
|
"inStock": 3079,
|
||||||
|
"offered": true,
|
||||||
|
"bandId": 265,
|
||||||
|
"locationId": 845
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nrOfConcerts": 5,
|
||||||
|
"rating": 4.428571428571429
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apis": [
|
||||||
|
"./backend/routes/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"icon": "public/logo-small.png"
|
"icon": "public/logo-small.png"
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": ["deb"],
|
"target": ["deb", "rpm"],
|
||||||
"maintainer": "Tobias Zoghaib",
|
"maintainer": "Tobias Zoghaib",
|
||||||
"icon": "public/logo-small.png",
|
"icon": "public/logo-small.png",
|
||||||
"category": "Education"
|
"category": "Education"
|
||||||
@@ -35,6 +35,8 @@
|
|||||||
"!release",
|
"!release",
|
||||||
"!src",
|
"!src",
|
||||||
"!dist",
|
"!dist",
|
||||||
"!out"
|
"!out",
|
||||||
|
"!misc",
|
||||||
|
"!database.sqlite"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
19
example-config.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"theme": "dark",
|
||||||
|
"language": "en",
|
||||||
|
"notAvailableExercises": [
|
||||||
|
"getting-known-register",
|
||||||
|
"getting-known-profile",
|
||||||
|
"getting-known-buy-ticket",
|
||||||
|
"broken-access-control-exercise-page",
|
||||||
|
"broken-access-control-hidden-concert",
|
||||||
|
"sql-injection-database-schema",
|
||||||
|
"sql-injection-all-accounts",
|
||||||
|
"sql-injection-account-roles",
|
||||||
|
"sql-injection-upgrade-privileges",
|
||||||
|
"sql-injection-capture-account",
|
||||||
|
"sql-injection-delete-rating",
|
||||||
|
"cross-site-scripting-hello-world",
|
||||||
|
"cross-site-scripting-external-script"
|
||||||
|
]
|
||||||
|
}
|
||||||
1481
misc/database.drawio
84
misc/frontend-backend-system.drawio
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.0.9 Chrome/128.0.6613.186 Electron/32.2.5 Safari/537.36" version="26.0.9">
|
||||||
|
<diagram name="Page-1" id="6ideKt-3XiqBCAVgAsyQ">
|
||||||
|
<mxGraphModel dx="1195" dy="689" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-49" value="Frontend" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="90" width="400" height="220" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-52" value="Subpage<div>"All Bands"</div>" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="50" y="120" width="150" height="180" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-50" value="Backend" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="630" y="90" width="210" height="280" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-55" value="API Endpoint" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="650" y="120" width="170" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-46" value="bandStore" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="270" y="120" width="160" height="160" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-70" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#00CC00;" parent="1" source="KDqfIFqn06qLZiIO6-10-47" target="KDqfIFqn06qLZiIO6-10-65" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-71" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#00CC00;" parent="1" source="KDqfIFqn06qLZiIO6-10-47" target="KDqfIFqn06qLZiIO6-10-66" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-73" value="<font color="#00cc00">Handles</font><div><font color="#00cc00">changes</font></div>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#00CC00;" parent="1" source="KDqfIFqn06qLZiIO6-10-47" target="KDqfIFqn06qLZiIO6-10-72" edge="1">
|
||||||
|
<mxGeometry x="0.168" y="24" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-47" value="bands" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="290" y="230" width="120" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-59" value="<div style=""><font style="color: rgb(204, 0, 0);">Stores</font></div><div style=""><font style="color: rgb(204, 0, 0);">results</font></div>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;align=left;fillColor=#e51400;strokeColor=#B20000;" parent="1" source="KDqfIFqn06qLZiIO6-10-48" target="KDqfIFqn06qLZiIO6-10-47" edge="1">
|
||||||
|
<mxGeometry x="-0.2" y="10" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-67" value="<font style="color: rgb(0, 29, 188);">requests</font>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;verticalAlign=bottom;fillColor=#0050ef;strokeColor=#001DBC;" parent="1" source="KDqfIFqn06qLZiIO6-10-48" target="KDqfIFqn06qLZiIO6-10-56" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-48" value="getBands()" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="290" y="150" width="120" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-53" value="<font color="#bd7000">Call function</font>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;verticalAlign=bottom;fillColor=#f0a30a;strokeColor=#BD7000;" parent="1" source="KDqfIFqn06qLZiIO6-10-52" target="KDqfIFqn06qLZiIO6-10-48" edge="1">
|
||||||
|
<mxGeometry x="-0.2148" y="7" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-68" value="<font style="color: rgb(0, 87, 0);">send http code &amp;</font><div><span style="color: light-dark(rgb(0, 87, 0), rgb(0, 87, 0)); background-color: light-dark(#ffffff, var(--ge-dark-color, #121212));">data sets</span></div>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;verticalAlign=top;fillColor=#008a00;strokeColor=#005700;" parent="1" source="KDqfIFqn06qLZiIO6-10-56" target="KDqfIFqn06qLZiIO6-10-48" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-56" value="/bands" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="670" y="150" width="120" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-63" value="<font style="color: rgb(0, 153, 153);">get raw</font><div><font color="#009999">datasets</font></div>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.855;exitY=0;exitDx=0;exitDy=4.35;exitPerimeter=0;align=left;strokeColor=#009999;" parent="1" source="KDqfIFqn06qLZiIO6-10-61" edge="1">
|
||||||
|
<mxGeometry x="-0.2154" y="-9" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset" />
|
||||||
|
<mxPoint x="751" y="180" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-61" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="280" width="60" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-62" value="<font style="color: rgb(255, 0, 255);">sends</font><div><font style="color: rgb(255, 0, 255);">SQL</font></div><div><font style="color: rgb(255, 0, 255);">command</font></div>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.321;exitY=1.017;exitDx=0;exitDy=0;entryX=0.145;entryY=0;entryDx=0;entryDy=4.35;entryPerimeter=0;align=right;strokeColor=#FF00FF;exitPerimeter=0;" parent="1" source="KDqfIFqn06qLZiIO6-10-56" target="KDqfIFqn06qLZiIO6-10-61" edge="1">
|
||||||
|
<mxGeometry x="0.2159" y="-9" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-65" value="Coldplay" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="65" y="170" width="120" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-66" value="Muse" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="65" y="210" width="120" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="KDqfIFqn06qLZiIO6-10-72" value="Radiohead" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="65" y="250" width="120" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.17 Chrome/128.0.6613.36 Electron/32.0.1 Safari/537.36" version="24.7.17">
|
|
||||||
<diagram name="Page-1" id="z4ePnmX8JLkG3N1GDPOr">
|
|
||||||
<mxGraphModel dx="1783" dy="1720" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1" target="Fk8QGSgGJ1bhqam2HO4i-3" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1" target="Fk8QGSgGJ1bhqam2HO4i-15" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" target="fY-Wzga6seKQwhded4A5-7">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="-360" y="700" as="targetPoint" />
|
|
||||||
<mxPoint x="-720" y="240" as="sourcePoint" />
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="-720" y="540" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1" target="Fk8QGSgGJ1bhqam2HO4i-13">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1" target="fY-Wzga6seKQwhded4A5-8">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="-680" y="-20" as="targetPoint" />
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="-720" y="-20" />
|
|
||||||
<mxPoint x="-680" y="-20" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1" target="fY-Wzga6seKQwhded4A5-1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1" target="Fk8QGSgGJ1bhqam2HO4i-14">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-1" target="Fk8QGSgGJ1bhqam2HO4i-4">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-1" value="Homepage<div><font face="Courier New"><b>/</b></font></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-800" y="-120" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-2" target="Fk8QGSgGJ1bhqam2HO4i-9">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-2" target="Fk8QGSgGJ1bhqam2HO4i-8">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-2" target="Fk8QGSgGJ1bhqam2HO4i-7">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-40" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;fillColor=#6d8764;strokeColor=#3A5431;" edge="1" parent="1" target="Fk8QGSgGJ1bhqam2HO4i-2">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="-679.6666666666667" y="70" as="targetPoint" />
|
|
||||||
<mxPoint x="-680" y="-20" as="sourcePoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-43" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-2" target="fY-Wzga6seKQwhded4A5-42">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-2" value="AccountHomePage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/account</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-600" y="-40" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-3" target="Fk8QGSgGJ1bhqam2HO4i-5">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-3" value="BandsPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/bands</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-600" y="280" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-4" value="PreferencesPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/preferences</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-600" y="600" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-5" target="fY-Wzga6seKQwhded4A5-5">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="-280" y="330" />
|
|
||||||
<mxPoint x="-280" y="330" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-5" value="BandDetailPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/bands/detail/:bandName</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-360" y="280" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-41" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;fillColor=#e51400;strokeColor=#B20000;" edge="1" parent="1" target="Fk8QGSgGJ1bhqam2HO4i-7">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="-200" y="-220" as="targetPoint" />
|
|
||||||
<mxPoint x="-680" y="-20" as="sourcePoint" />
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="-680" y="140" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-7" value="LoginPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/account/login</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-360" y="120" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-8" value="AccountData<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/account/data</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-360" y="40" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-9" value="OrdersPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/account/orders</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-360" y="-40" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-13" value="AdminHome<span style="background-color: initial;">Page</span><div><div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/admin</font></b><br></div></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-600" y="760" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-14" value="HelpPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/help</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-600" y="680" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-15" target="Fk8QGSgGJ1bhqam2HO4i-16">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-15" value="LocationsPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/locations</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-600" y="440" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fk8QGSgGJ1bhqam2HO4i-16" target="fY-Wzga6seKQwhded4A5-5">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="-280" y="420" />
|
|
||||||
<mxPoint x="-280" y="420" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Fk8QGSgGJ1bhqam2HO4i-16" value="LocationsDetailPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/locations/detail/:name</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="-360" y="440" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-30" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="fY-Wzga6seKQwhded4A5-1" target="fY-Wzga6seKQwhded4A5-5">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-1" value="ConcertsPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/concerts</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-600" y="360" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-3" value="if logged in" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-680" y="-60" width="80" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-4" value="else" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-690" y="110" width="80" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-34" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="fY-Wzga6seKQwhded4A5-5" target="fY-Wzga6seKQwhded4A5-7">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="-160" y="380" />
|
|
||||||
<mxPoint x="-160" y="540" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-5" value="ConcertsBookingPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/concerts/booking/:id</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-360" y="360" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-7" value="BasketPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/basket</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-600" y="520" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-37" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="fY-Wzga6seKQwhded4A5-8" target="Fk8QGSgGJ1bhqam2HO4i-5">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="fY-Wzga6seKQwhded4A5-8" target="fY-Wzga6seKQwhded4A5-5">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-39" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="fY-Wzga6seKQwhded4A5-8" target="Fk8QGSgGJ1bhqam2HO4i-16">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-8" value="SearchPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/search</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-600" y="200" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-14" value="if ticket add to basket" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-290" y="510" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-29" value="on log out" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-460" y="90" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="fY-Wzga6seKQwhded4A5-42" value="AccountRatingsPage<div><b style="font-family: &quot;Courier New&quot;;"><font style="font-size: 10px;">/account/ratings</font></b><br></div>" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="-360" y="-120" width="160" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
||||||
BIN
misc/images/admin-panel.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
misc/images/band-detail-seite.png
Normal file
|
After Width: | Height: | Size: 11 MiB |
BIN
misc/images/booking-concert.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
misc/images/frontend-backend-system.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
misc/images/homepage.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
misc/images/logo.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
misc/images/search.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
misc/images/snackbar-feedback.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
309
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "eventmaster",
|
"name": "eventmaster",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "eventmaster",
|
"name": "eventmaster",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
@@ -29,6 +29,9 @@
|
|||||||
"sequelize": "^6.37.4",
|
"sequelize": "^6.37.4",
|
||||||
"sequelize-typescript": "^2.1.6",
|
"sequelize-typescript": "^2.1.6",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
|
"swagger-autogen": "^2.23.7",
|
||||||
|
"swagger-jsdoc": "^6.2.8",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-i18n": "^10.0.4",
|
"vue-i18n": "^10.0.4",
|
||||||
"vue-router": "^4.4.5",
|
"vue-router": "^4.4.5",
|
||||||
@@ -44,6 +47,8 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.7",
|
"@types/jsonwebtoken": "^9.0.7",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
|
"@types/swagger-jsdoc": "^6.0.4",
|
||||||
|
"@types/swagger-ui-express": "^4.1.7",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
@@ -56,6 +61,50 @@
|
|||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||||
|
"version": "9.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz",
|
||||||
|
"integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jsdevtools/ono": "^7.1.3",
|
||||||
|
"@types/json-schema": "^7.0.6",
|
||||||
|
"call-me-maybe": "^1.0.1",
|
||||||
|
"js-yaml": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@apidevtools/openapi-schemas": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@apidevtools/swagger-methods": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@apidevtools/swagger-parser": {
|
||||||
|
"version": "10.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz",
|
||||||
|
"integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@apidevtools/json-schema-ref-parser": "^9.0.6",
|
||||||
|
"@apidevtools/openapi-schemas": "^2.0.4",
|
||||||
|
"@apidevtools/swagger-methods": "^3.0.2",
|
||||||
|
"@jsdevtools/ono": "^7.1.3",
|
||||||
|
"call-me-maybe": "^1.0.1",
|
||||||
|
"z-schema": "^5.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"openapi-types": ">=7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.25.7",
|
"version": "7.25.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
|
||||||
@@ -1546,6 +1595,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jsdevtools/ono": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@malept/cross-spawn-promise": {
|
"node_modules/@malept/cross-spawn-promise": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
|
||||||
@@ -1933,6 +1988,13 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@scarf/scarf": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@sideway/address": {
|
"node_modules/@sideway/address": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||||
@@ -2138,6 +2200,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/json-schema": {
|
||||||
|
"version": "7.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/jsonwebtoken": {
|
"node_modules/@types/jsonwebtoken": {
|
||||||
"version": "9.0.7",
|
"version": "9.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
|
||||||
@@ -2256,6 +2324,24 @@
|
|||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/swagger-jsdoc": {
|
||||||
|
"version": "6.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz",
|
||||||
|
"integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/swagger-ui-express": {
|
||||||
|
"version": "4.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.7.tgz",
|
||||||
|
"integrity": "sha512-ovLM9dNincXkzH4YwyYpll75vhzPBlWx6La89wwvYH7mHjVpf0X0K/vR/aUM7SRxmr5tt9z7E5XJcjQ46q+S3g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*",
|
||||||
|
"@types/serve-static": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/validator": {
|
"node_modules/@types/validator": {
|
||||||
"version": "13.12.2",
|
"version": "13.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
|
||||||
@@ -3050,7 +3136,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/array-flatten": {
|
"node_modules/array-flatten": {
|
||||||
@@ -3635,6 +3720,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-me-maybe": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/canvg": {
|
"node_modules/canvg": {
|
||||||
"version": "3.0.10",
|
"version": "3.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
|
||||||
@@ -4370,6 +4461,15 @@
|
|||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/defaults": {
|
"node_modules/defaults": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||||
@@ -4598,6 +4698,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/doctrine": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"esutils": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "2.5.7",
|
"version": "2.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz",
|
||||||
@@ -5060,6 +5172,15 @@
|
|||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/esutils": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/etag": {
|
"node_modules/etag": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
@@ -5086,9 +5207,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/exifreader/node_modules/@xmldom/xmldom": {
|
"node_modules/exifreader/node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.9.5",
|
"version": "0.9.6",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.6.tgz",
|
||||||
"integrity": "sha512-6g1EwSs8cr8JhP1iBxzyVAWM6BIDvx9Y3FZRIQiMDzgG43Pxi8YkWOZ0nQj2NHgNzgXDZbJewFx/n+YAvMZrfg==",
|
"integrity": "sha512-Su4xcxR0CPGwlDHNmVP09fqET9YxbyDXHaSob6JlBH7L6reTYaeim6zbk9o08UarO0L5GTRo3uzl0D+9lSxmvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -6210,7 +6331,6 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
@@ -6252,7 +6372,6 @@
|
|||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
@@ -6444,6 +6563,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.get": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||||
|
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
@@ -6456,6 +6581,12 @@
|
|||||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.isequal": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.isinteger": {
|
"node_modules/lodash.isinteger": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
@@ -6480,6 +6611,12 @@
|
|||||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.mergewith": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.once": {
|
"node_modules/lodash.once": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
@@ -7229,6 +7366,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openapi-types": {
|
||||||
|
"version": "12.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
|
||||||
|
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/ora": {
|
"node_modules/ora": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
||||||
@@ -8816,6 +8960,116 @@
|
|||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/swagger-autogen": {
|
||||||
|
"version": "2.23.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz",
|
||||||
|
"integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "^7.4.1",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"glob": "^7.1.7",
|
||||||
|
"json5": "^2.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-autogen/node_modules/acorn": {
|
||||||
|
"version": "7.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||||
|
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-jsdoc": {
|
||||||
|
"version": "6.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz",
|
||||||
|
"integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "6.2.0",
|
||||||
|
"doctrine": "3.0.0",
|
||||||
|
"glob": "7.1.6",
|
||||||
|
"lodash.mergewith": "^4.6.2",
|
||||||
|
"swagger-parser": "^10.0.3",
|
||||||
|
"yaml": "2.0.0-1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"swagger-jsdoc": "bin/swagger-jsdoc.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-jsdoc/node_modules/commander": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-jsdoc/node_modules/glob": {
|
||||||
|
"version": "7.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||||
|
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-parser": {
|
||||||
|
"version": "10.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz",
|
||||||
|
"integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@apidevtools/swagger-parser": "10.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-ui-dist": {
|
||||||
|
"version": "5.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz",
|
||||||
|
"integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@scarf/scarf": "=1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-ui-express": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"swagger-ui-dist": ">=5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= v0.10.32"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": ">=4.0.0 || >=5.0.0-beta"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tar": {
|
"node_modules/tar": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||||
@@ -9642,6 +9896,15 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.0.0-1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
|
||||||
|
"integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "17.7.2",
|
"version": "17.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
@@ -9705,6 +9968,36 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/z-schema": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"validator": "^13.7.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"z-schema": "bin/z-schema"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"commander": "^9.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/z-schema/node_modules/commander": {
|
||||||
|
"version": "9.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||||
|
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zip-stream": {
|
"node_modules/zip-stream": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "eventmaster",
|
"name": "eventmaster",
|
||||||
"version": "0.1.0",
|
"version": "0.4.0",
|
||||||
"author": "Tobias Zoghaib",
|
"author": "Tobias Zoghaib",
|
||||||
"description": "Hackable ticket store for educational purposes",
|
"description": "Hackable ticket store for educational purposes",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "www.uni-hannover.de",
|
"homepage": "https://www.itsec.uni-hannover.de/de/usec/forschung/eventmaster-learning-web-attacks",
|
||||||
"main": "build/src/electron/index.js",
|
"main": "build/src/electron/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -53,6 +53,9 @@
|
|||||||
"sequelize": "^6.37.4",
|
"sequelize": "^6.37.4",
|
||||||
"sequelize-typescript": "^2.1.6",
|
"sequelize-typescript": "^2.1.6",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
|
"swagger-autogen": "^2.23.7",
|
||||||
|
"swagger-jsdoc": "^6.2.8",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-i18n": "^10.0.4",
|
"vue-i18n": "^10.0.4",
|
||||||
"vue-router": "^4.4.5",
|
"vue-router": "^4.4.5",
|
||||||
@@ -68,6 +71,8 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.7",
|
"@types/jsonwebtoken": "^9.0.7",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
|
"@types/swagger-jsdoc": "^6.0.4",
|
||||||
|
"@types/swagger-ui-express": "^4.1.7",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
|
|||||||
@@ -3,17 +3,33 @@ defineProps({
|
|||||||
/** Displayed smaller text on the left side */
|
/** Displayed smaller text on the left side */
|
||||||
descriptionText: {
|
descriptionText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: "",
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Displayed bigger text on the right side */
|
/** Displayed bigger text on the right side */
|
||||||
valueText: [ String, Number ]
|
valueText: [String, Number],
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card variant="outlined" class="my-1 mx-2 px-2">
|
<v-card variant="outlined" class="my-1 px-2">
|
||||||
<v-row class="d-flex justify-center align-center">
|
<v-row v-if="loading">
|
||||||
|
<v-col>
|
||||||
|
<v-skeleton-loader
|
||||||
|
type="heading"
|
||||||
|
:loading="loading"
|
||||||
|
style="background-color: transparent"
|
||||||
|
>
|
||||||
|
sdasd
|
||||||
|
</v-skeleton-loader>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row class="d-flex justify-center align-center" v-else>
|
||||||
<v-col class="text-caption text-left" v-if="descriptionText.length > 0">
|
<v-col class="text-caption text-left" v-if="descriptionText.length > 0">
|
||||||
{{ descriptionText }}
|
{{ descriptionText }}
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|||||||
@@ -1,50 +1,122 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAccountStore } from '@/stores/account.store';
|
import { useAccountStore } from "@/stores/account.store";
|
||||||
import { useBasketStore } from '@/stores/basket.store';
|
import { useBasketStore } from "@/stores/basket.store";
|
||||||
import { useExerciseStore } from '@/stores/exercise.store';
|
import { useExerciseStore } from "@/stores/exercise.store";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore();
|
||||||
const basketStore = useBasketStore()
|
const basketStore = useBasketStore();
|
||||||
const exerciseStore = useExerciseStore()
|
const exerciseStore = useExerciseStore();
|
||||||
|
const basketItems = ref(0);
|
||||||
|
|
||||||
exerciseStore.getAllExercises()
|
exerciseStore.getAllExercises();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => basketStore.itemsInBasket,
|
||||||
|
() => {
|
||||||
|
basketItems.value = basketStore.itemsInBasket.reduce((tot, item) => {
|
||||||
|
return tot + item.seats.length;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-btn variant="plain" icon="mdi-magnify" to="/search" />
|
<!-- Global search -->
|
||||||
|
<v-tooltip :text="$t('misc.search.globalsearch')" location="bottom">
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<v-btn v-bind="props" variant="plain" icon="mdi-magnify" to="/search" />
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
|
<!-- Account -->
|
||||||
|
<v-tooltip :text="$t('account.account')" location="bottom">
|
||||||
|
<template #activator="{ props }">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="accountStore.userAccountToken == ''"
|
v-if="accountStore.userAccountToken == ''"
|
||||||
|
v-bind="props"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
icon="mdi-account"
|
icon="mdi-account"
|
||||||
to="/account/login"
|
to="/account/login"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-btn v-else variant="plain" icon="mdi-account-check" to="/account/home" />
|
<v-btn
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
variant="plain"
|
||||||
|
icon="mdi-account-check"
|
||||||
|
to="/account/home"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
<div>
|
<!-- Basket -->
|
||||||
|
<v-tooltip :text="$t('basket.basket')" location="bottom">
|
||||||
|
<template #activator="{ props }">
|
||||||
<v-badge
|
<v-badge
|
||||||
:content="basketStore.itemsInBasket.reduce((tot, item) => {
|
v-if="basketItems > 0"
|
||||||
return tot + item.seats.length
|
:content="basketItems"
|
||||||
}, 0)"
|
color="error"
|
||||||
color="error" offset-x="8" offset-y="8">
|
offset-x="8"
|
||||||
<v-btn variant="plain" icon="mdi-cart" to="/basket" />
|
offset-y="8"
|
||||||
|
>
|
||||||
|
<v-btn v-bind="props" variant="plain" icon="mdi-cart" to="/basket" />
|
||||||
</v-badge>
|
</v-badge>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
variant="plain"
|
||||||
|
icon="mdi-cart"
|
||||||
|
to="/basket"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
|
<!-- Exercise page -->
|
||||||
|
<v-tooltip :text="$t('misc.firstStartup.exercises')" location="bottom">
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<v-badge
|
||||||
|
v-if="exerciseStore.exercisePageVisible"
|
||||||
|
:content="
|
||||||
|
exerciseStore.exercises.reduce((tot, exercise) => {
|
||||||
|
if (exercise.available && !exercise.solved) {
|
||||||
|
return tot + 1;
|
||||||
|
} else {
|
||||||
|
return tot;
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
"
|
||||||
|
color="error"
|
||||||
|
offset-x="8"
|
||||||
|
offset-y="8"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
variant="plain"
|
||||||
|
icon="mdi-book-open-blank-variant"
|
||||||
|
to="/help"
|
||||||
|
/>
|
||||||
|
</v-badge>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
|
<!-- Admin panel -->
|
||||||
|
<v-tooltip :text="$t('admin.adminpanel')" location="bottom">
|
||||||
|
<template #activator="{ props }">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="accountStore.adminPanelVisible"
|
v-if="accountStore.adminPanelVisible"
|
||||||
|
v-bind="props"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
icon="mdi-table-cog"
|
icon="mdi-table-cog"
|
||||||
to="/admin"
|
to="/admin"
|
||||||
/>
|
/>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
<v-btn
|
<v-tooltip :text="$t('preferences.preferences')" location="bottom">
|
||||||
v-if="exerciseStore.helpPageVisible"
|
<template #activator="{ props }">
|
||||||
variant="plain"
|
<v-btn v-bind="props" variant="plain" icon="mdi-cog" to="/preferences" />
|
||||||
icon="mdi-help"
|
</template>
|
||||||
to="/help"
|
</v-tooltip>
|
||||||
/>
|
|
||||||
|
|
||||||
<v-btn variant="plain" icon="mdi-cog" to="/preferences"/>
|
|
||||||
</template>
|
</template>
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
|
import cardViewHorizontal from "@/components/basics/cardViewHorizontal.vue";
|
||||||
import { BandModel } from '@/data/models/acts/bandModel';
|
import { BandModel } from "@/data/models/acts/bandModel";
|
||||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
import { ConcertModel } from "@/data/models/acts/concertModel";
|
||||||
import { LocationModel } from '@/data/models/locations/locationModel';
|
import { LocationModel } from "@/data/models/locations/locationModel";
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
/** Concert to display */
|
/** Concert to display */
|
||||||
concert: {
|
concert: {
|
||||||
type: ConcertModel,
|
type: ConcertModel,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
band: {
|
band: {
|
||||||
type: BandModel,
|
type: BandModel,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
location: {
|
location: {
|
||||||
type: LocationModel,
|
type: LocationModel,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Display text parts as skeleton */
|
/** Display text parts as skeleton */
|
||||||
@@ -30,9 +30,9 @@ defineProps({
|
|||||||
/** Show or hide the button on the right side */
|
/** Show or hide the button on the right side */
|
||||||
showButton: {
|
showButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -40,7 +40,13 @@ defineProps({
|
|||||||
:title="concert.name"
|
:title="concert.name"
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
:link="showButton && concert.inStock > 0"
|
:link="showButton && concert.inStock > 0"
|
||||||
@click="showButton && concert.inStock > 0 ? router.push('/concerts/booking/' + location.urlName + '/' + concert.date) : () => {}"
|
@click="console.log(concert.date);
|
||||||
|
showButton && concert.inStock > 0
|
||||||
|
? router.push(
|
||||||
|
'/concerts/booking/' + location.urlName + '/' + concert.date
|
||||||
|
)
|
||||||
|
: () => {}
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<div>
|
<div>
|
||||||
@@ -49,7 +55,9 @@ defineProps({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-h6">
|
<div class="text-h6">
|
||||||
{{ new Date(concert.date).toLocaleString('default', { month: 'long' }) }}
|
{{
|
||||||
|
new Date(concert.date).toLocaleString("default", { month: "long" })
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-h6">
|
<div class="text-h6">
|
||||||
@@ -71,28 +79,23 @@ defineProps({
|
|||||||
<template #append>
|
<template #append>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-secondary font-weight-medium text-h6 pb-1">
|
<div class="text-secondary font-weight-medium text-h6 pb-1">
|
||||||
{{ $t('misc.from') + ' ' + concert.price.toFixed(2) + ' €' }}
|
{{ $t("misc.from") + " " + concert.price.toFixed(2) + " €" }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="concert.inStock == 0 && showButton" class="text-h6">
|
<div v-if="concert.inStock == 0 && showButton" class="text-h6">
|
||||||
{{ $t('concert.concertSoldOut') }}
|
{{ $t("concert.concertSoldOut") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="showButton">
|
<div v-else-if="showButton">
|
||||||
<v-btn variant="flat" color="secondary">
|
<v-btn variant="flat" color="secondary">
|
||||||
{{ $t('concert.goToTheConcert') }}
|
{{ $t("concert.goToTheConcert") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</card-view-horizontal>
|
</card-view-horizontal>
|
||||||
|
|
||||||
<card-view-horizontal
|
<card-view-horizontal v-else :loading="loading">
|
||||||
v-else
|
<v-skeleton-loader type="text" />
|
||||||
:loading="loading"
|
|
||||||
>
|
|
||||||
<v-skeleton-loader
|
|
||||||
type="text" />
|
|
||||||
</card-view-horizontal>
|
</card-view-horizontal>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,7 +37,7 @@ defineProps({
|
|||||||
{{ secondLine }}
|
{{ secondLine }}
|
||||||
</v-skeleton-loader>
|
</v-skeleton-loader>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions v-if="!$slots.actions">
|
||||||
<outlined-button
|
<outlined-button
|
||||||
@click="router.push(buttonRoute)"
|
@click="router.push(buttonRoute)"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -45,6 +45,10 @@ defineProps({
|
|||||||
{{ $t('misc.actions.more') }}
|
{{ $t('misc.actions.more') }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #actions v-else>
|
||||||
|
<slot name="actions"></slot>
|
||||||
|
</template>
|
||||||
</card-view>
|
</card-view>
|
||||||
</v-col>
|
</v-col>
|
||||||
</template>
|
</template>
|
||||||
@@ -27,7 +27,7 @@ export async function fetchAllAccounts(token: string) {
|
|||||||
* @returns Response from server with token body
|
* @returns Response from server with token body
|
||||||
*/
|
*/
|
||||||
export async function getLogin(username: string, password: string) {
|
export async function getLogin(username: string, password: string) {
|
||||||
return await axios.get(BASE_URL + "/account/login?username=" + username + "&password=" + password)
|
return await axios.get(BASE_URL + "/login?username=" + username + "&password=" + password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export async function getLogin(username: string, password: string) {
|
|||||||
* @returns Response from server with account body
|
* @returns Response from server with account body
|
||||||
*/
|
*/
|
||||||
export async function getAccount(token: string) {
|
export async function getAccount(token: string) {
|
||||||
return await axios.get(BASE_URL + "/account/data", {
|
return await axios.get(BASE_URL + "/account", {
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": token
|
"Authorization": token
|
||||||
}
|
}
|
||||||
@@ -77,9 +77,17 @@ export async function updateAccount(account: AccountModel, token: string) {
|
|||||||
* Delete an account in servers database
|
* Delete an account in servers database
|
||||||
*
|
*
|
||||||
* @param account Account to delete
|
* @param account Account to delete
|
||||||
|
* @param token Validation token
|
||||||
*
|
*
|
||||||
* @returns Response from server
|
* @returns Response from server
|
||||||
*/
|
*/
|
||||||
export async function deleteAccount(account: AccountModel) {
|
export async function deleteAccount(account: AccountModel, token: string) {
|
||||||
return await axios.delete(BASE_URL + "/account/" + account.id)
|
return await axios.delete(BASE_URL + "/account", {
|
||||||
|
headers: {
|
||||||
|
"Authorization": token
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
account: account
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -4,15 +4,19 @@ import { OrderApiModel } from "../models/apiEndpoints/orderApiModel"
|
|||||||
|
|
||||||
const BASE_URL = "http://localhost:3000/orders"
|
const BASE_URL = "http://localhost:3000/orders"
|
||||||
|
|
||||||
export async function fetchUserOrders(userId: number) {
|
export async function fetchUserOrders(userId: number, token: string) {
|
||||||
return axios.get(BASE_URL + "/" + userId)
|
return axios.get(BASE_URL + "?id=" + userId, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": token
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createOrder(
|
export async function createOrder(
|
||||||
accountId: number,
|
|
||||||
basketItem: Array<BasketItemModel>,
|
basketItem: Array<BasketItemModel>,
|
||||||
paymentId: number,
|
paymentId: number,
|
||||||
addressId: number
|
addressId: number,
|
||||||
|
token: string
|
||||||
) {
|
) {
|
||||||
let tickets = []
|
let tickets = []
|
||||||
|
|
||||||
@@ -27,10 +31,13 @@ export async function createOrder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return axios.post(BASE_URL, {
|
return axios.post(BASE_URL, {
|
||||||
accountId: accountId,
|
|
||||||
tickets: tickets,
|
tickets: tickets,
|
||||||
paymentId: paymentId,
|
paymentId: paymentId,
|
||||||
addressId: addressId
|
addressId: addressId,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": token
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export class ExerciseGroupModel {
|
|||||||
id = -1
|
id = -1
|
||||||
nameDe: string = ""
|
nameDe: string = ""
|
||||||
nameEn: string = ""
|
nameEn: string = ""
|
||||||
|
icon: string = ""
|
||||||
groupNr: number = 0
|
groupNr: number = 0
|
||||||
descriptionDe: string = ""
|
descriptionDe: string = ""
|
||||||
descriptionEn: string = ""
|
descriptionEn: string = ""
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ExerciseGroupModel } from "./exerciseGroupModel"
|
import { ExerciseGroupModel } from "./exerciseGroupModel"
|
||||||
|
|
||||||
export class ExerciseModel {
|
export class ExerciseModel {
|
||||||
id = -1
|
id: number = -1
|
||||||
|
uuid: string = ""
|
||||||
nameDe: string = ""
|
nameDe: string = ""
|
||||||
nameEn: string = ""
|
nameEn: string = ""
|
||||||
exerciseNr: number = 0
|
exerciseNr: number = 0
|
||||||
@@ -9,4 +10,5 @@ export class ExerciseModel {
|
|||||||
descriptionEn: string = ""
|
descriptionEn: string = ""
|
||||||
solved: boolean = false
|
solved: boolean = false
|
||||||
exerciseGroup: ExerciseGroupModel
|
exerciseGroup: ExerciseGroupModel
|
||||||
|
available: boolean = true
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"emailIsNotValid": "Ungültige E-Mail Addresse",
|
"emailIsNotValid": "Ungültige E-Mail Addresse",
|
||||||
"emailRequired": "E-Mail-Adresse benötigt",
|
"emailRequired": "E-Mail-Adresse benötigt",
|
||||||
"accountManagement": "Account verwalten",
|
"accountManagement": "Account verwalten",
|
||||||
"accountManagementDescription": "Persönliche Daten, Adressen, Bezahlmethoden",
|
"accountManagementDescription": "Persönliche Daten, Konto löschen",
|
||||||
"login": {
|
"login": {
|
||||||
"pleaseLoginToOrder": "Bitte anmelden zum bestellen",
|
"pleaseLoginToOrder": "Bitte anmelden zum bestellen",
|
||||||
"backToLogin": "Zurück zum Login",
|
"backToLogin": "Zurück zum Login",
|
||||||
@@ -97,7 +97,8 @@
|
|||||||
"postalCode": "Postleitzahl",
|
"postalCode": "Postleitzahl",
|
||||||
"placeOfResidence": "Wohnort",
|
"placeOfResidence": "Wohnort",
|
||||||
"bankName": "Name der Bank",
|
"bankName": "Name der Bank",
|
||||||
"iban": "IBAN"
|
"iban": "IBAN",
|
||||||
|
"actions": "Aktionen"
|
||||||
},
|
},
|
||||||
"deleteAccount": {
|
"deleteAccount": {
|
||||||
"deleteAccount": "Account löschen",
|
"deleteAccount": "Account löschen",
|
||||||
@@ -109,7 +110,16 @@
|
|||||||
"addNewAccount": "Neuen Account hinzufügen",
|
"addNewAccount": "Neuen Account hinzufügen",
|
||||||
"accountRole": "Account Rolle",
|
"accountRole": "Account Rolle",
|
||||||
"noRealPaymentsNeeded": "Keine echten Kontodaten nötig!",
|
"noRealPaymentsNeeded": "Keine echten Kontodaten nötig!",
|
||||||
"administrator": "Administrator | Administratoren"
|
"administrator": "Administrator | Administratoren",
|
||||||
|
"managePaymentsDescription": "Bezahlarten hinzufügen, ändern, löschen",
|
||||||
|
"paymentsManagement": "Bezahlarten verwalten",
|
||||||
|
"payments": {
|
||||||
|
"editPayment": "Bezahlart bearbeiten",
|
||||||
|
"editAddress": "Adresse bearbeiten"
|
||||||
|
},
|
||||||
|
"addressManagementDetails": "Adressen hinzufügen, ändern, löschen",
|
||||||
|
"addressManagement": "Adressen verwalten",
|
||||||
|
"sessionTime": "Session time"
|
||||||
},
|
},
|
||||||
"order": {
|
"order": {
|
||||||
"oclock": "Uhr",
|
"oclock": "Uhr",
|
||||||
@@ -121,7 +131,8 @@
|
|||||||
"noOrdersText": "Bisher wurden keine Bestellungen von diesem Account getätigt. Gehe zum Warenkorb und bestelle!",
|
"noOrdersText": "Bisher wurden keine Bestellungen von diesem Account getätigt. Gehe zum Warenkorb und bestelle!",
|
||||||
"ordersDescription": "Übersicht aller getätigten Bestellungen",
|
"ordersDescription": "Übersicht aller getätigten Bestellungen",
|
||||||
"order": "Bestellung | Bestellungen",
|
"order": "Bestellung | Bestellungen",
|
||||||
"notShipped": "noch nicht versendet"
|
"notShipped": "noch nicht versendet",
|
||||||
|
"orderState": "Bestellstatus"
|
||||||
},
|
},
|
||||||
"basket": {
|
"basket": {
|
||||||
"addToBasket": "Zum Warenkorb hinzufügen",
|
"addToBasket": "Zum Warenkorb hinzufügen",
|
||||||
@@ -157,12 +168,26 @@
|
|||||||
"title": "Auf Werkseinstellungen zurücksetzen?",
|
"title": "Auf Werkseinstellungen zurücksetzen?",
|
||||||
"description": "Sollen alle Einstellungen und Daten auf Werkseinstellungen zurückgesetzt werden? Alle Änderungen und Fortschritte gehen verloren!"
|
"description": "Sollen alle Einstellungen und Daten auf Werkseinstellungen zurückgesetzt werden? Alle Änderungen und Fortschritte gehen verloren!"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"exercises": {
|
||||||
|
"available": "{0} von {1} Aufgaben verfügbar",
|
||||||
|
"edit": "Verfügbare Aufgaben bearbeiten",
|
||||||
|
"settings": "Aufgaben-Konfiguration"
|
||||||
|
},
|
||||||
|
"importExport": {
|
||||||
|
"title": "Import/Export Konfiguration",
|
||||||
|
"selectConfigFile": "Konfigurations-Datei auswählen",
|
||||||
|
"download": "Konfiguration exportieren",
|
||||||
|
"upload": "Datei hochladen"
|
||||||
|
},
|
||||||
|
"preferences": "Einstellungen"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"scoreBoard": {
|
"scoreBoard": {
|
||||||
"exerciseGroupNr": "Aufgabengruppe {0}: ",
|
"exerciseGroupNr": "Aufgabengruppe {0}: ",
|
||||||
"exerciseNr": "Aufgabe {0}.{1}: "
|
"exerciseNr": "Aufgabe {0}.{1}: ",
|
||||||
|
"generatePdf": "PDF generieren",
|
||||||
|
"personalSolutionKey": "Persönlicher Lösungsschlüssel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bannerMessages": {
|
"bannerMessages": {
|
||||||
@@ -216,6 +241,7 @@
|
|||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"more": "Mehr",
|
"more": "Mehr",
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
|
"back": "Zurück",
|
||||||
"next": "Weiter"
|
"next": "Weiter"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
@@ -232,7 +258,7 @@
|
|||||||
"firstStartup": {
|
"firstStartup": {
|
||||||
"title": "Ersteinrichtung",
|
"title": "Ersteinrichtung",
|
||||||
"description": "Die Datenbank wird eingerichtet. Bitte warten...",
|
"description": "Die Datenbank wird eingerichtet. Bitte warten...",
|
||||||
"createDatabase": "Erstelle Datenbank...",
|
"createDatabase": "Datenbank Einrichtung",
|
||||||
"complete": "Fertig",
|
"complete": "Fertig",
|
||||||
"finished": "Abgeschlossen",
|
"finished": "Abgeschlossen",
|
||||||
"createExercises": "Erstelle Aufgaben...",
|
"createExercises": "Erstelle Aufgaben...",
|
||||||
@@ -240,6 +266,7 @@
|
|||||||
"database": "Datenbank",
|
"database": "Datenbank",
|
||||||
"exercises": "Aufgaben",
|
"exercises": "Aufgaben",
|
||||||
"userData": "Persönliche Daten",
|
"userData": "Persönliche Daten",
|
||||||
|
"lookAndFeel": "Look and feel",
|
||||||
"enterYourPersonalData": "Bitte gebe nun deinen Namen und deine Matrikelnummer von der Universität ein. Überprüfe die Angaben vor dem Absenden genau! Die Angaben können später nicht ohne Verlust des Bearbeitungsfortschrittes geändert werden!"
|
"enterYourPersonalData": "Bitte gebe nun deinen Namen und deine Matrikelnummer von der Universität ein. Überprüfe die Angaben vor dem Absenden genau! Die Angaben können später nicht ohne Verlust des Bearbeitungsfortschrittes geändert werden!"
|
||||||
},
|
},
|
||||||
"user": "Angaben zur Person",
|
"user": "Angaben zur Person",
|
||||||
@@ -254,10 +281,26 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"headline": "So leer hier..."
|
"headline": "So leer hier..."
|
||||||
},
|
},
|
||||||
"searchterm": "Suchbegriff"
|
"searchterm": "Suchbegriff",
|
||||||
}
|
"globalsearch": "Globale Suche"
|
||||||
|
},
|
||||||
|
"submit": "Absenden",
|
||||||
|
"content": "Inhalt",
|
||||||
|
"source": "Quelle",
|
||||||
|
"softwareVersion": "Software Version",
|
||||||
|
"license": "Lizenz",
|
||||||
|
"developer": "Entwickler",
|
||||||
|
"developedFor": "Entwickelt im Auftrag",
|
||||||
|
"copyright": "Copyright",
|
||||||
|
"githubRepository": "GitHub Repository",
|
||||||
|
"projectPage": "Projektseite"
|
||||||
},
|
},
|
||||||
"genre": {
|
"genre": {
|
||||||
"withoutBand": "ohne Band"
|
"withoutBand": "ohne Band",
|
||||||
|
"popular": "Beliebte Genres",
|
||||||
|
"allGenres": "Alle Genres"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"adminpanel": "Admin Panel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
"emailIsNotValid": "E-Mail not valid",
|
"emailIsNotValid": "E-Mail not valid",
|
||||||
"emailRequired": "E-Mail required",
|
"emailRequired": "E-Mail required",
|
||||||
"accountManagement": "Manage Account",
|
"accountManagement": "Manage Account",
|
||||||
"accountManagementDescription": "Personal data, addresses, payments",
|
"accountManagementDescription": "Personal data, delete account",
|
||||||
"login": {
|
"login": {
|
||||||
"pleaseLoginToOrder": "Please login to order",
|
"pleaseLoginToOrder": "Please login to order",
|
||||||
"backToLogin": "Back to Login",
|
"backToLogin": "Back to Login",
|
||||||
@@ -97,7 +97,8 @@
|
|||||||
"postalCode": "Postal code",
|
"postalCode": "Postal code",
|
||||||
"placeOfResidence": "Place of residence",
|
"placeOfResidence": "Place of residence",
|
||||||
"bankName": "Name of bank",
|
"bankName": "Name of bank",
|
||||||
"iban": "IBAN"
|
"iban": "IBAN",
|
||||||
|
"actions": "Actions"
|
||||||
},
|
},
|
||||||
"deleteAccount": {
|
"deleteAccount": {
|
||||||
"deleteAccount": "Delete Account",
|
"deleteAccount": "Delete Account",
|
||||||
@@ -109,7 +110,16 @@
|
|||||||
"addNewAccount": "Add new account",
|
"addNewAccount": "Add new account",
|
||||||
"accountRole": "Account Role",
|
"accountRole": "Account Role",
|
||||||
"noRealPaymentsNeeded": "No real payment data required!",
|
"noRealPaymentsNeeded": "No real payment data required!",
|
||||||
"administrator": "Administrator"
|
"administrator": "Administrator",
|
||||||
|
"managePaymentsDescription": "Add, change, remove payments",
|
||||||
|
"paymentsManagement": "Manage payments",
|
||||||
|
"payments": {
|
||||||
|
"editPayment": "Edit Payment",
|
||||||
|
"editAddress": "Edit address"
|
||||||
|
},
|
||||||
|
"addressManagementDetails": "Add, change, remove addresses",
|
||||||
|
"addressManagement": "Manage addresses",
|
||||||
|
"sessionTime": "Session time"
|
||||||
},
|
},
|
||||||
"order": {
|
"order": {
|
||||||
"oclock": "o'clock",
|
"oclock": "o'clock",
|
||||||
@@ -118,10 +128,11 @@
|
|||||||
"takeOrder": "Execute order",
|
"takeOrder": "Execute order",
|
||||||
"noOrders": "No orders found",
|
"noOrders": "No orders found",
|
||||||
"orderedAt": "Ordered at",
|
"orderedAt": "Ordered at",
|
||||||
"noOrdersText": "Bisher wurden keine Bestellungen von diesem Account getätigt. Gehe zum Warenkorb und bestelle!",
|
"noOrdersText": "No orders have been placed with this account yet. Go to your shopping cart and place your order!",
|
||||||
"ordersDescription": "Overview of all placed orders",
|
"ordersDescription": "Overview of all placed orders",
|
||||||
"order": "Order | Orders",
|
"order": "Order | Orders",
|
||||||
"notShipped": "don't shipped"
|
"notShipped": "don't shipped",
|
||||||
|
"orderState": "Order state"
|
||||||
},
|
},
|
||||||
"basket": {
|
"basket": {
|
||||||
"addToBasket": "Add to basket",
|
"addToBasket": "Add to basket",
|
||||||
@@ -157,12 +168,28 @@
|
|||||||
"title": "Factory reset?",
|
"title": "Factory reset?",
|
||||||
"description": "Do you really want to reset everything? Every change will be lost!"
|
"description": "Do you really want to reset everything? Every change will be lost!"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"exercises": {
|
||||||
|
"settings": "Exercise Configuration",
|
||||||
|
"available": "{0} of {1} exercises are available",
|
||||||
|
"uploadExerciseConfig": "Upload exercise config",
|
||||||
|
"edit": "Edit available exercises",
|
||||||
|
"upload": "Upload exercises config"
|
||||||
|
},
|
||||||
|
"importExport": {
|
||||||
|
"title": "Import/Export config",
|
||||||
|
"selectConfigFile": "Select config file",
|
||||||
|
"upload": "Upload file",
|
||||||
|
"download": "Export config"
|
||||||
|
},
|
||||||
|
"preferences": "Preferences"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"scoreBoard": {
|
"scoreBoard": {
|
||||||
"exerciseGroupNr": "Exercise Group {0}: ",
|
"exerciseGroupNr": "Exercise Group {0}: ",
|
||||||
"exerciseNr": "Exercise {0}.{1}: "
|
"exerciseNr": "Exercise {0}.{1}: ",
|
||||||
|
"generatePdf": "Generate PDF",
|
||||||
|
"personalSolutionKey": "Personal solution key"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bannerMessages": {
|
"bannerMessages": {
|
||||||
@@ -216,6 +243,7 @@
|
|||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
"back": "Back",
|
||||||
"next": "Next"
|
"next": "Next"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
@@ -232,14 +260,13 @@
|
|||||||
"firstStartup": {
|
"firstStartup": {
|
||||||
"title": "First startup",
|
"title": "First startup",
|
||||||
"description": "Creating database. Please wait...",
|
"description": "Creating database. Please wait...",
|
||||||
"createDatabase": "Create Database...",
|
"createDatabase": "Create Database",
|
||||||
"complete": "Complete",
|
"complete": "Complete",
|
||||||
"createExercises": "Create Exercises...",
|
|
||||||
"finished": "Finished",
|
|
||||||
"connectToServer": "Server",
|
"connectToServer": "Server",
|
||||||
"database": "Database",
|
"database": "Database",
|
||||||
"exercises": "Exercises",
|
"exercises": "Exercises",
|
||||||
"userData": "User data",
|
"userData": "User data",
|
||||||
|
"lookAndFeel": "Look and feel",
|
||||||
"enterYourPersonalData": "Please enter your name and your Matrikel number from your university. Check it twice! You can't change it later without loosing your exercise progress!"
|
"enterYourPersonalData": "Please enter your name and your Matrikel number from your university. Check it twice! You can't change it later without loosing your exercise progress!"
|
||||||
},
|
},
|
||||||
"user": "About person",
|
"user": "About person",
|
||||||
@@ -254,10 +281,26 @@
|
|||||||
"empty": {
|
"empty": {
|
||||||
"headline": "So empty here..."
|
"headline": "So empty here..."
|
||||||
},
|
},
|
||||||
"searchterm": "Search term"
|
"searchterm": "Search term",
|
||||||
}
|
"globalsearch": "Global Search"
|
||||||
|
},
|
||||||
|
"submit": "Submit",
|
||||||
|
"content": "Content",
|
||||||
|
"source": "Source",
|
||||||
|
"softwareVersion": "Software Version",
|
||||||
|
"license": "License",
|
||||||
|
"developer": "Developer",
|
||||||
|
"developedFor": "Developed for",
|
||||||
|
"copyright": "Copyright",
|
||||||
|
"githubRepository": "GitHub Repository",
|
||||||
|
"projectPage": "Project page"
|
||||||
},
|
},
|
||||||
"genre": {
|
"genre": {
|
||||||
"withoutBand": "without Band"
|
"withoutBand": "without Band",
|
||||||
|
"popular": "Popular Genres",
|
||||||
|
"allGenres": "All Genres"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"adminpanel": "Admin Panel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
src/pages/account/accountAddressesPage/addressEditDialog.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import actionDialog from '@/components/basics/actionDialog.vue';
|
||||||
|
import OutlinedButton from '@/components/basics/outlinedButton.vue';
|
||||||
|
import { getIbanRules, getNumberStartRules, getPostalRules, getStringRules } from '@/scripts/validationRules';
|
||||||
|
import { useAccountStore } from '@/stores/account.store';
|
||||||
|
import cardViewOneLine from '@/components/basics/cardViewOneLine.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const valid = ref(false)
|
||||||
|
|
||||||
|
const accountStore = useAccountStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<action-dialog
|
||||||
|
v-model="accountStore.showEditDialog"
|
||||||
|
max-width="800"
|
||||||
|
:title="$t('account.payments.editAddress')"
|
||||||
|
>
|
||||||
|
<v-container>
|
||||||
|
<v-form v-model="valid">
|
||||||
|
<v-row class="pt-5">
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('account.userData.street')"
|
||||||
|
v-model="accountStore.address.street"
|
||||||
|
:rules="getStringRules()"
|
||||||
|
variant="outlined"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('account.userData.houseNumber')"
|
||||||
|
v-model="accountStore.address.houseNumber"
|
||||||
|
:rules="getNumberStartRules()"
|
||||||
|
variant="outlined"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('account.userData.postalCode')"
|
||||||
|
v-model="accountStore.address.postalCode"
|
||||||
|
:rules="getPostalRules()"
|
||||||
|
variant="outlined"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('account.userData.placeOfResidence')"
|
||||||
|
v-model="accountStore.address.city"
|
||||||
|
:rules="getStringRules()"
|
||||||
|
variant="outlined"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<outlined-button
|
||||||
|
color="success"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
:disabled="!valid"
|
||||||
|
:loading="accountStore.fetchInProgress"
|
||||||
|
@click="accountStore.saveAddress"
|
||||||
|
>
|
||||||
|
{{ $t('misc.actions.save') }}
|
||||||
|
</outlined-button>
|
||||||
|
</template>
|
||||||
|
</action-dialog>
|
||||||
|
</template>
|
||||||
51
src/pages/account/accountAddressesPage/index.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
|
import { useAccountStore } from '@/stores/account.store';
|
||||||
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
|
import addressEditDialog from './addressEditDialog.vue';
|
||||||
|
|
||||||
|
const accountStore = useAccountStore()
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.street'), value: "street" },
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.houseNumber'), value: "houseNumber" },
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.postalCode'), value: "postalCode" },
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.placeOfResidence'), value: "city" },
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.actions'), value: "actions", width: 130 }
|
||||||
|
]
|
||||||
|
|
||||||
|
accountStore.refreshAccount()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<data-layout
|
||||||
|
:add-button-string="$t('misc.actions.add')"
|
||||||
|
:fetch-in-progress="accountStore.fetchInProgress"
|
||||||
|
:on-add-click="() => { accountStore.newAddress() }"
|
||||||
|
>
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="accountStore.userAccount.addresses"
|
||||||
|
:loading="accountStore.fetchInProgress"
|
||||||
|
>
|
||||||
|
<template #item.actions="{ item }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-pencil"
|
||||||
|
variant="plain"
|
||||||
|
color="orange"
|
||||||
|
@click="accountStore.editAddress(item)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
variant="plain"
|
||||||
|
color="red"
|
||||||
|
@click="accountStore.removeAddress(item)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</data-layout>
|
||||||
|
|
||||||
|
<address-edit-dialog />
|
||||||
|
</template>
|
||||||
@@ -81,7 +81,6 @@ const stringRules = [
|
|||||||
v-model="accountStore.userAccount.firstName"
|
v-model="accountStore.userAccount.firstName"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
:rules="stringRules"
|
:rules="stringRules"
|
||||||
hide-details
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
@@ -90,7 +89,6 @@ const stringRules = [
|
|||||||
v-model="accountStore.userAccount.lastName"
|
v-model="accountStore.userAccount.lastName"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
:rules="stringRules"
|
:rules="stringRules"
|
||||||
hide-details
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import cardView from '@/components/basics/cardView.vue';
|
|
||||||
import { useAccountStore } from '@/stores/account.store';
|
|
||||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
|
||||||
import { AddressModel } from '@/data/models/user/addressModel';
|
|
||||||
import { getNumberStartRules, getPostalRules, getStringRules } from '@/scripts/validationRules';
|
|
||||||
|
|
||||||
const accountStore = useAccountStore()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<card-view
|
|
||||||
icon="mdi-home"
|
|
||||||
:title="$t('account.userData.address', 2)"
|
|
||||||
>
|
|
||||||
<v-expansion-panels v-if="accountStore.userAccount.addresses.length > 0">
|
|
||||||
<v-expansion-panel
|
|
||||||
v-for="address in accountStore.userAccount.addresses"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<template #title>
|
|
||||||
<div v-if="address.street != undefined">
|
|
||||||
{{ address.street }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div v-if="address.houseNumber != undefined">
|
|
||||||
{{ address.houseNumber }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #text>
|
|
||||||
<v-row class="pt-5">
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('account.userData.street')"
|
|
||||||
v-model="address.street"
|
|
||||||
:rules="getStringRules()"
|
|
||||||
variant="outlined"
|
|
||||||
clearable
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('account.userData.houseNumber')"
|
|
||||||
v-model="address.houseNumber"
|
|
||||||
:rules="getNumberStartRules()"
|
|
||||||
variant="outlined"
|
|
||||||
clearable
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('account.userData.postalCode')"
|
|
||||||
v-model="address.postalCode"
|
|
||||||
:rules="getPostalRules()"
|
|
||||||
variant="outlined"
|
|
||||||
clearable
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('account.userData.placeOfResidence')"
|
|
||||||
v-model="address.city"
|
|
||||||
:rules="getStringRules()"
|
|
||||||
variant="outlined"
|
|
||||||
clearable
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col class="d-flex justify-center align-center">
|
|
||||||
<outlined-button
|
|
||||||
@click="accountStore.removeAddress(address)"
|
|
||||||
color="error"
|
|
||||||
prepend-icon="mdi-delete"
|
|
||||||
>
|
|
||||||
{{ $t('misc.actions.remove') }}
|
|
||||||
</outlined-button>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
</v-expansion-panel>
|
|
||||||
</v-expansion-panels>
|
|
||||||
|
|
||||||
<v-empty-state
|
|
||||||
v-else
|
|
||||||
:title="$t('account.noAddresses')"
|
|
||||||
icon="mdi-home-off"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #actions>
|
|
||||||
<outlined-button
|
|
||||||
@click="accountStore.userAccount.addresses.push(new AddressModel())"
|
|
||||||
prepend-icon="mdi-plus"
|
|
||||||
color="success"
|
|
||||||
>
|
|
||||||
{{ $t('misc.actions.add') }}
|
|
||||||
</outlined-button>
|
|
||||||
</template>
|
|
||||||
</card-view>
|
|
||||||
</template>
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import accountDataCard from './accountDataCard.vue';
|
import accountDataCard from './accountDataCard.vue';
|
||||||
import accountManagingCard from './accountManagingCard.vue';
|
import accountManagingCard from './accountManagingCard.vue';
|
||||||
import addressesCard from './addressesCard.vue';
|
|
||||||
import paymentsCard from './paymentsCard.vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import accountSubPageLayout from '@/layouts/accountSubPageLayout.vue';
|
import accountSubPageLayout from '@/layouts/accountSubPageLayout.vue';
|
||||||
|
|
||||||
@@ -17,18 +15,6 @@ const router = useRouter()
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<addresses-card />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<payments-card />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<account-managing-card />
|
<account-managing-card />
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import cardView from '@/components/basics/cardView.vue';
|
|
||||||
import { useAccountStore } from '@/stores/account.store';
|
|
||||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
|
||||||
import { PaymentModel } from '@/data/models/user/paymentModel';
|
|
||||||
import { getIbanRules, getStringRules } from '@/scripts/validationRules';
|
|
||||||
import cardViewOneLine from '@/components/basics/cardViewOneLine.vue';
|
|
||||||
|
|
||||||
const accountStore = useAccountStore()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<card-view
|
|
||||||
icon="mdi-currency-usd"
|
|
||||||
:title="$t('account.userData.payment', 2)"
|
|
||||||
>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<card-view-one-line
|
|
||||||
color="warning"
|
|
||||||
prepend-icon="mdi-alert"
|
|
||||||
:title="$t('account.noRealPaymentsNeeded')"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row v-if="accountStore.userAccount.payments.length > 0">
|
|
||||||
<v-col>
|
|
||||||
<v-expansion-panels>
|
|
||||||
<v-expansion-panel
|
|
||||||
v-for="payment in accountStore.userAccount.payments"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<template #title>
|
|
||||||
{{ payment.bankName }}
|
|
||||||
</template>
|
|
||||||
<template #text>
|
|
||||||
<v-row class="pt-5">
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('account.userData.bankName')"
|
|
||||||
v-model="payment.bankName"
|
|
||||||
:rules="getStringRules()"
|
|
||||||
variant="outlined"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('account.userData.iban')"
|
|
||||||
v-model="payment.iban"
|
|
||||||
:rules="getIbanRules()"
|
|
||||||
variant="outlined"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col class="d-flex justify-center align-center">
|
|
||||||
<outlined-button
|
|
||||||
@click="accountStore.removePayment(payment)"
|
|
||||||
color="error"
|
|
||||||
prepend-icon="mdi-delete"
|
|
||||||
>
|
|
||||||
{{ $t('misc.actions.remove') }}
|
|
||||||
</outlined-button>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
</v-expansion-panel>
|
|
||||||
</v-expansion-panels>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row v-else>
|
|
||||||
<v-col>
|
|
||||||
<v-empty-state
|
|
||||||
:title="$t('account.noPayments')"
|
|
||||||
icon="mdi-currency-usd-off"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<template #actions>
|
|
||||||
<outlined-button
|
|
||||||
@click="accountStore.userAccount.payments.push(new PaymentModel())"
|
|
||||||
prepend-icon="mdi-plus"
|
|
||||||
color="success"
|
|
||||||
>
|
|
||||||
{{ $t('misc.actions.add') }}
|
|
||||||
</outlined-button>
|
|
||||||
</template>
|
|
||||||
</card-view>
|
|
||||||
</template>
|
|
||||||
@@ -1,60 +1,76 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAccountStore } from '@/stores/account.store';
|
import { useAccountStore } from '@/stores/account.store';
|
||||||
import cardView from '@/components/basics/cardView.vue';
|
import dashboardCard from '@/components/pageParts/dashboardCard.vue';
|
||||||
|
import { useOrderStore } from '@/stores/order.store';
|
||||||
|
import OutlinedButton from '@/components/basics/outlinedButton.vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { millisecondsToHumanReadableString } from '@/scripts/dateTimeScripts';
|
||||||
|
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
|
const orderStore = useOrderStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
orderStore.getOrdersOfAccount(accountStore.userAccount, accountStore.userAccountToken)
|
||||||
|
accountStore.refreshAccount()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-container max-width="1000">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<card-view
|
|
||||||
:title="$t('misc.greeting', { msg: accountStore.userAccount.username })"
|
|
||||||
icon="mdi-hand-wave"
|
|
||||||
>
|
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<dashboard-card
|
||||||
<card-view
|
|
||||||
:title="$t('order.order', 2)"
|
:title="$t('order.order', 2)"
|
||||||
icon="mdi-basket-check"
|
icon="mdi-basket-check"
|
||||||
@click="router.push('/account/orders')"
|
:first-line="orderStore.orders.length + ' ' + $t('order.order', 2)"
|
||||||
>
|
:second-line="$t('order.ordersDescription')"
|
||||||
{{ $t('order.ordersDescription') }}
|
button-route="/account/orders"
|
||||||
</card-view>
|
:loading="orderStore.fetchInProgress"
|
||||||
</v-col>
|
/>
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
<dashboard-card
|
||||||
<v-col>
|
|
||||||
<card-view
|
|
||||||
:title="$t('account.accountManagement')"
|
:title="$t('account.accountManagement')"
|
||||||
icon="mdi-account"
|
icon="mdi-account"
|
||||||
@click="router.push('/account/data')"
|
:first-line="accountStore.userAccount.username"
|
||||||
>
|
:second-line="$t('account.accountManagementDescription')"
|
||||||
{{ $t('account.accountManagementDescription') }}
|
:loading="accountStore.fetchInProgress"
|
||||||
</card-view>
|
button-route="/account/data"
|
||||||
</v-col>
|
/>
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
<dashboard-card
|
||||||
<v-col>
|
:title="$t('account.addressManagement')"
|
||||||
<card-view
|
icon="mdi-city"
|
||||||
|
:first-line="accountStore.userAccount.addresses?.length + ' ' +
|
||||||
|
$t('account.userData.address', accountStore.userAccount.addresses?.length)"
|
||||||
|
:second-line="$t('account.addressManagementDetails')"
|
||||||
|
:loading="accountStore.fetchInProgress"
|
||||||
|
button-route="/account/addresses"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<dashboard-card
|
||||||
|
:title="$t('account.paymentsManagement', 2)"
|
||||||
|
icon="mdi-currency-eur"
|
||||||
|
:first-line="accountStore.userAccount.payments?.length + ' ' +
|
||||||
|
$t('account.userData.payment', accountStore.userAccount.payments?.length)"
|
||||||
|
:second-line="$t('account.managePaymentsDescription')"
|
||||||
|
:loading="accountStore.fetchInProgress"
|
||||||
|
button-route="/account/payments"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<dashboard-card
|
||||||
:title="$t('account.logout.logout')"
|
:title="$t('account.logout.logout')"
|
||||||
|
:first-line="millisecondsToHumanReadableString(moment().diff(moment(accountStore.loggedInTimeStamp))) + ' h ' + $t('account.sessionTime')"
|
||||||
|
:second-line="$t('account.logout.logoutDescription')"
|
||||||
icon="mdi-logout"
|
icon="mdi-logout"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<outlined-button
|
||||||
|
color="error"
|
||||||
@click="accountStore.logout(); router.push('/account/login')"
|
@click="accountStore.logout(); router.push('/account/login')"
|
||||||
>
|
>
|
||||||
{{ $t('account.logout.logoutDescription') }}
|
{{ $t('account.logout.logout') }}
|
||||||
</card-view>
|
</outlined-button>
|
||||||
</v-col>
|
</template>
|
||||||
</v-row>
|
</dashboard-card>
|
||||||
</v-container>
|
|
||||||
</card-view>
|
|
||||||
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
49
src/pages/account/accountPaymentsPage/index.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
|
import { useAccountStore } from '@/stores/account.store';
|
||||||
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
|
import PaymentEditDialog from './paymentEditDialog.vue';
|
||||||
|
|
||||||
|
const accountStore = useAccountStore()
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.bankName'), value: "bankName" },
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.iban'), value: "iban" },
|
||||||
|
{ title: feedbackStore.i18n.t('account.userData.actions'), value: "actions", width: 130 }
|
||||||
|
]
|
||||||
|
|
||||||
|
accountStore.refreshAccount()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<data-layout
|
||||||
|
:add-button-string="$t('misc.actions.add')"
|
||||||
|
:fetch-in-progress="accountStore.fetchInProgress"
|
||||||
|
:on-add-click="() => { accountStore.newPayment() }"
|
||||||
|
>
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="accountStore.userAccount.payments"
|
||||||
|
:loading="accountStore.fetchInProgress"
|
||||||
|
>
|
||||||
|
<template #item.actions="{ item }">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-pencil"
|
||||||
|
variant="plain"
|
||||||
|
color="orange"
|
||||||
|
@click="accountStore.editPayment(item)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
variant="plain"
|
||||||
|
color="red"
|
||||||
|
@click="accountStore.removePayment(item)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</data-layout>
|
||||||
|
|
||||||
|
<payment-edit-dialog />
|
||||||
|
</template>
|
||||||
69
src/pages/account/accountPaymentsPage/paymentEditDialog.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import actionDialog from '@/components/basics/actionDialog.vue';
|
||||||
|
import OutlinedButton from '@/components/basics/outlinedButton.vue';
|
||||||
|
import { getIbanRules, getStringRules } from '@/scripts/validationRules';
|
||||||
|
import { useAccountStore } from '@/stores/account.store';
|
||||||
|
import cardViewOneLine from '@/components/basics/cardViewOneLine.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const valid = ref(false)
|
||||||
|
|
||||||
|
const accountStore = useAccountStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<action-dialog
|
||||||
|
v-model="accountStore.showEditDialog"
|
||||||
|
max-width="800"
|
||||||
|
:title="$t('account.payments.editPayment')"
|
||||||
|
>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<card-view-one-line
|
||||||
|
color="warning"
|
||||||
|
prepend-icon="mdi-alert"
|
||||||
|
:title="$t('account.noRealPaymentsNeeded')"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-form v-model="valid">
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('account.userData.bankName')"
|
||||||
|
v-model="accountStore.payment.bankName"
|
||||||
|
:rules="getStringRules(8)"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('account.userData.iban')"
|
||||||
|
v-model="accountStore.payment.iban"
|
||||||
|
:rules="getIbanRules()"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<outlined-button
|
||||||
|
color="success"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
:disabled="!valid"
|
||||||
|
:loading="accountStore.fetchInProgress"
|
||||||
|
@click="accountStore.savePayment"
|
||||||
|
>
|
||||||
|
{{ $t('misc.actions.save') }}
|
||||||
|
</outlined-button>
|
||||||
|
</template>
|
||||||
|
</action-dialog>
|
||||||
|
</template>
|
||||||
@@ -10,6 +10,7 @@ const accountStore = useAccountStore()
|
|||||||
async function registerAccount() {
|
async function registerAccount() {
|
||||||
accountStore.registerAccount()
|
accountStore.registerAccount()
|
||||||
.then(result => {
|
.then(result => {
|
||||||
|
console.log(result)
|
||||||
if (result) {
|
if (result) {
|
||||||
showRegisterCard.value = false
|
showRegisterCard.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useOrderStore } from '@/stores/order.store';
|
|||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
const orderStore = useOrderStore()
|
const orderStore = useOrderStore()
|
||||||
|
|
||||||
orderStore.getOrdersOfAccount(accountStore.userAccount)
|
orderStore.getOrdersOfAccount(accountStore.userAccount, accountStore.userAccountToken)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAccountStore } from '@/stores/account.store';
|
import { useAccountStore } from '@/stores/account.store';
|
||||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
|
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
@@ -19,7 +19,7 @@ accountStore.getAllAccounts()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<admin-data-layout
|
<data-layout
|
||||||
:add-button-string="$t('account.addNewAccount')"
|
:add-button-string="$t('account.addNewAccount')"
|
||||||
:fetch-in-progress="accountStore.fetchInProgress"
|
:fetch-in-progress="accountStore.fetchInProgress"
|
||||||
>
|
>
|
||||||
@@ -44,5 +44,5 @@ accountStore.getAllAccounts()
|
|||||||
/> -->
|
/> -->
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</admin-data-layout>
|
</data-layout>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBandStore } from '@/stores/band.store';
|
import { useBandStore } from '@/stores/band.store';
|
||||||
import bandEditDialog from './bandEditDialog.vue';
|
import bandEditDialog from './bandEditDialog.vue';
|
||||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
|
|
||||||
const bandStore = useBandStore()
|
const bandStore = useBandStore()
|
||||||
@@ -22,7 +22,7 @@ bandStore.getBands()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<admin-data-layout
|
<data-layout
|
||||||
:add-button-string="$t('band.addNewBand')"
|
:add-button-string="$t('band.addNewBand')"
|
||||||
:fetch-in-progress="bandStore.fetchInProgress"
|
:fetch-in-progress="bandStore.fetchInProgress"
|
||||||
:on-add-click="() => bandStore.newBand()"
|
:on-add-click="() => bandStore.newBand()"
|
||||||
@@ -72,7 +72,7 @@ bandStore.getBands()
|
|||||||
/> -->
|
/> -->
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</admin-data-layout>
|
</data-layout>
|
||||||
|
|
||||||
<band-edit-dialog />
|
<band-edit-dialog />
|
||||||
</template>
|
</template>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useBandStore } from '@/stores/band.store';
|
import { useBandStore } from '@/stores/band.store';
|
||||||
import { useConcertStore } from '@/stores/concert.store';
|
import { useConcertStore } from '@/stores/concert.store';
|
||||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
const concertStore = useConcertStore()
|
const concertStore = useConcertStore()
|
||||||
@@ -25,7 +25,7 @@ concertStore.getConcerts()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<admin-data-layout
|
<data-layout
|
||||||
:add-button-string="$t('concert.addNewConcert')"
|
:add-button-string="$t('concert.addNewConcert')"
|
||||||
:fetch-in-progress="concertStore.fetchInProgress"
|
:fetch-in-progress="concertStore.fetchInProgress"
|
||||||
:on-add-click="() => concertStore.newConcert()"
|
:on-add-click="() => concertStore.newConcert()"
|
||||||
@@ -40,7 +40,7 @@ concertStore.getConcerts()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item.price="{ item }">
|
<template #item.price="{ item }">
|
||||||
{{ item.price.toFixed(2) }} €
|
{{ item.price.toFixed(2) + '€' }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item.image="{ item }">
|
<template #item.image="{ item }">
|
||||||
@@ -73,5 +73,5 @@ concertStore.getConcerts()
|
|||||||
/> -->
|
/> -->
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</admin-data-layout>
|
</data-layout>
|
||||||
</template>
|
</template>
|
||||||
@@ -5,7 +5,7 @@ import { useAccountStore } from '@/stores/account.store';
|
|||||||
import { useLocationStore } from '@/stores/location.store';
|
import { useLocationStore } from '@/stores/location.store';
|
||||||
import { useGenreStore } from '@/stores/genre.store';
|
import { useGenreStore } from '@/stores/genre.store';
|
||||||
import { usePreferencesStore } from '@/stores/preferences.store';
|
import { usePreferencesStore } from '@/stores/preferences.store';
|
||||||
import dashboardCard from './dashboardCard.vue';
|
import dashboardCard from '../../../components/pageParts/dashboardCard.vue';
|
||||||
import { useOrderStore } from '@/stores/order.store';
|
import { useOrderStore } from '@/stores/order.store';
|
||||||
import { useFilesStore } from '@/stores/files.store';
|
import { useFilesStore } from '@/stores/files.store';
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const test = ref()
|
|||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<v-btn type="submit">Submit</v-btn>
|
<v-btn type="submit">{{ $t('misc.submit') }}</v-btn>
|
||||||
</v-form>
|
</v-form>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import FileUploadDialog from './fileUploadDialog.vue';
|
import FileUploadDialog from './fileUploadDialog.vue';
|
||||||
import { useFilesStore } from '@/stores/files.store';
|
import { useFilesStore } from '@/stores/files.store';
|
||||||
@@ -12,7 +12,7 @@ filesStore.getStaticFolders()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<admin-data-layout
|
<data-layout
|
||||||
:add-button-string="$t('misc.uploadFile')"
|
:add-button-string="$t('misc.uploadFile')"
|
||||||
:fetch-in-progress="filesStore.fetchInProgress"
|
:fetch-in-progress="filesStore.fetchInProgress"
|
||||||
:on-add-click="() => { filesStore.showFileUploadDialog = true }"
|
:on-add-click="() => { filesStore.showFileUploadDialog = true }"
|
||||||
@@ -67,7 +67,7 @@ filesStore.getStaticFolders()
|
|||||||
v-if="filesStore.selectedFile != undefined && filesStore.selectedFile.name.endsWith('js')"
|
v-if="filesStore.selectedFile != undefined && filesStore.selectedFile.name.endsWith('js')"
|
||||||
:model-value="filesStore.selectedFile.content"
|
:model-value="filesStore.selectedFile.content"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
label="Content"
|
:label="$t('misc.content')"
|
||||||
height="300"
|
height="300"
|
||||||
rows="30"
|
rows="30"
|
||||||
/>
|
/>
|
||||||
@@ -104,7 +104,7 @@ filesStore.getStaticFolders()
|
|||||||
prepend-icon="mdi-web"
|
prepend-icon="mdi-web"
|
||||||
v-if="filesStore.selectedFile.copyright.url.length > 0"
|
v-if="filesStore.selectedFile.copyright.url.length > 0"
|
||||||
>
|
>
|
||||||
<a :href="filesStore.selectedFile.copyright.url" target="_blank" >Quelle</a>
|
<a :href="filesStore.selectedFile.copyright.url" target="_blank" >{{ $t('misc.source') }}</a>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</template>
|
</template>
|
||||||
</v-list>
|
</v-list>
|
||||||
@@ -112,7 +112,7 @@ filesStore.getStaticFolders()
|
|||||||
</v-row>
|
</v-row>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</admin-data-layout>
|
</data-layout>
|
||||||
|
|
||||||
<file-preview-dialog
|
<file-preview-dialog
|
||||||
v-model:show-dialog="showPreviewDialog"
|
v-model:show-dialog="showPreviewDialog"
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
import genreEditDialog from './genreEditDialog.vue';
|
import genreEditDialog from './genreEditDialog.vue';
|
||||||
import { useGenreStore } from '@/stores/genre.store';
|
import { useGenreStore } from '@/stores/genre.store';
|
||||||
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
|
|
||||||
const genreStore = useGenreStore()
|
const genreStore = useGenreStore()
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
{ title: "Name", value: "name" },
|
{ title: feedbackStore.i18n.t('band.genre'), value: "name" },
|
||||||
{ title: "Bands", value: "bands" },
|
{ title: feedbackStore.i18n.t('band.name'), value: "bands" },
|
||||||
{ title: "", value: "edit", width: 130 }
|
{ title: "", value: "edit", width: 130 }
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ genreStore.getGenres()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<admin-data-layout
|
<data-layout
|
||||||
:add-button-string="$t('band.addNewGenre')"
|
:add-button-string="$t('band.addNewGenre')"
|
||||||
:fetch-in-progress="genreStore.fetchInProgress"
|
:fetch-in-progress="genreStore.fetchInProgress"
|
||||||
:on-add-click="() => { genreStore.newGenre() }"
|
:on-add-click="() => { genreStore.newGenre() }"
|
||||||
@@ -48,7 +50,7 @@ genreStore.getGenres()
|
|||||||
/> -->
|
/> -->
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</admin-data-layout>
|
</data-layout>
|
||||||
|
|
||||||
<genre-edit-dialog />
|
<genre-edit-dialog />
|
||||||
</template>
|
</template>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
import { useLocationStore } from '@/stores/location.store';
|
import { useLocationStore } from '@/stores/location.store';
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ locationStore.getLocations()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<admin-data-layout
|
<data-layout
|
||||||
:fetch-in-progress="locationStore.fetchInProgress"
|
:fetch-in-progress="locationStore.fetchInProgress"
|
||||||
:add-button-string="$t('location.addLocation')"
|
:add-button-string="$t('location.addLocation')"
|
||||||
:on-add-click="() => { locationStore.newLocation() }"
|
:on-add-click="() => { locationStore.newLocation() }"
|
||||||
@@ -66,5 +66,5 @@ locationStore.getLocations()
|
|||||||
/> -->
|
/> -->
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</admin-data-layout>
|
</data-layout>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
import dataLayout from '@/layouts/dataLayout.vue';
|
||||||
import { useOrderStore } from '@/stores/order.store';
|
import { useOrderStore } from '@/stores/order.store';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import OrderDetailDialog from './orderDetailDialog.vue';
|
import OrderDetailDialog from './orderDetailDialog.vue';
|
||||||
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
|
|
||||||
const orderStore = useOrderStore()
|
const orderStore = useOrderStore()
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
{ title: "Account", value: "account.username" },
|
{ title: feedbackStore.i18n.t('account.userData.username'), value: "account.username" },
|
||||||
{ title: "Name", value: "account" },
|
{ title: feedbackStore.i18n.t('account.userData.firstName'), value: "account" },
|
||||||
{ title: "Bestellt am", value: "orderedAt" },
|
{ title: feedbackStore.i18n.t('order.orderedAt'), value: "orderedAt" },
|
||||||
{ title: "Adresse", value: "street" },
|
{ title: feedbackStore.i18n.t('account.userData.address'), value: "street" },
|
||||||
{ title: "Stadt", value: "city" },
|
{ title: feedbackStore.i18n.t('account.userData.placeOfResidence'), value: "city" },
|
||||||
{ title: "Versendet", value: "shipped" },
|
{ title: feedbackStore.i18n.t('order.orderState'), value: "shipped" },
|
||||||
{ title: "Aktionen", value: "edit", width: 130 }
|
{ title: "", value: "edit", width: 130 }
|
||||||
]
|
]
|
||||||
|
|
||||||
orderStore.getAllOrders()
|
orderStore.getAllOrders()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<admin-data-layout
|
<data-layout
|
||||||
:hide-add-button="true"
|
:hide-add-button="true"
|
||||||
>
|
>
|
||||||
<v-data-table
|
<v-data-table
|
||||||
@@ -68,7 +70,7 @@ orderStore.getAllOrders()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</admin-data-layout>
|
</data-layout>
|
||||||
|
|
||||||
<order-detail-dialog />
|
<order-detail-dialog />
|
||||||
</template>
|
</template>
|
||||||
@@ -7,9 +7,13 @@ import concertSection from './concertSection.vue';
|
|||||||
import heroImage from '@/components/pageParts/heroImage.vue';
|
import heroImage from '@/components/pageParts/heroImage.vue';
|
||||||
import { useBandStore } from '@/stores/band.store';
|
import { useBandStore } from '@/stores/band.store';
|
||||||
import { onMounted, watch } from 'vue';
|
import { onMounted, watch } from 'vue';
|
||||||
|
import { useConcertStore } from '@/stores/concert.store';
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const bandStore = useBandStore()
|
const bandStore = useBandStore()
|
||||||
|
const concertStore = useConcertStore()
|
||||||
|
|
||||||
|
concertStore.getConcerts()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
bandStore.getBand(String(router.currentRoute.value.params.name).replaceAll('-', ' '))
|
bandStore.getBand(String(router.currentRoute.value.params.name).replaceAll('-', ' '))
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ watch(() => router.currentRoute.value.query, () => {
|
|||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-row
|
<v-row
|
||||||
v-else-if="bandStore.bands.length > 0"
|
v-else-if="bandStore.filteredBands.length > 0"
|
||||||
v-for="band in bandStore.bands"
|
v-for="band in bandStore.filteredBands"
|
||||||
>
|
>
|
||||||
<v-col>
|
<v-col>
|
||||||
<band-list-item
|
<band-list-item
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ watch(() => router.currentRoute.value.params.date, () => {
|
|||||||
<circular-progress-indeterminate />
|
<circular-progress-indeterminate />
|
||||||
|
|
||||||
<div class="pt-5 text-h3">
|
<div class="pt-5 text-h3">
|
||||||
{{ $t('misc.loading') }}...
|
{{ $t('misc.loading') }}
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConcertStore } from '@/stores/concert.store';
|
import { useConcertStore } from "@/stores/concert.store";
|
||||||
import concertListItem from '@/components/pageParts/concertListItem.vue';
|
import concertListItem from "@/components/pageParts/concertListItem.vue";
|
||||||
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
|
import cardViewHorizontal from "@/components/basics/cardViewHorizontal.vue";
|
||||||
import sectionDivider from '@/components/basics/sectionDivider.vue';
|
import sectionDivider from "@/components/basics/sectionDivider.vue";
|
||||||
import concertFilterbar from './concertFilterbar.vue';
|
import concertFilterbar from "./concertFilterbar.vue";
|
||||||
|
|
||||||
const concertStore = useConcertStore()
|
const concertStore = useConcertStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="concertStore.fetchInProgress">
|
||||||
v-if="concertStore.fetchInProgress"
|
|
||||||
>
|
|
||||||
<section-divider :loading="true" />
|
<section-divider :loading="true" />
|
||||||
<v-row v-for="i in 3">
|
<v-row v-for="i in 3">
|
||||||
<v-col>
|
<v-col>
|
||||||
@@ -26,13 +24,21 @@ const concertStore = useConcertStore()
|
|||||||
>
|
>
|
||||||
<div v-if="concert.offered">
|
<div v-if="concert.offered">
|
||||||
<v-row
|
<v-row
|
||||||
v-if="index == 0 ||
|
v-if="
|
||||||
|
index == 0 ||
|
||||||
new Date(concertStore.concerts[index - 1].date).getMonth() !=
|
new Date(concertStore.concerts[index - 1].date).getMonth() !=
|
||||||
new Date(concertStore.concerts[index].date).getMonth()"
|
new Date(concertStore.concerts[index].date).getMonth()
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<v-col>
|
<v-col>
|
||||||
<section-divider
|
<section-divider
|
||||||
:title="new Date(concert.date).toLocaleString('default', { month: 'long' }) + ' ' + new Date(concert.date).getFullYear()"
|
:title="
|
||||||
|
new Date(concert.date).toLocaleString('default', {
|
||||||
|
month: 'long',
|
||||||
|
}) +
|
||||||
|
' ' +
|
||||||
|
new Date(concert.date).getFullYear()
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const showOrderingDialog = ref()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-card-text class="text-right text-h5" v-if="basketStore.itemsInBasket.length > 0">
|
<v-card-text class="text-right text-h5" v-if="basketStore.itemsInBasket.length > 0">
|
||||||
{{ $t('misc.totalPrice') }}: {{ (basketStore.getTotalPrice).toFixed(2) }} €
|
{{ $t('misc.totalPrice') }}: {{ (basketStore.getTotalPrice).toFixed(2) + '€' }}
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,19 +44,19 @@ function removeFromBasket(basketItem: BasketItemModel) {
|
|||||||
|
|
||||||
<!-- Quantity -->
|
<!-- Quantity -->
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{{ basketItem.seats.length }}x
|
{{ basketItem.seats.length + 'x' }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Price per event -->
|
<!-- Price per event -->
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div v-if="basketItem.seats">
|
<div v-if="basketItem.seats">
|
||||||
{{ basketItem.price.toFixed(2) }} €
|
{{ basketItem.price.toFixed(2) + '€' }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Total price -->
|
<!-- Total price -->
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{{ (calcPrice(basketItem.concert.price, basketItem.seats.length)).toFixed(2) }} €
|
{{ (calcPrice(basketItem.concert.price, basketItem.seats.length)).toFixed(2) + '€' }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
|
|||||||
@@ -1,42 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useExerciseStore } from '@/stores/exercise.store';
|
import { useExerciseStore } from "@/stores/exercise.store";
|
||||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
import outlinedButton from "@/components/basics/outlinedButton.vue";
|
||||||
import { generateResultsPdf } from '@/scripts/pdfScripts';
|
import { generateResultsPdf } from "@/scripts/pdfScripts";
|
||||||
import { usePreferencesStore } from '@/stores/preferences.store';
|
import { usePreferencesStore } from "@/stores/preferences.store";
|
||||||
import cardView from '@/components/basics/cardView.vue';
|
import cardView from "@/components/basics/cardView.vue";
|
||||||
import { LanguageEnum } from '@/data/enums/languageEnum';
|
import { LanguageEnum } from "@/data/enums/languageEnum";
|
||||||
|
import { ExerciseModel } from "@/data/models/exercises/exerciseModel";
|
||||||
|
import { getExerciseDotColor } from "@/scripts/colorScripts";
|
||||||
|
import { getExerciseDescriptionLanguage, getExerciseNameLanguage } from "@/scripts/languageScripts";
|
||||||
|
|
||||||
const exerciseStore = useExerciseStore()
|
const exerciseStore = useExerciseStore();
|
||||||
const preferencesStore = usePreferencesStore()
|
const preferencesStore = usePreferencesStore();
|
||||||
|
|
||||||
exerciseStore.solveExercise(1, 1)
|
// Mark this exercise as solved if page was opened
|
||||||
|
exerciseStore.solveExercise(1, 1);
|
||||||
|
|
||||||
function getDotColor(exerciseGroupNr: number) {
|
function generateExerciseKey() {
|
||||||
switch(exerciseGroupNr) {
|
|
||||||
case 0: return "purple"
|
|
||||||
case 1: return "orange"
|
|
||||||
case 2: return "blue"
|
|
||||||
case 3: return "pink"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checksum(num: number) {
|
|
||||||
let cs = 0
|
|
||||||
|
|
||||||
for (; num > 0; num = Math.trunc(num / 10)) {
|
|
||||||
cs += num % 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateExerciseKey(exerciseGroup: number, exerciseNr: number) {
|
|
||||||
try {
|
try {
|
||||||
let matrikelNr = Number(preferencesStore.registrationNumber)
|
let code = "";
|
||||||
let a = matrikelNr + exerciseGroup * 100 + exerciseNr * 12345678 +
|
|
||||||
checksum(Number(preferencesStore.registrationNumber)) * 123
|
for (let i = 0; i < 13; i++) {
|
||||||
return a.toString(16).toUpperCase()
|
if (exerciseStore.exercises[i].solved) {
|
||||||
} catch(e) {}
|
code += "3";
|
||||||
|
} else {
|
||||||
|
code += "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Number(code) + Number(preferencesStore.registrationNumber)) * 237;
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -45,30 +37,31 @@ function generateExerciseKey(exerciseGroup: number, exerciseNr: number) {
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
|
|
||||||
<v-col
|
|
||||||
v-if="preferencesStore.studentName.length < 3 || preferencesStore.registrationNumber.length < 7"
|
|
||||||
cols="auto"
|
|
||||||
>
|
|
||||||
<card-view variant="outlined" >
|
|
||||||
{{ $t('misc.fulfillYourPersonalDataFirst') }}
|
|
||||||
</card-view>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-tooltip :text="$t('misc.fulfillYourPersonalDataFirst')">
|
|
||||||
<template #activator="{ props }"></template>
|
|
||||||
|
|
||||||
</v-tooltip>
|
|
||||||
<outlined-button
|
<outlined-button
|
||||||
prepend-icon="mdi-file-pdf-box"
|
prepend-icon="mdi-file-pdf-box"
|
||||||
@click="generateResultsPdf()"
|
@click="generateResultsPdf()"
|
||||||
:disabled="preferencesStore.studentName.length < 3 || preferencesStore.registrationNumber.length < 7"
|
:disabled="
|
||||||
|
preferencesStore.studentName.length < 3 ||
|
||||||
|
preferencesStore.registrationNumber.length < 7
|
||||||
|
"
|
||||||
>
|
>
|
||||||
PDF generieren
|
{{ $t("help.scoreBoard.generatePdf") }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col class="text-h5 text-center">
|
||||||
|
<div>
|
||||||
|
{{ $t("help.scoreBoard.personalSolutionKey") + ":" }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ generateExerciseKey() }}
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<card-view
|
<card-view
|
||||||
@@ -76,49 +69,55 @@ function generateExerciseKey(exerciseGroup: number, exerciseNr: number) {
|
|||||||
icon="mdi-checkbox-marked-circle-auto-outline"
|
icon="mdi-checkbox-marked-circle-auto-outline"
|
||||||
>
|
>
|
||||||
<template #borderless>
|
<template #borderless>
|
||||||
<v-timeline
|
<v-timeline side="end" class="px-5" align="start">
|
||||||
side="end"
|
|
||||||
class="px-5"
|
|
||||||
align="start"
|
|
||||||
>
|
|
||||||
<template v-for="exercise of exerciseStore.exercises">
|
<template v-for="exercise of exerciseStore.exercises">
|
||||||
<v-timeline-item v-if="exercise.exerciseNr == 1"
|
<!-- Add exercise group description item -->
|
||||||
dot-color="grey"
|
<v-timeline-item
|
||||||
|
v-if="exercise.exerciseNr == 1"
|
||||||
|
:dot-color="
|
||||||
|
getExerciseDotColor(exercise.exerciseGroup.groupNr)
|
||||||
|
"
|
||||||
|
:icon="exercise.exerciseGroup.icon"
|
||||||
fill-dot
|
fill-dot
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="`pt-1 text-h5 font-weight-bold text-${getDotColor(exercise.exerciseGroup.groupNr)}`"
|
:class="`pt-1 text-h5 font-weight-bold text-${getExerciseDotColor(
|
||||||
|
exercise.exerciseGroup.groupNr
|
||||||
|
)}`"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
(preferencesStore.language == LanguageEnum.GERMAN
|
preferencesStore.language == LanguageEnum.GERMAN
|
||||||
? exercise.exerciseGroup.nameDe
|
? exercise.exerciseGroup.nameDe
|
||||||
: exercise.exerciseGroup.nameEn)
|
: exercise.exerciseGroup.nameEn
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{{
|
{{
|
||||||
(preferencesStore.language == LanguageEnum.GERMAN
|
preferencesStore.language == LanguageEnum.GERMAN
|
||||||
? exercise.exerciseGroup.descriptionDe
|
? exercise.exerciseGroup.descriptionDe
|
||||||
: exercise.exerciseGroup.descriptionEn)
|
: exercise.exerciseGroup.descriptionEn
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</v-timeline-item>
|
</v-timeline-item>
|
||||||
|
|
||||||
|
<!-- Exercise item -->
|
||||||
<v-timeline-item
|
<v-timeline-item
|
||||||
:dot-color="getDotColor(exercise.exerciseGroup.groupNr)"
|
v-if="exercise.available"
|
||||||
|
:dot-color="exercise.solved ? 'green' : 'primary'"
|
||||||
:icon="exercise.solved ? 'mdi-check' : 'mdi-pencil'"
|
:icon="exercise.solved ? 'mdi-check' : 'mdi-pencil'"
|
||||||
>
|
>
|
||||||
<!-- Right side -->
|
<!-- Right side -->
|
||||||
<card-view
|
<card-view
|
||||||
:title="$t('help.scoreBoard.exerciseNr', [exercise.exerciseGroup.groupNr, exercise.exerciseNr]) +
|
:title="
|
||||||
(preferencesStore.language == LanguageEnum.GERMAN ? exercise.nameDe : exercise.nameEn)"
|
$t('help.scoreBoard.exerciseNr', [
|
||||||
|
exercise.exerciseGroup.groupNr,
|
||||||
|
exercise.exerciseNr,
|
||||||
|
]) + getExerciseNameLanguage(exercise)
|
||||||
|
"
|
||||||
:color="exercise.solved ? 'green' : 'primary'"
|
:color="exercise.solved ? 'green' : 'primary'"
|
||||||
>
|
>
|
||||||
{{ preferencesStore.language == LanguageEnum.GERMAN ? exercise.descriptionDe : exercise.descriptionEn }}
|
{{ getExerciseDescriptionLanguage(exercise) }}
|
||||||
<div class="pt-2 text-h6">
|
|
||||||
Solution Code: 0x{{ generateExerciseKey(exercise.exerciseGroup.groupNr, exercise.exerciseNr) }}
|
|
||||||
</div>
|
|
||||||
</card-view>
|
</card-view>
|
||||||
</v-timeline-item>
|
</v-timeline-item>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConcertStore } from '@/stores/concert.store';
|
import { useConcertStore } from "@/stores/concert.store";
|
||||||
import { useLocationStore } from '@/stores/location.store';
|
import { useLocationStore } from "@/stores/location.store";
|
||||||
import bandSection from './bandsSection.vue';
|
import bandSection from "./bandsSection.vue";
|
||||||
import UpcomingConcertsSection from './upcomingConcertsSection.vue';
|
import UpcomingConcertsSection from "./upcomingConcertsSection.vue";
|
||||||
import TopLocationsSection from './topLocationsSection.vue';
|
import TopLocationsSection from "./topLocationsSection.vue";
|
||||||
import { usePreferencesStore } from '@/stores/preferences.store';
|
import { usePreferencesStore } from "@/stores/preferences.store";
|
||||||
import welcomeDialog from './welcomeDialog.vue';
|
import welcomeDialog from "./welcomeDialog/dialog.vue";
|
||||||
import { ref } from 'vue';
|
import { ref } from "vue";
|
||||||
|
import genresSection from "./topGenresSection.vue";
|
||||||
|
|
||||||
const concertStore = useConcertStore()
|
const concertStore = useConcertStore();
|
||||||
const locationStore = useLocationStore()
|
const locationStore = useLocationStore();
|
||||||
const preferencesStore = usePreferencesStore()
|
const preferencesStore = usePreferencesStore();
|
||||||
const showWelcomeDialog = ref(false)
|
const showWelcomeDialog = ref(false);
|
||||||
|
|
||||||
concertStore.getUpcomingConcerts()
|
concertStore.getUpcomingConcerts();
|
||||||
locationStore.getTopLocations()
|
locationStore.getTopLocations();
|
||||||
|
|
||||||
// First startup
|
// First startup
|
||||||
if (preferencesStore.firstStartup) {
|
if (preferencesStore.firstStartup) {
|
||||||
showWelcomeDialog.value = true
|
showWelcomeDialog.value = true;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -34,6 +35,8 @@ if (preferencesStore.firstStartup) {
|
|||||||
<v-col cols="10">
|
<v-col cols="10">
|
||||||
<upcoming-concerts-section />
|
<upcoming-concerts-section />
|
||||||
|
|
||||||
|
<genres-section />
|
||||||
|
|
||||||
<top-locations-section />
|
<top-locations-section />
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
|
|||||||
62
src/pages/misc/homePage/topGenresSection.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CardView from "@/components/basics/cardView.vue";
|
||||||
|
import CardViewOneLine from "@/components/basics/cardViewOneLine.vue";
|
||||||
|
import SectionDivider from "@/components/basics/sectionDivider.vue";
|
||||||
|
import { GenreApiModel } from "@/data/models/acts/genreApiModel";
|
||||||
|
import { useGenreStore } from "@/stores/genre.store";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import outlinedButton from "@/components/basics/outlinedButton.vue";
|
||||||
|
|
||||||
|
const genreStore = useGenreStore();
|
||||||
|
const genresByNumberOfBands = ref<Array<GenreApiModel>>([]);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
genreStore.getGenres();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => genreStore.genres,
|
||||||
|
() => {
|
||||||
|
genresByNumberOfBands.value = genreStore.genres;
|
||||||
|
|
||||||
|
genresByNumberOfBands.value.sort((a, b) => {
|
||||||
|
return b.bands.length - a.bands.length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<section-divider :title="$t('genre.popular')" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col v-if="genreStore.fetchInProgress" v-for="n in 4" cols="6" md="">
|
||||||
|
<v-skeleton-loader :loading="true" type="card" />
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col v-else v-for="genre in genreStore.topGenres" cols="6" md="3">
|
||||||
|
<card-view
|
||||||
|
@click="router.push({ path: '/bands', query: { genreName: genre.name }})"
|
||||||
|
:title="genre.name"
|
||||||
|
:subtitle="genre.bands.length + ' ' + $t('band.band', genre.bands.length)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- todo?
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<outlined-button
|
||||||
|
append-icon="mdi-chevron-right"
|
||||||
|
@click="router.push('/')"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
{{ $t('genre.allGenres') }}
|
||||||
|
</outlined-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row> -->
|
||||||
|
</template>
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import actionDialog from '@/components/basics/actionDialog.vue';
|
|
||||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
|
||||||
import ServerStateText from '@/components/pageParts/serverStateText.vue';
|
|
||||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
|
||||||
import { usePreferencesStore } from '@/stores/preferences.store';
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
|
|
||||||
const preferencesStore = usePreferencesStore()
|
|
||||||
const feedbackStore = useFeedbackStore()
|
|
||||||
const showDialog = defineModel()
|
|
||||||
const currentStep = ref(0)
|
|
||||||
|
|
||||||
const steps = [
|
|
||||||
feedbackStore.i18n.t('misc.firstStartup.connectToServer'),
|
|
||||||
feedbackStore.i18n.t('misc.firstStartup.database'),
|
|
||||||
feedbackStore.i18n.t('misc.firstStartup.exercises'),
|
|
||||||
feedbackStore.i18n.t('misc.firstStartup.userData'),
|
|
||||||
]
|
|
||||||
|
|
||||||
preferencesStore.getServerState()
|
|
||||||
|
|
||||||
|
|
||||||
watch(() => currentStep.value, () => {
|
|
||||||
switch(currentStep.value) {
|
|
||||||
case 2: {
|
|
||||||
preferencesStore.resetDb();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 3: {
|
|
||||||
preferencesStore.resetExerciseProg();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 4: {
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<action-dialog
|
|
||||||
v-model="showDialog"
|
|
||||||
:title="$t('misc.firstStartup.title')"
|
|
||||||
icon="mdi-human-greeting"
|
|
||||||
max-width="800"
|
|
||||||
persistent
|
|
||||||
>
|
|
||||||
<v-stepper
|
|
||||||
v-model="currentStep"
|
|
||||||
alt-labels
|
|
||||||
flat
|
|
||||||
>
|
|
||||||
<template #default="{ prev, next }">
|
|
||||||
<!-- Header items -->
|
|
||||||
<v-stepper-header>
|
|
||||||
<template v-for="(step, n) in steps">
|
|
||||||
<v-stepper-item
|
|
||||||
:complete="currentStep > n + 1"
|
|
||||||
:title="step"
|
|
||||||
:value="n + 1"
|
|
||||||
complete-icon="mdi-check"
|
|
||||||
color="success"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<v-divider v-if="n < steps.length - 1" />
|
|
||||||
</template>
|
|
||||||
</v-stepper-header>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<v-stepper-window>
|
|
||||||
<!-- Step 1: Check connection to backend server -->
|
|
||||||
<v-stepper-window-item
|
|
||||||
:value="1"
|
|
||||||
class="text-h4 text-center"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{{ $t('preferences.serverState') }}:
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<server-state-text />
|
|
||||||
</v-stepper-window-item>
|
|
||||||
|
|
||||||
<!-- Step 2: Reset the database -->
|
|
||||||
<v-stepper-window-item
|
|
||||||
:value="2"
|
|
||||||
>
|
|
||||||
<div v-if="preferencesStore.fetchInProgress" class="text-center text-h4 pb-4">
|
|
||||||
<div class="pb-4">
|
|
||||||
{{ $t('misc.firstStartup.createDatabase') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-progress-linear indeterminate />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="text-center text-h4 pb-4 text-green">
|
|
||||||
<v-icon icon="mdi-check" /> {{ $t('misc.firstStartup.finished') }}
|
|
||||||
</div>
|
|
||||||
</v-stepper-window-item>
|
|
||||||
|
|
||||||
<!-- Step 3: Create exercises -->
|
|
||||||
<v-stepper-window-item
|
|
||||||
:value="3"
|
|
||||||
>
|
|
||||||
<div v-if="preferencesStore.fetchInProgress" class="text-center text-h4 pb-4">
|
|
||||||
<div class="pb-4">
|
|
||||||
{{ $t('misc.firstStartup.createExercises') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-progress-linear indeterminate />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="text-center text-h4 pb-4 text-green">
|
|
||||||
<v-icon icon="mdi-check" /> {{ $t('misc.firstStartup.finished') }}
|
|
||||||
</div>
|
|
||||||
</v-stepper-window-item>
|
|
||||||
|
|
||||||
<!-- Step 4: Personal data -->
|
|
||||||
<v-stepper-window-item
|
|
||||||
:value="4"
|
|
||||||
>
|
|
||||||
<v-container class="px-0 py-2">
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-alert color="warning" icon="mdi-alert">
|
|
||||||
{{ $t('misc.firstStartup.enterYourPersonalData') }}
|
|
||||||
</v-alert>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
variant="outlined"
|
|
||||||
hide-details
|
|
||||||
:label="$t('misc.yourFullName')"
|
|
||||||
v-model="preferencesStore.studentName"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-text-field
|
|
||||||
variant="outlined"
|
|
||||||
hide-details
|
|
||||||
:label="$t('misc.registrationNumber')"
|
|
||||||
v-model="preferencesStore.registrationNumber"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</v-stepper-window-item>
|
|
||||||
</v-stepper-window>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Next/Previous buttons -->
|
|
||||||
<v-stepper-actions
|
|
||||||
@click:next="next"
|
|
||||||
>
|
|
||||||
<template #prev="{ props }">
|
|
||||||
<v-spacer />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #next="{ props }">
|
|
||||||
<outlined-button
|
|
||||||
v-if="currentStep < 4"
|
|
||||||
@click="props.onClick()"
|
|
||||||
:disabled="preferencesStore.fetchInProgress"
|
|
||||||
>
|
|
||||||
{{ $t('misc.actions.next') }}
|
|
||||||
</outlined-button>
|
|
||||||
|
|
||||||
<outlined-button
|
|
||||||
v-else
|
|
||||||
@click="showDialog = false; preferencesStore.firstStartup = false"
|
|
||||||
:disabled="preferencesStore.studentName.length == 0 ||
|
|
||||||
preferencesStore.registrationNumber.length == 0"
|
|
||||||
prepend-icon="mdi-check"
|
|
||||||
color="success"
|
|
||||||
>
|
|
||||||
{{ $t('misc.firstStartup.complete') }}
|
|
||||||
</outlined-button>
|
|
||||||
</template>
|
|
||||||
</v-stepper-actions>
|
|
||||||
</template>
|
|
||||||
</v-stepper>
|
|
||||||
</action-dialog>
|
|
||||||
</template>
|
|
||||||
143
src/pages/misc/homePage/welcomeDialog/dialog.vue
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import actionDialog from '@/components/basics/actionDialog.vue';
|
||||||
|
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||||
|
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences.store';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import step1 from './step1.vue';
|
||||||
|
import step2 from './step2.vue';
|
||||||
|
import step3 from './step3.vue';
|
||||||
|
import step4 from './step4.vue';
|
||||||
|
|
||||||
|
const preferencesStore = usePreferencesStore()
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
const showDialog = defineModel()
|
||||||
|
const currentStep = ref(1)
|
||||||
|
const databaseCreated = ref(false)
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
feedbackStore.i18n.t('misc.firstStartup.connectToServer'),
|
||||||
|
feedbackStore.i18n.t('misc.firstStartup.lookAndFeel'),
|
||||||
|
feedbackStore.i18n.t('misc.firstStartup.database'),
|
||||||
|
feedbackStore.i18n.t('misc.firstStartup.userData'),
|
||||||
|
]
|
||||||
|
|
||||||
|
preferencesStore.getServerState()
|
||||||
|
|
||||||
|
|
||||||
|
watch(() => currentStep.value, async () => {
|
||||||
|
if (currentStep.value == 3 && !databaseCreated.value) {
|
||||||
|
await preferencesStore.resetDb();
|
||||||
|
await preferencesStore.resetExerciseProg();
|
||||||
|
|
||||||
|
databaseCreated.value = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<action-dialog
|
||||||
|
v-model="showDialog"
|
||||||
|
:title="$t('misc.firstStartup.title')"
|
||||||
|
icon="mdi-human-greeting"
|
||||||
|
max-width="800"
|
||||||
|
persistent
|
||||||
|
>
|
||||||
|
<v-stepper
|
||||||
|
v-model="currentStep"
|
||||||
|
alt-labels
|
||||||
|
flat
|
||||||
|
>
|
||||||
|
<template #default="{ prev, next }">
|
||||||
|
<!-- Header items -->
|
||||||
|
<v-stepper-header>
|
||||||
|
<template v-for="(step, n) in steps">
|
||||||
|
<v-stepper-item
|
||||||
|
:complete="currentStep > n + 1"
|
||||||
|
:title="step"
|
||||||
|
:value="n + 1"
|
||||||
|
complete-icon="mdi-check"
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-divider v-if="n < steps.length - 1" />
|
||||||
|
</template>
|
||||||
|
</v-stepper-header>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<v-stepper-window>
|
||||||
|
<!-- Step 1: Check connection to backend server -->
|
||||||
|
<v-stepper-window-item
|
||||||
|
:value="1"
|
||||||
|
>
|
||||||
|
<step1 />
|
||||||
|
</v-stepper-window-item>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Step 2: Select theme and language -->
|
||||||
|
<v-stepper-window-item
|
||||||
|
:value="2"
|
||||||
|
|
||||||
|
>
|
||||||
|
<step2 />
|
||||||
|
</v-stepper-window-item>
|
||||||
|
|
||||||
|
<!-- Step 3: Reset the database -->
|
||||||
|
<v-stepper-window-item
|
||||||
|
:value="3"
|
||||||
|
>
|
||||||
|
<step3 />
|
||||||
|
</v-stepper-window-item>
|
||||||
|
|
||||||
|
<!-- Step 4: Personal data -->
|
||||||
|
<v-stepper-window-item
|
||||||
|
:value="4"
|
||||||
|
>
|
||||||
|
<step4 />
|
||||||
|
</v-stepper-window-item>
|
||||||
|
</v-stepper-window>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Next/Previous buttons -->
|
||||||
|
<v-stepper-actions
|
||||||
|
@click:next="next"
|
||||||
|
@click:prev="prev"
|
||||||
|
>
|
||||||
|
<template #prev="{ props }">
|
||||||
|
<outlined-button
|
||||||
|
@click="props.onClick()"
|
||||||
|
:disabled="currentStep == 1 || preferencesStore.fetchInProgress"
|
||||||
|
color="grey"
|
||||||
|
prepend-icon="mdi-arrow-left"
|
||||||
|
>
|
||||||
|
{{ $t('misc.actions.back') }}
|
||||||
|
</outlined-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #next="{ props }">
|
||||||
|
<outlined-button
|
||||||
|
v-if="currentStep < steps.length"
|
||||||
|
@click="props.onClick()"
|
||||||
|
:disabled="preferencesStore.fetchInProgress"
|
||||||
|
append-icon="mdi-arrow-right"
|
||||||
|
>
|
||||||
|
{{ $t('misc.actions.next') }}
|
||||||
|
</outlined-button>
|
||||||
|
|
||||||
|
<outlined-button
|
||||||
|
v-else
|
||||||
|
@click="showDialog = false; preferencesStore.firstStartup = false"
|
||||||
|
:disabled="preferencesStore.studentName.length < 5 ||
|
||||||
|
preferencesStore.registrationNumber.length < 8"
|
||||||
|
append-icon="mdi-check"
|
||||||
|
color="success"
|
||||||
|
>
|
||||||
|
{{ $t('misc.firstStartup.complete') }}
|
||||||
|
</outlined-button>
|
||||||
|
</template>
|
||||||
|
</v-stepper-actions>
|
||||||
|
</template>
|
||||||
|
</v-stepper>
|
||||||
|
</action-dialog>
|
||||||
|
</template>
|
||||||
23
src/pages/misc/homePage/welcomeDialog/step1.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ServerStateText from '@/components/pageParts/serverStateText.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container class="text-h4 text-center">
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-icon icon="mdi-server" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<div>
|
||||||
|
{{ $t('preferences.serverState') + ':' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<server-state-text />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
48
src/pages/misc/homePage/welcomeDialog/step2.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ThemeEnum } from '@/data/enums/themeEnums';
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences.store';
|
||||||
|
|
||||||
|
const preferencesStore = usePreferencesStore()
|
||||||
|
|
||||||
|
const themeEnums = Object.values(ThemeEnum)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container width="600" class="text-h4 text-center">
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-icon icon="mdi-palette" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
{{ $t('misc.firstStartup.lookAndFeel') }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-select
|
||||||
|
v-model="preferencesStore.theme"
|
||||||
|
:items="themeEnums"
|
||||||
|
:label="$t('preferences.selectedTheme')"
|
||||||
|
variant="outlined"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-select
|
||||||
|
v-model="preferencesStore.language"
|
||||||
|
:items="$i18n.availableLocales"
|
||||||
|
:label="$t('preferences.language')"
|
||||||
|
variant="outlined"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
31
src/pages/misc/homePage/welcomeDialog/step3.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences.store';
|
||||||
|
|
||||||
|
const preferencesStore = usePreferencesStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container width="600" class="text-h4 text-center">
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-icon icon="mdi-database" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
{{ $t('misc.firstStartup.createDatabase') }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col v-if="preferencesStore.fetchInProgress">
|
||||||
|
<v-progress-linear indeterminate />
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col v-else class="text-green">
|
||||||
|
<v-icon icon="mdi-check" /> {{ $t('misc.firstStartup.finished') }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
54
src/pages/misc/homePage/welcomeDialog/step4.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { getRegisterNumberRules, getStringRules } from '@/scripts/validationRules';
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences.store';
|
||||||
|
|
||||||
|
const preferencesStore = usePreferencesStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container class="px-0 py-2" width="600">
|
||||||
|
<v-row>
|
||||||
|
<v-col class="text-h4 text-center">
|
||||||
|
<v-icon icon="mdi-account" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col class="text-h4 text-center">
|
||||||
|
{{ $t('misc.firstStartup.userData') }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-alert color="warning" icon="mdi-alert">
|
||||||
|
{{ $t('misc.firstStartup.enterYourPersonalData') }}
|
||||||
|
</v-alert>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
variant="outlined"
|
||||||
|
:label="$t('misc.yourFullName')"
|
||||||
|
v-model="preferencesStore.studentName"
|
||||||
|
:rules="getStringRules(4)"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field
|
||||||
|
variant="outlined"
|
||||||
|
:label="$t('misc.registrationNumber')"
|
||||||
|
v-model="preferencesStore.registrationNumber"
|
||||||
|
:rules="getRegisterNumberRules()"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
@@ -1,41 +1,55 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import cardView from '@/components/basics/cardView.vue';
|
import cardView from "@/components/basics/cardView.vue";
|
||||||
import packageJson from './../../../../package.json'
|
import packageJson from "./../../../../package.json";
|
||||||
|
|
||||||
|
function openExternal(url: string) {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<card-view
|
<card-view :title="$t('preferences.aboutProject')" icon="mdi-information">
|
||||||
:title="$t('preferences.aboutProject')"
|
|
||||||
icon="mdi-information"
|
|
||||||
>
|
|
||||||
<template #borderless>
|
<template #borderless>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
title="Software Version"
|
:title="$t('misc.softwareVersion')"
|
||||||
:subtitle="packageJson.version"
|
:subtitle="packageJson.version"
|
||||||
prepend-icon="mdi-counter"
|
prepend-icon="mdi-counter"
|
||||||
/>
|
/>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
title="Lizenz"
|
:title="$t('misc.license')"
|
||||||
subtitle="MIT"
|
subtitle="MIT"
|
||||||
prepend-icon="mdi-license"
|
prepend-icon="mdi-license"
|
||||||
/>
|
/>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
title="Entwickler"
|
:title="$t('misc.developer')"
|
||||||
subtitle="Tobias Zoghaib"
|
subtitle="Tobias Zoghaib"
|
||||||
prepend-icon="mdi-account"
|
prepend-icon="mdi-account"
|
||||||
/>
|
/>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
title="Entwickelt im Auftrag"
|
:title="$t('misc.developedFor')"
|
||||||
subtitle="Uni Hannover, Institut für IT-Sicherheit, Fachgebiet Usable Security and Privacy"
|
subtitle="Uni Hannover, Institut für IT-Sicherheit, Fachgebiet Usable Security and Privacy"
|
||||||
prepend-icon="mdi-school"
|
prepend-icon="mdi-school"
|
||||||
/>
|
/>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
title="Copyright"
|
:title="$t('misc.copyright')"
|
||||||
subtitle="2024"
|
subtitle="2024-2025"
|
||||||
prepend-icon="mdi-copyright"
|
prepend-icon="mdi-copyright"
|
||||||
/>
|
/>
|
||||||
|
<v-list-item
|
||||||
|
:title="$t('misc.githubRepository')"
|
||||||
|
prepend-icon="mdi-web"
|
||||||
|
@click="openExternal('https://github.com/TobiZog/eventmaster')"
|
||||||
|
/>
|
||||||
|
<v-list-item
|
||||||
|
:title="$t('misc.projectPage')"
|
||||||
|
prepend-icon="mdi-web"
|
||||||
|
@click="
|
||||||
|
openExternal(
|
||||||
|
'https://www.itsec.uni-hannover.de/de/usec/forschung/eventmaster-learning-web-attacks'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</v-list>
|
</v-list>
|
||||||
</template>
|
</template>
|
||||||
</card-view>
|
</card-view>
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import actionDialog from "@/components/basics/actionDialog.vue";
|
||||||
|
import OutlinedButton from "@/components/basics/outlinedButton.vue";
|
||||||
|
import {
|
||||||
|
getExerciseGroupNameLanguage,
|
||||||
|
getExerciseNameLanguage,
|
||||||
|
} from "@/scripts/languageScripts";
|
||||||
|
import { useExerciseStore } from "@/stores/exercise.store";
|
||||||
|
import { usePreferencesStore } from "@/stores/preferences.store";
|
||||||
|
import { ModelRef } from "vue";
|
||||||
|
|
||||||
|
const showDialog: ModelRef<boolean> = defineModel();
|
||||||
|
const exerciseStore = useExerciseStore();
|
||||||
|
const preferencesStore = usePreferencesStore();
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
preferencesStore.notAvailableExercises = []
|
||||||
|
|
||||||
|
for (let exercise of exerciseStore.exercises) {
|
||||||
|
if (!exercise.available) {
|
||||||
|
preferencesStore.notAvailableExercises.push(exercise.uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<action-dialog
|
||||||
|
v-model="showDialog"
|
||||||
|
:title="$t('preferences.exercises.edit')"
|
||||||
|
icon="mdi-pencil"
|
||||||
|
width="800"
|
||||||
|
persistent
|
||||||
|
>
|
||||||
|
<v-container>
|
||||||
|
<v-list>
|
||||||
|
<div v-for="exercise in exerciseStore.exercises">
|
||||||
|
<div
|
||||||
|
v-if="exercise.exerciseNr == 1"
|
||||||
|
>
|
||||||
|
<v-divider v-if="exercise.exerciseGroup.groupNr != 0"></v-divider>
|
||||||
|
<v-list-item
|
||||||
|
type="subheader"
|
||||||
|
:title="getExerciseGroupNameLanguage(exercise.exerciseGroup)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<v-checkbox
|
||||||
|
:label="getExerciseNameLanguage(exercise)"
|
||||||
|
v-model="exercise.available"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
</div>
|
||||||
|
</v-list>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<outlined-button color="warning" prepend-icon="mdi-close" @click="showDialog = false">
|
||||||
|
{{ $t('misc.actions.cancel') }}
|
||||||
|
</outlined-button>
|
||||||
|
<outlined-button color="success" prepend-icon="mdi-content-save" @click="saveConfig()">
|
||||||
|
{{ $t("misc.actions.save") }}
|
||||||
|
</outlined-button>
|
||||||
|
</template>
|
||||||
|
</action-dialog>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import cardView from "@/components/basics/cardView.vue";
|
||||||
|
import OutlinedButton from "@/components/basics/outlinedButton.vue";
|
||||||
|
import { ExerciseGroupModel } from "@/data/models/exercises/exerciseGroupModel";
|
||||||
|
import { ExerciseModel } from "@/data/models/exercises/exerciseModel";
|
||||||
|
import { getExerciseGroupNameLanguage } from "@/scripts/languageScripts";
|
||||||
|
import { useExerciseStore } from "@/stores/exercise.store";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import exerciseDialog from "./exerciseDialog.vue";
|
||||||
|
|
||||||
|
const exerciseStore = useExerciseStore();
|
||||||
|
const exerciseGroups = ref<Array<ExerciseGroupModel>>([]);
|
||||||
|
const showExerciseDialog = ref(false);
|
||||||
|
|
||||||
|
exerciseStore.getAllExercises();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract exercise groups from all exercises
|
||||||
|
*/
|
||||||
|
function groupExercises() {
|
||||||
|
exerciseStore.exercises.forEach((exercise) => {
|
||||||
|
if (
|
||||||
|
!exerciseGroups.value.find(
|
||||||
|
(exerciseGroup) => exerciseGroup.id == exercise.exerciseGroup.id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
exerciseGroups.value.push(exercise.exerciseGroup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByExerciseGroup(
|
||||||
|
exercises: Array<ExerciseModel>,
|
||||||
|
group: ExerciseGroupModel
|
||||||
|
) {
|
||||||
|
return exercises.filter((exercise) => exercise.exerciseGroup.id == group.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => exerciseStore.exercises,
|
||||||
|
() => groupExercises()
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<card-view
|
||||||
|
:title="$t('preferences.exercises.settings')"
|
||||||
|
icon="mdi-book-open-blank-variant"
|
||||||
|
>
|
||||||
|
<template #borderless>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
v-for="group in exerciseGroups"
|
||||||
|
:title="getExerciseGroupNameLanguage(group)"
|
||||||
|
:subtitle="
|
||||||
|
$t('preferences.exercises.available', [
|
||||||
|
filterByExerciseGroup(exerciseStore.exercises, group).filter((exercise) => exercise.available)
|
||||||
|
.length,
|
||||||
|
filterByExerciseGroup(exerciseStore.exercises, group).length,
|
||||||
|
])
|
||||||
|
"
|
||||||
|
:prepend-icon="group.icon"
|
||||||
|
/>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<outlined-button prepend-icon="mdi-pencil" @click="showExerciseDialog = true">
|
||||||
|
{{ $t("preferences.exercises.edit") }}
|
||||||
|
</outlined-button>
|
||||||
|
</template>
|
||||||
|
</card-view>
|
||||||
|
|
||||||
|
<exercise-dialog v-model="showExerciseDialog" />
|
||||||
|
</template>
|
||||||
34
src/pages/misc/preferencesPage/importExportSection.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import cardView from '@/components/basics/cardView.vue';
|
||||||
|
import OutlinedButton from "@/components/basics/outlinedButton.vue";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<card-view
|
||||||
|
:title="$t('preferences.importExport.title')"
|
||||||
|
icon="mdi-swap-horizontal-bold"
|
||||||
|
>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-file-input
|
||||||
|
:label="$t('preferences.importExport.selectConfigFile')"
|
||||||
|
variant="outlined"
|
||||||
|
accept=".json"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<outlined-button prepend-icon="mdi-export">
|
||||||
|
{{ $t("preferences.importExport.download") }}
|
||||||
|
</outlined-button>
|
||||||
|
|
||||||
|
<outlined-button prepend-icon="mdi-upload" color="green">
|
||||||
|
{{ $t("preferences.importExport.upload") }}
|
||||||
|
</outlined-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</card-view>
|
||||||
|
</template>
|
||||||
@@ -2,16 +2,29 @@
|
|||||||
import pageSetup from './pageSetupSection.vue';
|
import pageSetup from './pageSetupSection.vue';
|
||||||
import systemSetup from './systemSetupSection.vue';
|
import systemSetup from './systemSetupSection.vue';
|
||||||
import aboutSection from './aboutSection.vue';
|
import aboutSection from './aboutSection.vue';
|
||||||
|
import exerciseSection from './exerciseConfig/exerciseSection.vue';
|
||||||
|
import importExportSection from './importExportSection.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-container max-width="800">
|
<v-container max-width="800">
|
||||||
|
<!-- <v-row>
|
||||||
|
<v-col>
|
||||||
|
<import-export-section />
|
||||||
|
</v-col>
|
||||||
|
</v-row> -->
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<page-setup />
|
<page-setup />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<exercise-section />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<system-setup />
|
<system-setup />
|
||||||
|
|||||||
@@ -1,27 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import cardView from '@/components/basics/cardView.vue';
|
import cardView from "@/components/basics/cardView.vue";
|
||||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
import outlinedButton from "@/components/basics/outlinedButton.vue";
|
||||||
import confirmDialog from '@/components/basics/confirmDialog.vue';
|
import confirmDialog from "@/components/basics/confirmDialog.vue";
|
||||||
import { ServerStateEnum } from '@/data/enums/serverStateEnum';
|
import { ServerStateEnum } from "@/data/enums/serverStateEnum";
|
||||||
import { usePreferencesStore } from '@/stores/preferences.store';
|
import { usePreferencesStore } from "@/stores/preferences.store";
|
||||||
import ServerStateText from '@/components/pageParts/serverStateText.vue';
|
import ServerStateText from "@/components/pageParts/serverStateText.vue";
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const preferenceStore = usePreferencesStore()
|
const preferenceStore = usePreferencesStore();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
preferenceStore.getServerState()
|
preferenceStore.getServerState();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<card-view
|
<card-view :title="$t('preferences.systemSetup')" icon="mdi-engine">
|
||||||
:title="$t('preferences.systemSetup')"
|
|
||||||
icon="mdi-engine"
|
|
||||||
>
|
|
||||||
<template #borderless>
|
<template #borderless>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item class="text-h6 text-center">
|
<v-list-item class="text-h6 text-center">
|
||||||
{{ $t('preferences.serverState') }}: <server-state-text />
|
{{ $t("preferences.serverState") + ":" }} <server-state-text />
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
|
||||||
<v-list-item class="text-center">
|
<v-list-item class="text-center">
|
||||||
@@ -29,9 +26,12 @@ preferenceStore.getServerState()
|
|||||||
@click="preferenceStore.showDeleteDbDialog = true"
|
@click="preferenceStore.showDeleteDbDialog = true"
|
||||||
prepend-icon="mdi-database-refresh"
|
prepend-icon="mdi-database-refresh"
|
||||||
color="warning"
|
color="warning"
|
||||||
:disabled="preferenceStore.serverState != ServerStateEnum.ONLINE || preferenceStore.fetchInProgress"
|
:disabled="
|
||||||
|
preferenceStore.serverState != ServerStateEnum.ONLINE ||
|
||||||
|
preferenceStore.fetchInProgress
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('preferences.resetDatabase.resetDatabase') }}
|
{{ $t("preferences.resetDatabase.resetDatabase") }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
|
||||||
@@ -40,9 +40,12 @@ preferenceStore.getServerState()
|
|||||||
@click="preferenceStore.showDeleteExerciseDialog = true"
|
@click="preferenceStore.showDeleteExerciseDialog = true"
|
||||||
prepend-icon="mdi-progress-close"
|
prepend-icon="mdi-progress-close"
|
||||||
color="warning"
|
color="warning"
|
||||||
:disabled="preferenceStore.serverState != ServerStateEnum.ONLINE || preferenceStore.fetchInProgress"
|
:disabled="
|
||||||
|
preferenceStore.serverState != ServerStateEnum.ONLINE ||
|
||||||
|
preferenceStore.fetchInProgress
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('preferences.resetExerciseProgress.resetExerciseProgress') }}
|
{{ $t("preferences.resetExerciseProgress.resetExerciseProgress") }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
|
||||||
@@ -51,9 +54,12 @@ preferenceStore.getServerState()
|
|||||||
@click="preferenceStore.showFactoryResetDialog = true"
|
@click="preferenceStore.showFactoryResetDialog = true"
|
||||||
prepend-icon="mdi-factory"
|
prepend-icon="mdi-factory"
|
||||||
color="warning"
|
color="warning"
|
||||||
:disabled="preferenceStore.serverState != ServerStateEnum.ONLINE || preferenceStore.fetchInProgress"
|
:disabled="
|
||||||
|
preferenceStore.serverState != ServerStateEnum.ONLINE ||
|
||||||
|
preferenceStore.fetchInProgress
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('preferences.factoryReset.factoryReset') }}
|
{{ $t("preferences.factoryReset.factoryReset") }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
@@ -85,10 +91,12 @@ preferenceStore.getServerState()
|
|||||||
:description="$t('preferences.factoryReset.dialog.description')"
|
:description="$t('preferences.factoryReset.dialog.description')"
|
||||||
v-model="preferenceStore.showFactoryResetDialog"
|
v-model="preferenceStore.showFactoryResetDialog"
|
||||||
icon="mdi-factory"
|
icon="mdi-factory"
|
||||||
:onConfirm="() => {
|
:onConfirm="
|
||||||
preferenceStore.resetToFactorySettings()
|
() => {
|
||||||
router.push('/')
|
preferenceStore.resetToFactorySettings();
|
||||||
}"
|
router.push('/');
|
||||||
|
}
|
||||||
|
"
|
||||||
:loading="preferenceStore.fetchInProgress"
|
:loading="preferenceStore.fetchInProgress"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -16,6 +16,8 @@ import PreferencesPage from "@/pages/misc/preferencesPage/index.vue";
|
|||||||
import HelpPage from "@/pages/misc/helpPage/index.vue"
|
import HelpPage from "@/pages/misc/helpPage/index.vue"
|
||||||
import ErrorPage from "@/pages/misc/errorPage/index.vue"
|
import ErrorPage from "@/pages/misc/errorPage/index.vue"
|
||||||
import ImageLicensePage from "@/pages/misc/imageLicensePage/index.vue"
|
import ImageLicensePage from "@/pages/misc/imageLicensePage/index.vue"
|
||||||
|
import AccountPaymentsPage from "@/pages/account/accountPaymentsPage/index.vue"
|
||||||
|
import AccountAddressesPage from "@/pages/account/accountAddressesPage/index.vue"
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
// Main page
|
// Main page
|
||||||
@@ -32,6 +34,8 @@ const routes = [
|
|||||||
{ path: '/account/orders', component: OrdersPage },
|
{ path: '/account/orders', component: OrdersPage },
|
||||||
{ path: '/account/data', component: AccountDataPage },
|
{ path: '/account/data', component: AccountDataPage },
|
||||||
{ path: '/account/login', component: LoginPage },
|
{ path: '/account/login', component: LoginPage },
|
||||||
|
{ path: '/account/payments', component: AccountPaymentsPage },
|
||||||
|
{ path: '/account/addresses', component: AccountAddressesPage },
|
||||||
|
|
||||||
// Admin
|
// Admin
|
||||||
...adminRoutes,
|
...adminRoutes,
|
||||||
|
|||||||
@@ -20,3 +20,19 @@ export function getSeatColor(surcharge: number, state: number): string {
|
|||||||
case 2: return "orange"
|
case 2: return "orange"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get color of exercise group
|
||||||
|
*
|
||||||
|
* @param exerciseGroupNr Number of exercise group
|
||||||
|
*
|
||||||
|
* @returns Color as string
|
||||||
|
*/
|
||||||
|
export function getExerciseDotColor(exerciseGroupNr: number) {
|
||||||
|
switch(exerciseGroupNr) {
|
||||||
|
case 0: return "purple"
|
||||||
|
case 1: return "orange"
|
||||||
|
case 2: return "blue"
|
||||||
|
case 3: return "pink"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,3 +21,15 @@ export function dateToHumanReadableString(date: Date) {
|
|||||||
export function dateStringToHumanReadableString(string: string) {
|
export function dateStringToHumanReadableString(string: string) {
|
||||||
return dateToHumanReadableString(new Date(string))
|
return dateToHumanReadableString(new Date(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format milliseconds to a readable format
|
||||||
|
*
|
||||||
|
* @param milliseconds Milliseconds to format
|
||||||
|
*
|
||||||
|
* @returns h:mm format
|
||||||
|
*/
|
||||||
|
export function millisecondsToHumanReadableString(milliseconds: number): string {
|
||||||
|
return Math.floor(milliseconds / 1000 / 60 / 60) + ':' +
|
||||||
|
String(Math.floor(milliseconds / 60000)).padStart(2, "0") + ''
|
||||||
|
}
|
||||||
37
src/scripts/languageScripts.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { LanguageEnum } from "@/data/enums/languageEnum";
|
||||||
|
import { ExerciseGroupModel } from "@/data/models/exercises/exerciseGroupModel";
|
||||||
|
import { ExerciseModel } from "@/data/models/exercises/exerciseModel";
|
||||||
|
import { usePreferencesStore } from "@/stores/preferences.store";
|
||||||
|
|
||||||
|
export function getExerciseNameLanguage(exercise: ExerciseModel) {
|
||||||
|
let preferencesStore = usePreferencesStore()
|
||||||
|
|
||||||
|
switch (preferencesStore.language) {
|
||||||
|
case LanguageEnum.GERMAN:
|
||||||
|
return exercise.nameDe;
|
||||||
|
case LanguageEnum.ENGLISH:
|
||||||
|
return exercise.nameEn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getExerciseDescriptionLanguage(exercise: ExerciseModel) {
|
||||||
|
let preferencesStore = usePreferencesStore()
|
||||||
|
|
||||||
|
switch (preferencesStore.language) {
|
||||||
|
case LanguageEnum.GERMAN:
|
||||||
|
return exercise.descriptionDe;
|
||||||
|
case LanguageEnum.ENGLISH:
|
||||||
|
return exercise.descriptionEn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getExerciseGroupNameLanguage(exerciseGroup: ExerciseGroupModel) {
|
||||||
|
let preferencesStore = usePreferencesStore()
|
||||||
|
|
||||||
|
switch (preferencesStore.language) {
|
||||||
|
case LanguageEnum.GERMAN:
|
||||||
|
return exerciseGroup.nameDe;
|
||||||
|
case LanguageEnum.ENGLISH:
|
||||||
|
return exerciseGroup.nameEn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,3 +169,31 @@ export function getIbanRules() {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRegisterNumberRules() {
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
|
||||||
|
return [
|
||||||
|
value => {
|
||||||
|
if (value) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('misc.validation.required')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value => {
|
||||||
|
if (value?.length >= 8) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('misc.validation.notEnoughChars')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value => {
|
||||||
|
if(!isNaN(value) && !isNaN(parseFloat(value))) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('misc.validation.onlyDigitsAllowed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { AccountApiModel } from "../data/models/user/accountApiModel";
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { useExerciseStore } from "./exercise.store";
|
import { useExerciseStore } from "./exercise.store";
|
||||||
|
import moment, { Moment } from "moment";
|
||||||
|
|
||||||
export const useAccountStore = defineStore("accountStore", {
|
export const useAccountStore = defineStore("accountStore", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -17,10 +18,10 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
accounts: ref<Array<AccountApiModel>>([]),
|
accounts: ref<Array<AccountApiModel>>([]),
|
||||||
|
|
||||||
/** Server token of currently logged in account */
|
/** Server token of currently logged in account */
|
||||||
userAccountToken: useLocalStorage("hackmycart/accountStore/userAccountToken", ""),
|
userAccountToken: useLocalStorage("eventMaster/accountStore/userAccountToken", ""),
|
||||||
|
|
||||||
/** Useraccount which is currently logged in */
|
/** Useraccount which is currently logged in */
|
||||||
userAccount: useLocalStorage("hackmycart/accountStore/userAccount", new AccountApiModel()),
|
userAccount: useLocalStorage("eventMaster/accountStore/userAccount", new AccountApiModel()),
|
||||||
|
|
||||||
/** User input on login screen */
|
/** User input on login screen */
|
||||||
loginData: ref<{ username: String, password: String}>(
|
loginData: ref<{ username: String, password: String}>(
|
||||||
@@ -37,7 +38,15 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
adminPanelVisible: ref(false),
|
adminPanelVisible: ref(false),
|
||||||
|
|
||||||
/** Flag to activate buy option on basket page */
|
/** Flag to activate buy option on basket page */
|
||||||
privilegeBuy: ref(false)
|
privilegeBuy: ref(false),
|
||||||
|
|
||||||
|
payment: ref(),
|
||||||
|
|
||||||
|
address: ref(),
|
||||||
|
|
||||||
|
showEditDialog: ref(false),
|
||||||
|
|
||||||
|
loggedInTimeStamp: useLocalStorage<string>("eventMaster/accountStore/loggedInTimeStamp", "")
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@@ -80,6 +89,7 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
await getLogin(this.loginData.username, this.loginData.password)
|
await getLogin(this.loginData.username, this.loginData.password)
|
||||||
.then(async result => {
|
.then(async result => {
|
||||||
this.userAccountToken = result.data.token
|
this.userAccountToken = result.data.token
|
||||||
|
this.loggedInTimeStamp = moment().format("YYYY-MM-DDTHH:mm:ss.SSS")
|
||||||
|
|
||||||
getAccount(this.userAccountToken)
|
getAccount(this.userAccountToken)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@@ -113,14 +123,16 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
* Reload account information about current logged in user
|
* Reload account information about current logged in user
|
||||||
*/
|
*/
|
||||||
async refreshAccount() {
|
async refreshAccount() {
|
||||||
|
this.fetchInProgress = true
|
||||||
|
|
||||||
getAccount(this.userAccountToken)
|
getAccount(this.userAccountToken)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.userAccount = response.data
|
this.userAccount = response.data
|
||||||
|
|
||||||
this.fetchInProgress = false
|
|
||||||
|
|
||||||
this.privilegeBuy = true
|
this.privilegeBuy = true
|
||||||
this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel
|
this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel
|
||||||
|
|
||||||
|
this.fetchInProgress = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -133,6 +145,7 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
async registerAccount(): Promise<boolean> {
|
async registerAccount(): Promise<boolean> {
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore()
|
||||||
const exerciseStore = useExerciseStore()
|
const exerciseStore = useExerciseStore()
|
||||||
|
let success = false
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true
|
||||||
|
|
||||||
if (this.registerData.username == null || this.registerData.username.length < 4) {
|
if (this.registerData.username == null || this.registerData.username.length < 4) {
|
||||||
@@ -142,7 +155,8 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
} else if (!this.registerData.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)) {
|
} else if (!this.registerData.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)) {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTMAILADDRESSUNVALID)
|
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTMAILADDRESSUNVALID)
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
await registerAccount(this.registerData)
|
await registerAccount(this.registerData)
|
||||||
.then(async res => {
|
.then(async res => {
|
||||||
if (res.status == 201) {
|
if (res.status == 201) {
|
||||||
@@ -156,6 +170,7 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false
|
||||||
|
success = true
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.status == 400) {
|
if (error.status == 400) {
|
||||||
@@ -165,12 +180,11 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false
|
||||||
return false
|
return success
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,6 +193,7 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
async updateAccount() {
|
async updateAccount() {
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore()
|
||||||
const exerciseStore = useExerciseStore()
|
const exerciseStore = useExerciseStore()
|
||||||
|
this.fetchInProgress = true
|
||||||
|
|
||||||
// Check for exercise 0.2 completion
|
// Check for exercise 0.2 completion
|
||||||
let accountComplete = this.userAccount.firstName != "" && this.userAccount.lastName != "" &&
|
let accountComplete = this.userAccount.firstName != "" && this.userAccount.lastName != "" &&
|
||||||
@@ -196,6 +211,7 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTUPDATESUCCESSFUL)
|
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTUPDATESUCCESSFUL)
|
||||||
|
|
||||||
this.userAccount = res.data
|
this.userAccount = res.data
|
||||||
|
this.fetchInProgress = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -222,13 +238,40 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
async refreshOrders() {
|
async refreshOrders() {
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true
|
||||||
|
|
||||||
await fetchUserOrders(this.userAccount.id)
|
await fetchUserOrders(this.userAccount.id, this.userAccountToken)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.orders = result.data
|
this.orders = result.data
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
newAddress() {
|
||||||
|
this.address = new AddressModel()
|
||||||
|
this.showEditDialog = true
|
||||||
|
},
|
||||||
|
|
||||||
|
editAddress(address: AddressModel) {
|
||||||
|
this.address = address
|
||||||
|
this.showEditDialog = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveAddress() {
|
||||||
|
this.fetchInProgress = true
|
||||||
|
|
||||||
|
if (this.address.id == undefined) {
|
||||||
|
this.userAccount.addresses.push(this.address)
|
||||||
|
} else {
|
||||||
|
this.userAccount.addresses = this.userAccount.addresses.filter(address => {
|
||||||
|
return address.id != this.address.id
|
||||||
|
})
|
||||||
|
|
||||||
|
this.userAccount.addresses.push(this.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateAccount()
|
||||||
|
this.showEditDialog = false
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an address from the user model
|
* Remove an address from the user model
|
||||||
*
|
*
|
||||||
@@ -238,17 +281,59 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
this.userAccount.addresses = this.userAccount.addresses.filter((addr: AddressModel) =>
|
this.userAccount.addresses = this.userAccount.addresses.filter((addr: AddressModel) =>
|
||||||
addr != address
|
addr != address
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.updateAccount()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an payment from the user model
|
* Add a new payment, opens dialog
|
||||||
|
*/
|
||||||
|
newPayment() {
|
||||||
|
this.payment = new PaymentModel()
|
||||||
|
this.showEditDialog = true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit existing payment, opens dialog
|
||||||
|
*
|
||||||
|
* @param payment Payment dataset to edit
|
||||||
|
*/
|
||||||
|
editPayment(payment: PaymentModel) {
|
||||||
|
this.payment = payment
|
||||||
|
this.showEditDialog = true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save current edited payment
|
||||||
|
*/
|
||||||
|
async savePayment() {
|
||||||
|
this.fetchInProgress = true
|
||||||
|
|
||||||
|
if (this.payment.id == undefined) {
|
||||||
|
this.userAccount.payments.push(this.payment)
|
||||||
|
} else {
|
||||||
|
this.userAccount.payments = this.userAccount.payments.filter(payment => {
|
||||||
|
return payment.id != this.payment.id
|
||||||
|
})
|
||||||
|
|
||||||
|
this.userAccount.payments.push(this.payment)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateAccount()
|
||||||
|
this.showEditDialog = false
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a payment from the user model
|
||||||
*
|
*
|
||||||
* @param address Payment dataset to remove
|
* @param address Payment dataset to remove
|
||||||
*/
|
*/
|
||||||
removePayment(payment: PaymentModel) {
|
async removePayment(payment: PaymentModel) {
|
||||||
this.userAccount.payments = this.userAccount.payments.filter((paym: PaymentModel) =>
|
this.userAccount.payments = await this.userAccount.payments.filter((paym: PaymentModel) =>
|
||||||
paym != payment
|
paym != payment
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.updateAccount()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -259,7 +344,7 @@ export const useAccountStore = defineStore("accountStore", {
|
|||||||
async deleteAccount(account: AccountModel) {
|
async deleteAccount(account: AccountModel) {
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true
|
||||||
|
|
||||||
deleteAccount(account)
|
deleteAccount(account, this.userAccountToken)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ export const useBandStore = defineStore("bandStore", {
|
|||||||
/** All available bands */
|
/** All available bands */
|
||||||
bands: ref<Array<BandApiModel>>([]),
|
bands: ref<Array<BandApiModel>>([]),
|
||||||
|
|
||||||
|
/** Available bands filtered by parameters */
|
||||||
|
filteredBands: ref<Array<BandApiModel>>([]),
|
||||||
|
|
||||||
/** All information about a single band */
|
/** All information about a single band */
|
||||||
band: ref<BandDetailsApiModel>(new BandDetailsApiModel()),
|
band: ref<BandDetailsApiModel>(new BandDetailsApiModel()),
|
||||||
|
|
||||||
@@ -32,7 +35,9 @@ export const useBandStore = defineStore("bandStore", {
|
|||||||
|
|
||||||
await fetchAllBands()
|
await fetchAllBands()
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.bands = result.data.filter((band: BandApiModel) => {
|
this.bands = result.data
|
||||||
|
|
||||||
|
this.filteredBands = result.data.filter((band: BandApiModel) => {
|
||||||
if (genreStore.genre == null) {
|
if (genreStore.genre == null) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useExerciseStore } from "./exercise.store";
|
|||||||
export const useBasketStore = defineStore('basketStore', {
|
export const useBasketStore = defineStore('basketStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
/** Items in customers basket */
|
/** Items in customers basket */
|
||||||
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("hackmycart/basketStore/itemsInBasket", []),
|
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("eventMaster/basketStore/itemsInBasket", []),
|
||||||
|
|
||||||
/** Address used in the order dialog */
|
/** Address used in the order dialog */
|
||||||
usedAddress: ref<AddressModel>(null),
|
usedAddress: ref<AddressModel>(null),
|
||||||
@@ -100,10 +100,10 @@ export const useBasketStore = defineStore('basketStore', {
|
|||||||
const exerciseStore = useExerciseStore()
|
const exerciseStore = useExerciseStore()
|
||||||
|
|
||||||
await createOrder(
|
await createOrder(
|
||||||
accountStore.userAccount.id,
|
|
||||||
this.itemsInBasket,
|
this.itemsInBasket,
|
||||||
this.usedPayment.id,
|
this.usedPayment.id,
|
||||||
this.usedAddress.id
|
this.usedAddress.id,
|
||||||
|
accountStore.userAccountToken
|
||||||
)
|
)
|
||||||
.then(async result => {
|
.then(async result => {
|
||||||
if (result.status == 201) {
|
if (result.status == 201) {
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ export const useConcertStore = defineStore("concertStore", {
|
|||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore()
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true
|
||||||
|
|
||||||
|
console.log("LOcation & Date:")
|
||||||
|
console.log(this.concerts)
|
||||||
|
|
||||||
let id = this.concerts.find((concert: ConcertApiModel) => {
|
let id = this.concerts.find((concert: ConcertApiModel) => {
|
||||||
return (concert.location.urlName == location && concert.date == date)
|
return (concert.location.urlName == location && concert.date == date)
|
||||||
}).id
|
}).id
|
||||||
@@ -75,6 +78,7 @@ export const useConcertStore = defineStore("concertStore", {
|
|||||||
})
|
})
|
||||||
.catch(res => {
|
.catch(res => {
|
||||||
feedbackStore.notFound = true
|
feedbackStore.notFound = true
|
||||||
|
this.fetchInProgress = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,13 +99,5 @@ export const useConcertStore = defineStore("concertStore", {
|
|||||||
this.concert = new ConcertDetailsApiModel()
|
this.concert = new ConcertDetailsApiModel()
|
||||||
this.showEditDialog = true
|
this.showEditDialog = true
|
||||||
},
|
},
|
||||||
|
|
||||||
editConcert(concert: ConcertModel) {
|
|
||||||
// todo
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteConcert(item: ConcertModel) {
|
|
||||||
// todo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -4,6 +4,7 @@ import { ref } from "vue";
|
|||||||
import { useFeedbackStore } from "./feedback.store";
|
import { useFeedbackStore } from "./feedback.store";
|
||||||
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
|
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
|
||||||
import { ExerciseModel } from "@/data/models/exercises/exerciseModel";
|
import { ExerciseModel } from "@/data/models/exercises/exerciseModel";
|
||||||
|
import { usePreferencesStore } from "./preferences.store";
|
||||||
|
|
||||||
export const useExerciseStore = defineStore("exerciseStore", {
|
export const useExerciseStore = defineStore("exerciseStore", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -13,23 +14,45 @@ export const useExerciseStore = defineStore("exerciseStore", {
|
|||||||
/** Request to server sent, waiting for data response */
|
/** Request to server sent, waiting for data response */
|
||||||
fetchInProgress: ref(false),
|
fetchInProgress: ref(false),
|
||||||
|
|
||||||
helpPageVisible: ref(false)
|
exercisePageVisible: ref(false),
|
||||||
|
|
||||||
|
/** All available exercise uuids are stored here */
|
||||||
|
exerciseConfig: ref<Array<string>>(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
* Get all exercises and exercise groups from server
|
* Get all exercises and exercise groups from server
|
||||||
|
*
|
||||||
|
* @param firstLoad True sets all exercises as available, for first load after database was initialised
|
||||||
*/
|
*/
|
||||||
async getAllExercises() {
|
async getAllExercises(firstLoad: boolean = false) {
|
||||||
this.fetchInProgress = true
|
const preferencesStore = usePreferencesStore();
|
||||||
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
await fetchAllExerciseGroups()
|
await fetchAllExerciseGroups().then((result) => {
|
||||||
.then(result => {
|
this.exercises = result.data;
|
||||||
this.exercises = result.data
|
|
||||||
|
|
||||||
this.helpPageVisible = this.getExercise(1, 1).solved
|
if (firstLoad) {
|
||||||
this.fetchInProgress = false
|
preferencesStore.notAvailableExercises = []
|
||||||
})
|
}
|
||||||
|
|
||||||
|
result.data.forEach((exercise) => {
|
||||||
|
if (firstLoad) {
|
||||||
|
exercise.available = true
|
||||||
|
} else {
|
||||||
|
exercise.available =
|
||||||
|
preferencesStore.notAvailableExercises.find(
|
||||||
|
(availableExercise: string) => {
|
||||||
|
return availableExercise == exercise.uuid;
|
||||||
|
}
|
||||||
|
) == undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.exercisePageVisible = this.getExercise(1, 1).solved;
|
||||||
|
this.fetchInProgress = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,8 +65,11 @@ export const useExerciseStore = defineStore("exerciseStore", {
|
|||||||
*/
|
*/
|
||||||
getExercise(exerciseGroupNr: number, exerciseNr: number): ExerciseModel {
|
getExercise(exerciseGroupNr: number, exerciseNr: number): ExerciseModel {
|
||||||
return this.exercises.find((exercise: ExerciseModel) => {
|
return this.exercises.find((exercise: ExerciseModel) => {
|
||||||
return exercise.exerciseNr == exerciseNr && exercise.exerciseGroup.groupNr == exerciseGroupNr
|
return (
|
||||||
})
|
exercise.exerciseNr == exerciseNr &&
|
||||||
|
exercise.exerciseGroup.groupNr == exerciseGroupNr
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,65 +80,89 @@ export const useExerciseStore = defineStore("exerciseStore", {
|
|||||||
*/
|
*/
|
||||||
async solveExercise(exerciseGroupNr: number, exerciseNr: number) {
|
async solveExercise(exerciseGroupNr: number, exerciseNr: number) {
|
||||||
// Request all exercises from server
|
// Request all exercises from server
|
||||||
await this.getAllExercises()
|
await this.getAllExercises();
|
||||||
|
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore();
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
// Change only if the exercise is not solved
|
// Change only if the exercise is not solved
|
||||||
updateExercise(exerciseGroupNr, exerciseNr, true)
|
updateExercise(exerciseGroupNr, exerciseNr, true).then((result) => {
|
||||||
.then(result => {
|
|
||||||
if (result.data.changed) {
|
if (result.data.changed) {
|
||||||
|
let bannerState = BannerStateEnum.ERROR;
|
||||||
|
|
||||||
let bannerState = BannerStateEnum.ERROR
|
switch (exerciseGroupNr) {
|
||||||
|
|
||||||
switch(exerciseGroupNr) {
|
|
||||||
case 0: {
|
case 0: {
|
||||||
switch(exerciseNr) {
|
switch (exerciseNr) {
|
||||||
case 1: bannerState = BannerStateEnum.EXERCISESOLVED01; break;
|
case 1:
|
||||||
case 2: bannerState = BannerStateEnum.EXERCISESOLVED02; break;
|
bannerState = BannerStateEnum.EXERCISESOLVED01;
|
||||||
case 3: bannerState = BannerStateEnum.EXERCISESOLVED03; break;
|
break;
|
||||||
|
case 2:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED02;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED03;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 1: {
|
case 1: {
|
||||||
switch(exerciseNr) {
|
switch (exerciseNr) {
|
||||||
case 1: bannerState = BannerStateEnum.EXERCISESOLVED11; break;
|
case 1:
|
||||||
case 2: bannerState = BannerStateEnum.EXERCISESOLVED12; break;
|
bannerState = BannerStateEnum.EXERCISESOLVED11;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED12;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 2: {
|
case 2: {
|
||||||
switch(exerciseNr) {
|
switch (exerciseNr) {
|
||||||
case 1: bannerState = BannerStateEnum.EXERCISESOLVED21; break;
|
case 1:
|
||||||
case 2: bannerState = BannerStateEnum.EXERCISESOLVED22; break;
|
bannerState = BannerStateEnum.EXERCISESOLVED21;
|
||||||
case 3: bannerState = BannerStateEnum.EXERCISESOLVED23; break;
|
break;
|
||||||
case 4: bannerState = BannerStateEnum.EXERCISESOLVED24; break;
|
case 2:
|
||||||
case 5: bannerState = BannerStateEnum.EXERCISESOLVED25; break;
|
bannerState = BannerStateEnum.EXERCISESOLVED22;
|
||||||
case 6: bannerState = BannerStateEnum.EXERCISESOLVED26; break;
|
break;
|
||||||
|
case 3:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED23;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED24;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED25;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED26;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 3: {
|
case 3: {
|
||||||
switch(exerciseNr) {
|
switch (exerciseNr) {
|
||||||
case 1: bannerState = BannerStateEnum.EXERCISESOLVED31; break;
|
case 1:
|
||||||
case 2: bannerState = BannerStateEnum.EXERCISESOLVED32; break;
|
bannerState = BannerStateEnum.EXERCISESOLVED31;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
bannerState = BannerStateEnum.EXERCISESOLVED32;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
feedbackStore.addSnackbar(bannerState)
|
feedbackStore.addSnackbar(bannerState);
|
||||||
this.getAllExercises()
|
this.getAllExercises();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { deleteGenre, fetchAllGenres, patchGenre, postGenre } from "@/data/api/genreApi";
|
import {
|
||||||
|
deleteGenre,
|
||||||
|
fetchAllGenres,
|
||||||
|
patchGenre,
|
||||||
|
postGenre,
|
||||||
|
} from "@/data/api/genreApi";
|
||||||
import { GenreApiModel } from "@/data/models/acts/genreApiModel";
|
import { GenreApiModel } from "@/data/models/acts/genreApiModel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
@@ -10,6 +15,8 @@ export const useGenreStore = defineStore("genreStore", {
|
|||||||
/** All available genres from server */
|
/** All available genres from server */
|
||||||
genres: ref<Array<GenreApiModel>>([]),
|
genres: ref<Array<GenreApiModel>>([]),
|
||||||
|
|
||||||
|
topGenres: ref<Array<GenreApiModel>>([]),
|
||||||
|
|
||||||
/** Currently selected genre */
|
/** Currently selected genre */
|
||||||
genre: ref<GenreApiModel>(null),
|
genre: ref<GenreApiModel>(null),
|
||||||
|
|
||||||
@@ -17,7 +24,7 @@ export const useGenreStore = defineStore("genreStore", {
|
|||||||
showEditDialog: ref(false),
|
showEditDialog: ref(false),
|
||||||
|
|
||||||
/** Request to server sent, waiting for data response */
|
/** Request to server sent, waiting for data response */
|
||||||
fetchInProgress: ref(false)
|
fetchInProgress: ref(false),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@@ -25,21 +32,29 @@ export const useGenreStore = defineStore("genreStore", {
|
|||||||
* Get all genres from the database
|
* Get all genres from the database
|
||||||
*/
|
*/
|
||||||
getGenres() {
|
getGenres() {
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
fetchAllGenres()
|
fetchAllGenres().then((response) => {
|
||||||
.then(response => {
|
this.genres = response.data;
|
||||||
this.genres = response.data
|
|
||||||
this.fetchInProgress = false
|
let genresByNumberOfBands = this.genres.slice();
|
||||||
})
|
|
||||||
|
genresByNumberOfBands.sort((a, b) => {
|
||||||
|
return b.bands.length - a.bands.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.topGenres = genresByNumberOfBands.splice(0, 8)
|
||||||
|
|
||||||
|
this.fetchInProgress = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare edit dialog for new genre, opens it
|
* Prepare edit dialog for new genre, opens it
|
||||||
*/
|
*/
|
||||||
newGenre() {
|
newGenre() {
|
||||||
this.genre = new GenreApiModel()
|
this.genre = new GenreApiModel();
|
||||||
this.showEditDialog = true
|
this.showEditDialog = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,41 +63,39 @@ export const useGenreStore = defineStore("genreStore", {
|
|||||||
* @param genre Selected Genre object
|
* @param genre Selected Genre object
|
||||||
*/
|
*/
|
||||||
editGenre(genre: GenreApiModel) {
|
editGenre(genre: GenreApiModel) {
|
||||||
this.genre = genre
|
this.genre = genre;
|
||||||
this.showEditDialog = true
|
this.showEditDialog = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save edited genre to the backend server
|
* Save edited genre to the backend server
|
||||||
*/
|
*/
|
||||||
saveGenre() {
|
saveGenre() {
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore();
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
if (this.genre.id == undefined) {
|
if (this.genre.id == undefined) {
|
||||||
// Creating new Genre
|
// Creating new Genre
|
||||||
postGenre(this.genre)
|
postGenre(this.genre).then((response) => {
|
||||||
.then(response => {
|
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDSUCCESSFUL)
|
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDSUCCESSFUL);
|
||||||
this.getGenres()
|
this.getGenres();
|
||||||
this.showEditDialog = false
|
this.showEditDialog = false;
|
||||||
} else {
|
} else {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDERROR)
|
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDERROR);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
// Update existing Genre
|
// Update existing Genre
|
||||||
patchGenre(this.genre)
|
patchGenre(this.genre).then((response) => {
|
||||||
.then(response => {
|
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDSUCCESSFUL)
|
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDSUCCESSFUL);
|
||||||
this.getGenres()
|
this.getGenres();
|
||||||
this.showEditDialog = false
|
this.showEditDialog = false;
|
||||||
} else {
|
} else {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDERROR)
|
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDERROR);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -92,31 +105,30 @@ export const useGenreStore = defineStore("genreStore", {
|
|||||||
* @param genre Genre to delete
|
* @param genre Genre to delete
|
||||||
*/
|
*/
|
||||||
deleteGenre(genre: GenreApiModel) {
|
deleteGenre(genre: GenreApiModel) {
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore();
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
deleteGenre(genre)
|
deleteGenre(genre).then((response) => {
|
||||||
.then(response => {
|
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.GENREDELETESUCCESSFUL)
|
feedbackStore.addSnackbar(BannerStateEnum.GENREDELETESUCCESSFUL);
|
||||||
this.getGenres()
|
this.getGenres();
|
||||||
} else {
|
} else {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.GENREDELETEERROR)
|
feedbackStore.addSnackbar(BannerStateEnum.GENREDELETEERROR);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setGenreByName(name: string) {
|
setGenreByName(name: string) {
|
||||||
this.genre = null
|
this.genre = null;
|
||||||
name = name.replace("+", " ")
|
name = name.replace("+", " ");
|
||||||
|
|
||||||
let newGenre = this.genres.find(genre => {
|
let newGenre = this.genres.find((genre) => {
|
||||||
return genre.name == name
|
return genre.name == name;
|
||||||
})
|
});
|
||||||
|
|
||||||
if (newGenre != undefined) {
|
if (newGenre != undefined) {
|
||||||
this.genre = newGenre
|
this.genre = newGenre;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ export const useOrderStore = defineStore("orderStore", {
|
|||||||
*
|
*
|
||||||
* @param user User to request orders from
|
* @param user User to request orders from
|
||||||
*/
|
*/
|
||||||
async getOrdersOfAccount(user: AccountModel) {
|
async getOrdersOfAccount(user: AccountModel, token: string) {
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true
|
||||||
|
|
||||||
fetchUserOrders(user.id)
|
fetchUserOrders(user.id, token)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.orders = res.data
|
this.orders = res.data
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import { useLocalStorage } from "@vueuse/core";
|
|||||||
import { ThemeEnum } from "../data/enums/themeEnums";
|
import { ThemeEnum } from "../data/enums/themeEnums";
|
||||||
import { LanguageEnum } from "../data/enums/languageEnum";
|
import { LanguageEnum } from "../data/enums/languageEnum";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { fetchServerState,resetDatabase, resetExerciseProgress } from "@/data/api/mainApi";
|
import {
|
||||||
|
fetchServerState,
|
||||||
|
resetDatabase,
|
||||||
|
resetExerciseProgress,
|
||||||
|
} from "@/data/api/mainApi";
|
||||||
import { ServerStateEnum } from "@/data/enums/serverStateEnum";
|
import { ServerStateEnum } from "@/data/enums/serverStateEnum";
|
||||||
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
|
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
|
||||||
import { useFeedbackStore } from "./feedback.store";
|
import { useFeedbackStore } from "./feedback.store";
|
||||||
@@ -12,13 +16,19 @@ import { useExerciseStore } from "./exercise.store";
|
|||||||
import { useAccountStore } from "./account.store";
|
import { useAccountStore } from "./account.store";
|
||||||
import { AccountApiModel } from "@/data/models/user/accountApiModel";
|
import { AccountApiModel } from "@/data/models/user/accountApiModel";
|
||||||
|
|
||||||
export const usePreferencesStore = defineStore('preferencesStore', {
|
export const usePreferencesStore = defineStore("preferencesStore", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
/** Selected theme by user */
|
/** Selected theme by user */
|
||||||
theme: useLocalStorage<ThemeEnum>("hackmycart/preferencesStore/theme", ThemeEnum.DARK),
|
theme: useLocalStorage<ThemeEnum>(
|
||||||
|
"eventMaster/preferencesStore/theme",
|
||||||
|
ThemeEnum.DARK
|
||||||
|
),
|
||||||
|
|
||||||
/** Selected language by user */
|
/** Selected language by user */
|
||||||
language: useLocalStorage<LanguageEnum>("hackmycart/preferencesStore/language", LanguageEnum.GERMAN),
|
language: useLocalStorage<LanguageEnum>(
|
||||||
|
"eventMaster/preferencesStore/language",
|
||||||
|
LanguageEnum.GERMAN
|
||||||
|
),
|
||||||
|
|
||||||
/** Request to server sent, waiting for data response */
|
/** Request to server sent, waiting for data response */
|
||||||
fetchInProgress: ref(false),
|
fetchInProgress: ref(false),
|
||||||
@@ -36,13 +46,27 @@ export const usePreferencesStore = defineStore('preferencesStore', {
|
|||||||
showFactoryResetDialog: ref(false),
|
showFactoryResetDialog: ref(false),
|
||||||
|
|
||||||
/** Marks the first run of the app */
|
/** Marks the first run of the app */
|
||||||
firstStartup: useLocalStorage<Boolean>("hackmycart/preferencesStore/firstStartup", true),
|
firstStartup: useLocalStorage<Boolean>(
|
||||||
|
"eventMaster/preferencesStore/firstStartup",
|
||||||
|
true
|
||||||
|
),
|
||||||
|
|
||||||
/** Full name of student */
|
/** Full name of student */
|
||||||
studentName: useLocalStorage<string>("hackmycart/preferencesStore/studentName", ""),
|
studentName: useLocalStorage<string>(
|
||||||
|
"eventMaster/preferencesStore/studentName",
|
||||||
|
""
|
||||||
|
),
|
||||||
|
|
||||||
/** Matrikel number */
|
/** Matrikel number */
|
||||||
registrationNumber: useLocalStorage<string>("hackmycart/preferencesStore/registrationNumber", "")
|
registrationNumber: useLocalStorage<string>(
|
||||||
|
"eventMaster/preferencesStore/registrationNumber",
|
||||||
|
""
|
||||||
|
),
|
||||||
|
|
||||||
|
notAvailableExercises: useLocalStorage<Array<string>>(
|
||||||
|
"eventMaster/preferencesStore/notAvailableExercises",
|
||||||
|
[]
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@@ -50,92 +74,90 @@ export const usePreferencesStore = defineStore('preferencesStore', {
|
|||||||
* Request the state of the backend server
|
* Request the state of the backend server
|
||||||
*/
|
*/
|
||||||
async getServerState() {
|
async getServerState() {
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
fetchServerState()
|
fetchServerState()
|
||||||
.then(result => {
|
.then((result) => {
|
||||||
if (result.status == 200) {
|
if (result.status == 200) {
|
||||||
this.serverState = ServerStateEnum.ONLINE
|
this.serverState = ServerStateEnum.ONLINE;
|
||||||
} else {
|
} else {
|
||||||
this.serverState = ServerStateEnum.OFFLINE
|
this.serverState = ServerStateEnum.OFFLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false;
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
this.serverState = ServerStateEnum.OFFLINE
|
|
||||||
this.fetchInProgress = false
|
|
||||||
})
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.serverState = ServerStateEnum.OFFLINE;
|
||||||
|
this.fetchInProgress = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the database (without exercise tables)
|
* Resets the database (without exercise tables)
|
||||||
*/
|
*/
|
||||||
async resetDb() {
|
async resetDb() {
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore();
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore();
|
||||||
|
|
||||||
this.serverState = ServerStateEnum.PENDING
|
this.serverState = ServerStateEnum.PENDING;
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
// Logout user
|
// Logout user
|
||||||
accountStore.logout()
|
accountStore.logout();
|
||||||
|
|
||||||
await resetDatabase()
|
await resetDatabase().then((result) => {
|
||||||
.then(result => {
|
|
||||||
if (result.status == 200) {
|
if (result.status == 200) {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.DATABASERESETSUCCESSFUL)
|
feedbackStore.addSnackbar(BannerStateEnum.DATABASERESETSUCCESSFUL);
|
||||||
this.serverState = ServerStateEnum.ONLINE
|
this.serverState = ServerStateEnum.ONLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false;
|
||||||
this.showDeleteDbDialog = false
|
this.showDeleteDbDialog = false;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the exercise progress
|
* Resets the exercise progress
|
||||||
*/
|
*/
|
||||||
async resetExerciseProg() {
|
async resetExerciseProg() {
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore();
|
||||||
const exerciseStore = useExerciseStore()
|
const exerciseStore = useExerciseStore();
|
||||||
|
|
||||||
this.serverState = ServerStateEnum.PENDING
|
this.serverState = ServerStateEnum.PENDING;
|
||||||
this.fetchInProgress = true
|
this.fetchInProgress = true;
|
||||||
|
|
||||||
await resetExerciseProgress()
|
await resetExerciseProgress().then((result) => {
|
||||||
.then(result => {
|
|
||||||
if (result.status == 200) {
|
if (result.status == 200) {
|
||||||
feedbackStore.addSnackbar(BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL)
|
feedbackStore.addSnackbar(
|
||||||
this.serverState = ServerStateEnum.ONLINE
|
BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL
|
||||||
|
);
|
||||||
|
this.serverState = ServerStateEnum.ONLINE;
|
||||||
|
|
||||||
exerciseStore.getAllExercises()
|
exerciseStore.getAllExercises(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchInProgress = false
|
this.fetchInProgress = false;
|
||||||
this.showDeleteExerciseDialog = false
|
this.showDeleteExerciseDialog = false;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset all store values to factory state
|
* Reset all store values to factory state
|
||||||
*/
|
*/
|
||||||
resetToFactorySettings() {
|
resetToFactorySettings() {
|
||||||
const basketStore = useBasketStore()
|
const basketStore = useBasketStore();
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore();
|
||||||
|
|
||||||
this.firstStartup = true
|
this.firstStartup = true;
|
||||||
this.studentName = ""
|
this.studentName = "";
|
||||||
this.registrationNumber = ""
|
this.registrationNumber = "";
|
||||||
this.theme = "dark"
|
this.theme = "dark";
|
||||||
this.language = LanguageEnum.GERMAN
|
this.language = LanguageEnum.GERMAN;
|
||||||
basketStore.itemsInBasket = []
|
basketStore.itemsInBasket = [];
|
||||||
accountStore.userAccountToken = ""
|
accountStore.userAccountToken = "";
|
||||||
accountStore.userAccount = new AccountApiModel()
|
accountStore.userAccount = new AccountApiModel();
|
||||||
|
|
||||||
|
this.showFactoryResetDialog = false;
|
||||||
|
},
|
||||||
this.showFactoryResetDialog = false
|
},
|
||||||
}
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const useSearchStore = defineStore("searchStore", {
|
|||||||
|
|
||||||
|
|
||||||
// Check for exercise solution
|
// Check for exercise solution
|
||||||
if (result.data.length != 0) {
|
if (this.searchTerm.toUpperCase().includes("SELECT")) {
|
||||||
// Exercise 2.1
|
// Exercise 2.1
|
||||||
if (result.data[0].type != undefined && result.data[0].type == "table") {
|
if (result.data[0].type != undefined && result.data[0].type == "table") {
|
||||||
exerciseStore.solveExercise(2, 1)
|
exerciseStore.solveExercise(2, 1)
|
||||||
@@ -68,7 +68,7 @@ export const useSearchStore = defineStore("searchStore", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exercise 2.4
|
// Exercise 2.4
|
||||||
else if (this.searchTerm.includes("UPDATE")) {
|
else if (this.searchTerm.toUpperCase().includes("UPDATE")) {
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
await accountStore.refreshAccount()
|
await accountStore.refreshAccount()
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ export const useSearchStore = defineStore("searchStore", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exercise 2.6
|
// Exercise 2.6
|
||||||
else if (this.searchTerm.includes("DELETE")) {
|
else if (this.searchTerm.toUpperCase().includes("DELETE")) {
|
||||||
const bandStore = useBandStore()
|
const bandStore = useBandStore()
|
||||||
await bandStore.getBand("muse")
|
await bandStore.getBand("muse")
|
||||||
|
|
||||||
@@ -92,6 +92,7 @@ export const useSearchStore = defineStore("searchStore", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
// Normal behaviour
|
||||||
this.bands = result.data
|
this.bands = result.data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||