Implementing Exercise system in database with API and frontend visualization
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<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">
|
<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="WevClHWmhzPAQ7FDN5po">
|
<diagram name="Page-1" id="WevClHWmhzPAQ7FDN5po">
|
||||||
<mxGraphModel dx="3157" dy="1300" 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">
|
<mxGraphModel dx="3021" dy="1221" 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>
|
<root>
|
||||||
<mxCell id="0" />
|
<mxCell id="0" />
|
||||||
<mxCell id="1" parent="0" />
|
<mxCell id="1" parent="0" />
|
||||||
@@ -856,12 +856,74 @@
|
|||||||
<mxCell id="7DIVNgt3cFn7LyBarME5-9" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">image: String</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
|
<mxCell id="7DIVNgt3cFn7LyBarME5-9" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">image: String</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" parent="1" vertex="1">
|
||||||
<mxGeometry x="-399.99999999999994" y="320" width="160" height="30" as="geometry" />
|
<mxGeometry x="-399.99999999999994" y="320" width="160" height="30" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="ixBWz6MtEORAIzOqTLXs-1" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;fillColor=#6a00ff;strokeColor=#3700CC;" edge="1" parent="1">
|
<mxCell id="ixBWz6MtEORAIzOqTLXs-1" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;fillColor=#6a00ff;strokeColor=#3700CC;" parent="1" edge="1">
|
||||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
<mxPoint x="-561.2099999999999" y="110" as="sourcePoint" />
|
<mxPoint x="-561.2099999999999" y="110" as="sourcePoint" />
|
||||||
<mxPoint x="-561.2099999999999" y="180" as="targetPoint" />
|
<mxPoint x="-561.2099999999999" y="180" as="targetPoint" />
|
||||||
</mxGeometry>
|
</mxGeometry>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-1" value="<blockquote style="margin: 0px; border: none; padding: 0px;"><b><u>Exercises</u></b></blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=center;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="760" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-2" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">name: String</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="820" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-3" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;"><u>id:&nbsp;</u><u style="background-color: initial;">Number</u></blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="790" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-4" value="<blockquote style="margin: 0px; border: none; padding: 0px;"><b><u>ExerciseGroups</u></b></blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=center;fillColor=#d80073;strokeColor=#A50040;fontColor=#ffffff;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="560" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-5" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;"><u>id:&nbsp;</u><u style="background-color: initial;">Number</u></blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="590" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-6" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">name: String</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="620" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-7" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">descriptionDe: String</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="850" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-8" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">descriptionEn: String</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="880" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-9" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">solved: Boolean</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="910" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-10" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">exerciseGroupId: Number</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="940" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-11" value="" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.security.key_permissions;fillColor=#CCCCCC;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-780" y="945" width="9.43" height="20" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-12" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="4QbvcL21_BjxR5MsDnpr-10" target="4QbvcL21_BjxR5MsDnpr-5">
|
||||||
|
<mxGeometry x="389.35999999999996" y="350" as="geometry">
|
||||||
|
<mxPoint x="-619" y="864.6600000000001" as="sourcePoint" />
|
||||||
|
<mxPoint x="-700" y="864.6600000000001" as="targetPoint" />
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-720" y="955" />
|
||||||
|
<mxPoint x="-720" y="605" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-13" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" connectable="0" vertex="1" parent="4QbvcL21_BjxR5MsDnpr-12">
|
||||||
|
<mxGeometry x="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="3" y="-28" as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-14" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" connectable="0" vertex="1" parent="4QbvcL21_BjxR5MsDnpr-12">
|
||||||
|
<mxGeometry x="1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="16" y="-30" as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-15" value="" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.security.key_permissions;fillColor=#2072B8;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-780.0000000000001" y="795" width="9.43" height="20" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-16" value="" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.security.key_permissions;fillColor=#2072B8;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-780.0000000000001" y="595" width="9.43" height="20" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4QbvcL21_BjxR5MsDnpr-17" value="<blockquote style="margin: 0px 0px 0px 8px; border: none; padding: 0px;">groupNr: Number</blockquote>" style="rounded=0;whiteSpace=wrap;html=1;align=left;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-920" y="650" width="160" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
</root>
|
</root>
|
||||||
</mxGraphModel>
|
</mxGraphModel>
|
||||||
</diagram>
|
</diagram>
|
||||||
|
|||||||
54
software/backend/data/exercises.json
Normal file
54
software/backend/data/exercises.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"nameDe": "Den Shop kennenlernen",
|
||||||
|
"nameEn": "Getting to know the shop",
|
||||||
|
"groupNr": 0,
|
||||||
|
"exercises": [
|
||||||
|
{
|
||||||
|
"nameDe": "Registrieren",
|
||||||
|
"nameEn": "Register",
|
||||||
|
"exerciseNr": 1,
|
||||||
|
"descriptionDe": "Erstelle einen neuen Account im Online Shop",
|
||||||
|
"descriptionEn": "Create a new account in the online shop",
|
||||||
|
"solved": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nameDe": "Ein Ticket kaufen",
|
||||||
|
"nameEn": "Buy a ticket",
|
||||||
|
"exerciseNr": 2,
|
||||||
|
"descriptionDe": "Suche dir ein Event deiner Wahl und kaufe dafür ein Ticket",
|
||||||
|
"descriptionEn": "Search for an event of choice and buy a ticket for",
|
||||||
|
"solved": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nameDe": "SQL Injections",
|
||||||
|
"nameEn": "SQL Injections",
|
||||||
|
"groupNr": 1,
|
||||||
|
"exercises": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nameDe": "Broken Access Control",
|
||||||
|
"nameEn": "Broken Access Control",
|
||||||
|
"groupNr": 2,
|
||||||
|
"exercises": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nameDe": "Cross-Site Scripting (XSS)",
|
||||||
|
"nameEn": "Cross-Site Scripting (XSS)",
|
||||||
|
"groupNr": 3,
|
||||||
|
"exercises": [
|
||||||
|
{
|
||||||
|
"nameDe": "Hallo Welt!",
|
||||||
|
"nameEn": "Hello World!",
|
||||||
|
"exerciseNr": 1,
|
||||||
|
"descriptionDe": "Nimm dir eine URL des Shops und erweitere sie mit JavaScript Code so, dass beim Öffnen des Links eine 'Hallo Welt' Nachricht erscheint",
|
||||||
|
"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",
|
||||||
|
"solved": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
{
|
{
|
||||||
"concertId": 0,
|
"concertId": 0,
|
||||||
"orderPrice": 184,
|
"orderPrice": 184,
|
||||||
"seatId": 43
|
"seatGroup": "A",
|
||||||
|
"seatRow": 0,
|
||||||
|
"seat": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -24,12 +26,16 @@
|
|||||||
{
|
{
|
||||||
"concertId": 0,
|
"concertId": 0,
|
||||||
"orderPrice": 184,
|
"orderPrice": 184,
|
||||||
"seatId": 2
|
"seatGroup": "A",
|
||||||
|
"seatRow": 0,
|
||||||
|
"seat": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"concertId": 0,
|
"concertId": 0,
|
||||||
"orderPrice": 184,
|
"orderPrice": 184,
|
||||||
"seatId": 3
|
"seatGroup": "A",
|
||||||
|
"seatRow": 0,
|
||||||
|
"seat": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +49,9 @@
|
|||||||
{
|
{
|
||||||
"concertId": 0,
|
"concertId": 0,
|
||||||
"orderPrice": 184,
|
"orderPrice": 184,
|
||||||
"seatId": 3
|
"seatGroup": "A",
|
||||||
|
"seatRow": 0,
|
||||||
|
"seat": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import { BandGenre } from "./models/acts/bandGenre.model"
|
|||||||
import { Seat } from "./models/locations/seat.model"
|
import { Seat } from "./models/locations/seat.model"
|
||||||
import { SeatGroup } from "./models/locations/seatGroup.model"
|
import { SeatGroup } from "./models/locations/seatGroup.model"
|
||||||
import { SeatRow } from "./models/locations/seatRow.model"
|
import { SeatRow } from "./models/locations/seatRow.model"
|
||||||
|
import { Exercise } from "./models/exercises/exercise.model"
|
||||||
|
import { ExerciseGroup } from "./models/exercises/exerciseGroup.model"
|
||||||
|
|
||||||
const dbName = "database"
|
const dbName = "database"
|
||||||
const dbUser = "root"
|
const dbUser = "root"
|
||||||
@@ -36,19 +38,20 @@ export const sequelize = new Sequelize({
|
|||||||
AccountRole, Account, Payment, Address,
|
AccountRole, Account, Payment, Address,
|
||||||
City, Location, SeatGroup, SeatRow, Seat,
|
City, Location, SeatGroup, SeatRow, Seat,
|
||||||
Genre, Band, BandGenre, Rating, Member, Event, Concert,
|
Genre, Band, BandGenre, Rating, Member, Event, Concert,
|
||||||
Order, Ticket
|
Order, Ticket,
|
||||||
|
Exercise, ExerciseGroup
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
export function startDatabase() {
|
export function startDatabase() {
|
||||||
let force = false
|
let recreateDb = false
|
||||||
|
|
||||||
// Create database and tables
|
// Create database and tables
|
||||||
sequelize.sync({ force: force })
|
sequelize.sync({ force: recreateDb })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Database & tables created!")
|
console.log("Database & tables created!")
|
||||||
|
|
||||||
if (force) {
|
if (recreateDb) {
|
||||||
prepopulateDatabase()
|
prepopulateDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
software/backend/models/exercises/exercise.model.ts
Normal file
33
software/backend/models/exercises/exercise.model.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { BelongsTo, Column, ForeignKey, Model, Table } from "sequelize-typescript";
|
||||||
|
import { ExerciseGroup } from "./exerciseGroup.model";
|
||||||
|
|
||||||
|
@Table({ timestamps: false })
|
||||||
|
export class Exercise extends Model {
|
||||||
|
@Column
|
||||||
|
nameDe: String
|
||||||
|
|
||||||
|
@Column
|
||||||
|
nameEn: String
|
||||||
|
|
||||||
|
@Column
|
||||||
|
exerciseNr: Number
|
||||||
|
|
||||||
|
@Column
|
||||||
|
descriptionDe: String
|
||||||
|
|
||||||
|
@Column
|
||||||
|
descriptionEn: String
|
||||||
|
|
||||||
|
@Column
|
||||||
|
solved: Boolean
|
||||||
|
|
||||||
|
@ForeignKey(() => ExerciseGroup)
|
||||||
|
@Column
|
||||||
|
exerciseGroupId: Number
|
||||||
|
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
|
||||||
|
@BelongsTo(() => ExerciseGroup)
|
||||||
|
exerciseGroup: ExerciseGroup
|
||||||
|
}
|
||||||
20
software/backend/models/exercises/exerciseGroup.model.ts
Normal file
20
software/backend/models/exercises/exerciseGroup.model.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Column, HasMany, Model, Table } from "sequelize-typescript";
|
||||||
|
import { Exercise } from "./exercise.model";
|
||||||
|
|
||||||
|
@Table({ timestamps: false })
|
||||||
|
export class ExerciseGroup extends Model {
|
||||||
|
@Column
|
||||||
|
nameDe: String
|
||||||
|
|
||||||
|
@Column
|
||||||
|
nameEn: String
|
||||||
|
|
||||||
|
@Column
|
||||||
|
groupNr: Number
|
||||||
|
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
|
||||||
|
@HasMany(() => Exercise)
|
||||||
|
exercises: Exercise[]
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { validateString } from "../scripts/validateHelper";
|
|||||||
import { Address } from "../models/user/address.model";
|
import { Address } from "../models/user/address.model";
|
||||||
import { Payment } from "../models/user/payment.model";
|
import { Payment } from "../models/user/payment.model";
|
||||||
import { AccountRole } from "../models/user/accountRole.model";
|
import { AccountRole } from "../models/user/accountRole.model";
|
||||||
|
import { Exercise } from "../models/exercises/exercise.model";
|
||||||
|
|
||||||
export const account = Router()
|
export const account = Router()
|
||||||
|
|
||||||
@@ -65,16 +66,26 @@ account.post("/", (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// Create account
|
// Create account
|
||||||
Account.create(req.body)
|
Account.create(req.body)
|
||||||
.then(account => {
|
.then(account => {
|
||||||
// Status: 201 Created
|
// Status: 201 Created
|
||||||
res.status(201).json(account)
|
res.status(201).json(account)
|
||||||
}).catch(reason => {
|
|
||||||
// Status: 409 Conflict
|
// Check exercise in table
|
||||||
res.status(409).json({
|
Exercise.update(
|
||||||
code: 409,
|
{ solved: true },
|
||||||
message: "Username already in use"
|
{
|
||||||
|
where: {
|
||||||
|
nameEn: "Register"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}).catch(reason => {
|
||||||
|
// Status: 409 Conflict
|
||||||
|
res.status(409).json({
|
||||||
|
code: 409,
|
||||||
|
message: "Username already in use"
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
account.patch("/", (req: Request, res: Response) => {
|
account.patch("/", (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ api.get("/", (req: Request, res: Response, next: NextFunction) => {
|
|||||||
res.status(200).send()
|
res.status(200).send()
|
||||||
})
|
})
|
||||||
|
|
||||||
api.get("/resetdatabase", (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
|
||||||
deleteAllTables()
|
deleteAllTables()
|
||||||
|
|
||||||
// Step 2: Prepopulate with default values
|
// Step 2: Prepopulate with default values
|
||||||
prepopulateDatabase()
|
await prepopulateDatabase()
|
||||||
|
|
||||||
// Step 3: Send status back
|
// Step 3: Send status back
|
||||||
res.status(200).send()
|
res.status(200).send()
|
||||||
|
|||||||
23
software/backend/routes/exercise.routes.ts
Normal file
23
software/backend/routes/exercise.routes.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Exercise } from "../models/exercises/exercise.model";
|
||||||
|
import { ExerciseGroup } from "../models/exercises/exerciseGroup.model";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
|
export const exercises = Router()
|
||||||
|
|
||||||
|
exercises.get("/", (req: Request, res: Response) => {
|
||||||
|
ExerciseGroup.findAll(
|
||||||
|
{
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Exercise,
|
||||||
|
separate: true,
|
||||||
|
order: [
|
||||||
|
[ 'id', 'ASC' ]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
).then(result => {
|
||||||
|
res.status(200).json(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -16,6 +16,8 @@ import { BandGenre } from '../models/acts/bandGenre.model'
|
|||||||
import { SeatGroup } from '../models/locations/seatGroup.model'
|
import { SeatGroup } from '../models/locations/seatGroup.model'
|
||||||
import { Seat } from '../models/locations/seat.model'
|
import { Seat } from '../models/locations/seat.model'
|
||||||
import { SeatRow } from '../models/locations/seatRow.model'
|
import { SeatRow } from '../models/locations/seatRow.model'
|
||||||
|
import { Exercise } from '../models/exercises/exercise.model'
|
||||||
|
import { ExerciseGroup } from '../models/exercises/exerciseGroup.model'
|
||||||
|
|
||||||
import accounts from "./../data/accounts.json"
|
import accounts from "./../data/accounts.json"
|
||||||
import orders from "./../data/orders.json"
|
import orders from "./../data/orders.json"
|
||||||
@@ -24,6 +26,7 @@ import bands from "./../data/bands.json"
|
|||||||
import genres from "./../data/genres.json"
|
import genres from "./../data/genres.json"
|
||||||
import events from "./../data/events.json"
|
import events from "./../data/events.json"
|
||||||
import cities from "./../data/cities.json"
|
import cities from "./../data/cities.json"
|
||||||
|
import exercises from "./../data/exercises.json"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,6 +52,9 @@ export function deleteAllTables() {
|
|||||||
Payment.destroy({ truncate: true })
|
Payment.destroy({ truncate: true })
|
||||||
Account.destroy({ truncate: true })
|
Account.destroy({ truncate: true })
|
||||||
AccountRole.destroy({ truncate: true})
|
AccountRole.destroy({ truncate: true})
|
||||||
|
|
||||||
|
Exercise.destroy({truncate: true})
|
||||||
|
ExerciseGroup.destroy({truncate: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,10 +150,10 @@ export async function prepopulateDatabase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (let tour of events.data) {
|
for (let event of events.data) {
|
||||||
await Event.create(tour)
|
await Event.create(event)
|
||||||
.then(async dataset => {
|
.then(async dataset => {
|
||||||
for (let concert of tour.concerts) {
|
for (let concert of event.concerts) {
|
||||||
concert["eventId"] = dataset.id
|
concert["eventId"] = dataset.id
|
||||||
|
|
||||||
await Concert.create(concert)
|
await Concert.create(concert)
|
||||||
@@ -161,7 +167,42 @@ export async function prepopulateDatabase() {
|
|||||||
for (let ticket of order.tickets) {
|
for (let ticket of order.tickets) {
|
||||||
ticket["orderId"] = dataset.id
|
ticket["orderId"] = dataset.id
|
||||||
|
|
||||||
await Ticket.create(ticket)
|
SeatGroup.findOne({
|
||||||
|
where: {
|
||||||
|
name: ticket.seatGroup
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(seatGroup => {
|
||||||
|
SeatRow.findOne({
|
||||||
|
where: {
|
||||||
|
seatGroupId: seatGroup.id,
|
||||||
|
row: ticket.seatRow
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(seatRow => {
|
||||||
|
Seat.findOne({
|
||||||
|
where: {
|
||||||
|
seatRowId: seatRow.id,
|
||||||
|
seatNr: ticket.seat
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async seat => {
|
||||||
|
ticket["seatId"] = seat.id
|
||||||
|
await Ticket.create(ticket)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let exerciseGroup of exercises.data) {
|
||||||
|
ExerciseGroup.create(exerciseGroup)
|
||||||
|
.then(async dataset => {
|
||||||
|
for (let exercise of exerciseGroup.exercises) {
|
||||||
|
exercise["exerciseGroupId"] = dataset.id
|
||||||
|
|
||||||
|
await Exercise.create(exercise)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { genre } from './routes/genre.routes'
|
|||||||
import { location } from './routes/location.routes'
|
import { location } from './routes/location.routes'
|
||||||
import { events } from './routes/events.routes'
|
import { events } from './routes/events.routes'
|
||||||
import { city } from './routes/city.routes'
|
import { city } from './routes/city.routes'
|
||||||
|
import { exercises } from './routes/exercise.routes'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const port = 3000
|
const port = 3000
|
||||||
@@ -43,6 +44,7 @@ app.use("/orders", order)
|
|||||||
app.use("/accounts", account)
|
app.use("/accounts", account)
|
||||||
app.use("/cities", city)
|
app.use("/cities", city)
|
||||||
app.use("/concerts", concert)
|
app.use("/concerts", concert)
|
||||||
|
app.use("/exercises", exercises)
|
||||||
|
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
|
|||||||
@@ -5,20 +5,28 @@ defineProps({
|
|||||||
icon: {
|
icon: {
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
subtitle: {
|
loading: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card
|
<v-card variant="tonal" >
|
||||||
variant="tonal"
|
<v-card-title v-if="title || loading" color="primary" class="pa-0">
|
||||||
:prepend-icon="icon"
|
<v-sheet color="primary" class="pl-2 py-1">
|
||||||
:title="title"
|
<v-skeleton-loader
|
||||||
:subtitle="subtitle"
|
type="heading"
|
||||||
>
|
:loading="loading"
|
||||||
<v-container class="pt-0">
|
style="background-color: transparent"
|
||||||
|
>
|
||||||
|
<v-icon :icon="icon" v-if="icon" /> {{ title }}
|
||||||
|
</v-skeleton-loader>
|
||||||
|
</v-sheet>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col >
|
<v-col >
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
7
software/src/data/api/exerciseApi.ts
Normal file
7
software/src/data/api/exerciseApi.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
const BASE_URL = "http://localhost:3000/exercises"
|
||||||
|
|
||||||
|
export async function getAllExerciseGroups() {
|
||||||
|
return await axios.get(BASE_URL)
|
||||||
|
}
|
||||||
8
software/src/data/models/exercises/exerciseGroupModel.ts
Normal file
8
software/src/data/models/exercises/exerciseGroupModel.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { ExerciseModel } from "./exerciseModel"
|
||||||
|
|
||||||
|
export class ExerciseGroupModel {
|
||||||
|
nameDe: string = ""
|
||||||
|
nameEn: string = ""
|
||||||
|
groupNr: number = 0
|
||||||
|
exercises: Array<ExerciseModel>
|
||||||
|
}
|
||||||
8
software/src/data/models/exercises/exerciseModel.ts
Normal file
8
software/src/data/models/exercises/exerciseModel.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export class ExerciseModel {
|
||||||
|
nameDe: string = ""
|
||||||
|
nameEn: string = ""
|
||||||
|
exerciseNr: number = 0
|
||||||
|
descriptionDe: string = ""
|
||||||
|
descriptionEn: string = ""
|
||||||
|
solved: boolean = false
|
||||||
|
}
|
||||||
@@ -159,5 +159,7 @@
|
|||||||
"seatGroup": "Kategorie",
|
"seatGroup": "Kategorie",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"price": "Preis",
|
"price": "Preis",
|
||||||
"standingArea": "Stehbereich"
|
"standingArea": "Stehbereich",
|
||||||
|
"exerciseGroup": "Aufgabengruppe",
|
||||||
|
"exercise": "Aufgabe"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,5 +159,7 @@
|
|||||||
"seatGroup": "Category",
|
"seatGroup": "Category",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"price": "Price",
|
"price": "Price",
|
||||||
"standingArea": "Standing Area"
|
"standingArea": "Standing Area",
|
||||||
|
"exerciseGroup": "Exercise group",
|
||||||
|
"exercise": "Exercise"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ function itemPropsGenre(genre: GenreModel) {
|
|||||||
<card-view
|
<card-view
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
:title="$t('filtering')"
|
:title="$t('filtering')"
|
||||||
subtitle="123"
|
|
||||||
icon="mdi-cog"
|
icon="mdi-cog"
|
||||||
>
|
>
|
||||||
<v-row class="d-flex justify-center" >
|
<v-row class="d-flex justify-center" >
|
||||||
|
|||||||
@@ -1,49 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { getAllExerciseGroups } from '@/data/api/exerciseApi';
|
||||||
import scoreCard from './scoreCard.vue';
|
import scoreCard from './scoreCard.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { ExerciseGroupModel } from '@/data/models/exercises/exerciseGroupModel';
|
||||||
|
import { useFeedbackStore } from '@/data/stores/feedbackStore';
|
||||||
|
|
||||||
|
const exerciseGroups = ref<Array<ExerciseGroupModel>>([])
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
|
||||||
|
feedbackStore.fetchDataFromServerInProgress = true
|
||||||
|
|
||||||
|
getAllExerciseGroups()
|
||||||
|
.then(result => {
|
||||||
|
exerciseGroups.value = result.data
|
||||||
|
feedbackStore.fetchDataFromServerInProgress = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-container max-width="1000">
|
<v-container max-width="1000">
|
||||||
<v-row>
|
<v-row v-if="feedbackStore.fetchDataFromServerInProgress" v-for="i in 3">
|
||||||
<v-col>
|
<v-col>
|
||||||
<score-card
|
<score-card :loading="true"
|
||||||
:title="$t('scoreBoard.exerciseGroup0')"
|
/>
|
||||||
:progress="2"
|
|
||||||
:total-steps="2"
|
|
||||||
:step-names="['Registrieren', 'Bestellung ausführen']"
|
|
||||||
/>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-row>
|
<v-row v-for="exerciseGroup in exerciseGroups">
|
||||||
<v-col>
|
<v-col>
|
||||||
<score-card
|
<score-card
|
||||||
:title="$t('scoreBoard.exerciseGroup1')"
|
:exercise-group="exerciseGroup"
|
||||||
:progress="1"
|
|
||||||
:total-steps="4"
|
|
||||||
:step-names="['', '']"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<score-card
|
|
||||||
:title="$t('scoreBoard.exerciseGroup2')"
|
|
||||||
:progress="1"
|
|
||||||
:total-steps="4"
|
|
||||||
:step-names="['', '']"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<score-card
|
|
||||||
:title="$t('scoreBoard.exerciseGroup3')"
|
|
||||||
:progress="0"
|
|
||||||
:total-steps="3"
|
|
||||||
:step-names="['', '', '']"
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|||||||
@@ -1,46 +1,83 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import cardView from '@/components/basics/cardView.vue';
|
import cardView from '@/components/basics/cardView.vue';
|
||||||
|
import { ExerciseGroupModel } from '@/data/models/exercises/exerciseGroupModel';
|
||||||
|
|
||||||
const props = defineProps({
|
defineProps({
|
||||||
exerciseGroup: String,
|
exerciseGroup: ExerciseGroupModel,
|
||||||
progress: Number,
|
loading: Boolean
|
||||||
totalSteps: Number,
|
|
||||||
stepNames: Array<String>
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function getDotColor(step: number) {
|
|
||||||
if (props.progress >= step) {
|
|
||||||
return "green"
|
|
||||||
} else {
|
|
||||||
return "grey"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIcon(step: number) {
|
|
||||||
if (props.progress >= step) {
|
|
||||||
return "mdi-check"
|
|
||||||
} else {
|
|
||||||
return "mdi-pencil"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<card-view :title="exerciseGroup" >
|
<card-view v-if="loading" :loading="loading" >
|
||||||
<v-timeline
|
<v-timeline
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
side="end"
|
side="start"
|
||||||
class="pb-3"
|
class="pb-3"
|
||||||
>
|
>
|
||||||
<v-timeline-item
|
<v-timeline-item
|
||||||
v-for="step in totalSteps"
|
v-for="i in 3"
|
||||||
:dot-color="getDotColor(step)"
|
dot-color="grey"
|
||||||
:icon="getIcon(step)"
|
icon="mdi-pencil"
|
||||||
>
|
>
|
||||||
{{ stepNames[step - 1] }}
|
<v-skeleton-loader
|
||||||
|
type="list-item"
|
||||||
|
:loading="loading"
|
||||||
|
width="200"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<template #opposite>
|
||||||
|
<v-skeleton-loader
|
||||||
|
type="sentences"
|
||||||
|
:loading="loading"
|
||||||
|
width="200"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-timeline-item>
|
||||||
|
</v-timeline>
|
||||||
|
</card-view>
|
||||||
|
|
||||||
|
|
||||||
|
<card-view
|
||||||
|
v-else
|
||||||
|
:title="$t('exerciseGroup') + ' ' + exerciseGroup.groupNr + ': ' + exerciseGroup.nameDe"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<v-timeline
|
||||||
|
direction="horizontal"
|
||||||
|
side="start"
|
||||||
|
class="pb-3"
|
||||||
|
>
|
||||||
|
<v-timeline-item
|
||||||
|
v-for="exercise in exerciseGroup.exercises"
|
||||||
|
:dot-color="exercise.solved ? 'green' : 'grey'"
|
||||||
|
:icon="exercise.solved ? 'mdi-check' : 'mdi-pencil'"
|
||||||
|
>
|
||||||
|
<v-skeleton-loader
|
||||||
|
type="text"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<div class="text-h6">
|
||||||
|
{{ $t('exercise') }} {{ exercise.exerciseNr }}
|
||||||
|
</div>
|
||||||
|
</v-skeleton-loader>
|
||||||
|
|
||||||
|
|
||||||
<template #opposite>
|
<template #opposite>
|
||||||
{{ $t('scoreBoard.exercise', [step]) }}
|
<v-skeleton-loader
|
||||||
|
type="text"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-h6">
|
||||||
|
{{ exercise.nameDe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ exercise.descriptionDe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-skeleton-loader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</v-timeline-item>
|
</v-timeline-item>
|
||||||
|
|||||||
Reference in New Issue
Block a user