From f81e9be3204aec2c965f728959d455b3e581e0c5 Mon Sep 17 00:00:00 2001 From: TobiZog Date: Mon, 7 Oct 2024 13:15:16 +0200 Subject: [PATCH] Implementing Exercise system in database with API and frontend visualization --- misc/database.drawio | 66 ++++++++++++- software/backend/data/exercises.json | 54 +++++++++++ software/backend/data/orders.json | 16 +++- software/backend/database.ts | 11 ++- .../models/exercises/exercise.model.ts | 33 +++++++ .../models/exercises/exerciseGroup.model.ts | 20 ++++ software/backend/routes/account.routes.ts | 29 ++++-- software/backend/routes/api.routes.ts | 4 +- software/backend/routes/exercise.routes.ts | 23 +++++ software/backend/scripts/databaseHelper.ts | 49 +++++++++- software/backend/server.ts | 2 + software/src/components/basics/cardView.vue | 26 ++++-- software/src/data/api/exerciseApi.ts | 7 ++ .../models/exercises/exerciseGroupModel.ts | 8 ++ .../data/models/exercises/exerciseModel.ts | 8 ++ software/src/locales/de.json | 4 +- software/src/locales/en.json | 4 +- .../src/pages/events/eventsPage/filterBar.vue | 1 - software/src/pages/system/helpPage/index.vue | 54 ++++------- .../src/pages/system/helpPage/scoreCard.vue | 93 +++++++++++++------ 20 files changed, 413 insertions(+), 99 deletions(-) create mode 100644 software/backend/data/exercises.json create mode 100644 software/backend/models/exercises/exercise.model.ts create mode 100644 software/backend/models/exercises/exerciseGroup.model.ts create mode 100644 software/backend/routes/exercise.routes.ts create mode 100644 software/src/data/api/exerciseApi.ts create mode 100644 software/src/data/models/exercises/exerciseGroupModel.ts create mode 100644 software/src/data/models/exercises/exerciseModel.ts diff --git a/misc/database.drawio b/misc/database.drawio index 23307db..68b03a9 100644 --- a/misc/database.drawio +++ b/misc/database.drawio @@ -1,6 +1,6 @@ - + @@ -856,12 +856,74 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/software/backend/data/exercises.json b/software/backend/data/exercises.json new file mode 100644 index 0000000..b59659d --- /dev/null +++ b/software/backend/data/exercises.json @@ -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 + } + ] + } + ] +} \ No newline at end of file diff --git a/software/backend/data/orders.json b/software/backend/data/orders.json index 5a20fa4..c4653ba 100644 --- a/software/backend/data/orders.json +++ b/software/backend/data/orders.json @@ -10,7 +10,9 @@ { "concertId": 0, "orderPrice": 184, - "seatId": 43 + "seatGroup": "A", + "seatRow": 0, + "seat": 1 } ] }, @@ -24,12 +26,16 @@ { "concertId": 0, "orderPrice": 184, - "seatId": 2 + "seatGroup": "A", + "seatRow": 0, + "seat": 2 }, { "concertId": 0, "orderPrice": 184, - "seatId": 3 + "seatGroup": "A", + "seatRow": 0, + "seat": 3 } ] }, @@ -43,7 +49,9 @@ { "concertId": 0, "orderPrice": 184, - "seatId": 3 + "seatGroup": "A", + "seatRow": 0, + "seat": 4 } ] } diff --git a/software/backend/database.ts b/software/backend/database.ts index cc5b00a..97cd745 100644 --- a/software/backend/database.ts +++ b/software/backend/database.ts @@ -20,6 +20,8 @@ import { BandGenre } from "./models/acts/bandGenre.model" import { Seat } from "./models/locations/seat.model" import { SeatGroup } from "./models/locations/seatGroup.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 dbUser = "root" @@ -36,19 +38,20 @@ export const sequelize = new Sequelize({ AccountRole, Account, Payment, Address, City, Location, SeatGroup, SeatRow, Seat, Genre, Band, BandGenre, Rating, Member, Event, Concert, - Order, Ticket + Order, Ticket, + Exercise, ExerciseGroup ] }) export function startDatabase() { - let force = false + let recreateDb = false // Create database and tables - sequelize.sync({ force: force }) + sequelize.sync({ force: recreateDb }) .then(() => { console.log("Database & tables created!") - if (force) { + if (recreateDb) { prepopulateDatabase() } diff --git a/software/backend/models/exercises/exercise.model.ts b/software/backend/models/exercises/exercise.model.ts new file mode 100644 index 0000000..b779dea --- /dev/null +++ b/software/backend/models/exercises/exercise.model.ts @@ -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 +} \ No newline at end of file diff --git a/software/backend/models/exercises/exerciseGroup.model.ts b/software/backend/models/exercises/exerciseGroup.model.ts new file mode 100644 index 0000000..70af2d1 --- /dev/null +++ b/software/backend/models/exercises/exerciseGroup.model.ts @@ -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[] +} \ No newline at end of file diff --git a/software/backend/routes/account.routes.ts b/software/backend/routes/account.routes.ts index c79bf7b..0bac911 100644 --- a/software/backend/routes/account.routes.ts +++ b/software/backend/routes/account.routes.ts @@ -4,6 +4,7 @@ import { validateString } from "../scripts/validateHelper"; import { Address } from "../models/user/address.model"; import { Payment } from "../models/user/payment.model"; import { AccountRole } from "../models/user/accountRole.model"; +import { Exercise } from "../models/exercises/exercise.model"; export const account = Router() @@ -65,16 +66,26 @@ account.post("/", (req: Request, res: Response) => { // Create account Account.create(req.body) - .then(account => { - // Status: 201 Created - res.status(201).json(account) - }).catch(reason => { - // Status: 409 Conflict - res.status(409).json({ - code: 409, - message: "Username already in use" + .then(account => { + // Status: 201 Created + res.status(201).json(account) + + // Check exercise in table + Exercise.update( + { solved: true }, + { + 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) => { diff --git a/software/backend/routes/api.routes.ts b/software/backend/routes/api.routes.ts index 3259210..39ae630 100644 --- a/software/backend/routes/api.routes.ts +++ b/software/backend/routes/api.routes.ts @@ -7,12 +7,12 @@ api.get("/", (req: Request, res: Response, next: NextFunction) => { 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 deleteAllTables() // Step 2: Prepopulate with default values - prepopulateDatabase() + await prepopulateDatabase() // Step 3: Send status back res.status(200).send() diff --git a/software/backend/routes/exercise.routes.ts b/software/backend/routes/exercise.routes.ts new file mode 100644 index 0000000..32aa8bb --- /dev/null +++ b/software/backend/routes/exercise.routes.ts @@ -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) + }) +}) \ No newline at end of file diff --git a/software/backend/scripts/databaseHelper.ts b/software/backend/scripts/databaseHelper.ts index 64286e6..7dad791 100644 --- a/software/backend/scripts/databaseHelper.ts +++ b/software/backend/scripts/databaseHelper.ts @@ -16,6 +16,8 @@ import { BandGenre } from '../models/acts/bandGenre.model' import { SeatGroup } from '../models/locations/seatGroup.model' import { Seat } from '../models/locations/seat.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 orders from "./../data/orders.json" @@ -24,6 +26,7 @@ import bands from "./../data/bands.json" import genres from "./../data/genres.json" import events from "./../data/events.json" import cities from "./../data/cities.json" +import exercises from "./../data/exercises.json" /** @@ -49,6 +52,9 @@ export function deleteAllTables() { Payment.destroy({ truncate: true }) Account.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) { - await Event.create(tour) + for (let event of events.data) { + await Event.create(event) .then(async dataset => { - for (let concert of tour.concerts) { + for (let concert of event.concerts) { concert["eventId"] = dataset.id await Concert.create(concert) @@ -161,7 +167,42 @@ export async function prepopulateDatabase() { for (let ticket of order.tickets) { 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) } }) } diff --git a/software/backend/server.ts b/software/backend/server.ts index d0b3bdc..ea8b63a 100644 --- a/software/backend/server.ts +++ b/software/backend/server.ts @@ -11,6 +11,7 @@ import { genre } from './routes/genre.routes' import { location } from './routes/location.routes' import { events } from './routes/events.routes' import { city } from './routes/city.routes' +import { exercises } from './routes/exercise.routes' const app = express() const port = 3000 @@ -43,6 +44,7 @@ app.use("/orders", order) app.use("/accounts", account) app.use("/cities", city) app.use("/concerts", concert) +app.use("/exercises", exercises) // Start server diff --git a/software/src/components/basics/cardView.vue b/software/src/components/basics/cardView.vue index 45c57e2..b910c92 100644 --- a/software/src/components/basics/cardView.vue +++ b/software/src/components/basics/cardView.vue @@ -5,20 +5,28 @@ defineProps({ icon: { type: String }, - subtitle: { - type: String, + loading: { + type: Boolean, + default: false } })