Move software files one directory up, Readme

This commit is contained in:
2024-11-19 16:51:28 +01:00
parent baf763c4cb
commit 1dc5740f03
329 changed files with 255 additions and 31 deletions

236
src/stores/account.store.ts Normal file
View File

@@ -0,0 +1,236 @@
import { useLocalStorage } from "@vueuse/core";
import { AccountModel } from "../data/models/user/accountModel";
import { useFeedbackStore } from "./feedback.store";
import { deleteAccount, fetchAllAccounts, getAccount, login, registerAccount, updateAccount } from "../data/api/accountApi";
import { fetchUserOrders } from "../data/api/orderApi";
import { BannerStateEnum } from "../data/enums/bannerStateEnum";
import { AddressModel } from "../data/models/user/addressModel";
import { PaymentModel } from "../data/models/user/paymentModel";
import { AccountApiModel } from "../data/models/user/accountApiModel";
import { ref } from "vue";
import { defineStore } from "pinia";
import { useExerciseStore } from "./exercise.store";
export const useAccountStore = defineStore("accountStore", {
state: () => ({
/** All accounts */
accounts: ref<Array<AccountApiModel>>([]),
/** Useraccount which is currently logged in */
userAccountToken: useLocalStorage("hackmycart/accountStore/userAccountToken", ""),
userAccount: useLocalStorage("hackmycart/accountStore/userAccount", new AccountApiModel()),
/** User input on login screen */
loginData: ref<{ username: String, password: String}>(
{ username: "", password: "" }
),
/** Buffer for register data */
registerData: ref<AccountModel>(new AccountModel()),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
adminPanelVisible: ref(false),
privilegeBuy: ref(false)
}),
actions: {
async getAllAccounts() {
this.fetchInProgress = true
fetchAllAccounts()
.then(response => {
this.accounts = response.data
this.fetchInProgress = false
})
},
/**
* Start the login process
*
* @returns True on success
*/
async login(): Promise<boolean> {
const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore()
this.fetchInProgress = true
// Validate
if (this.loginData.username == null || this.loginData.username.length == 0 ||
this.loginData.password == null || this.loginData.password.length == 0
) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTLOGINWRONGLOGIN)
this.fetchInProgress = false
return false
}
else
{
await login(this.loginData.username, this.loginData.password)
.then(async result => {
this.userAccountToken = result.data.token
getAccount(this.userAccountToken)
.then(response => {
this.userAccount = response.data
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTLOGINSUCCESSFUL)
this.fetchInProgress = false
this.privilegeBuy = true
this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel
if (response.data.accountRoleId == 3) {
exerciseStore.solveExercise(2, 4)
}
})
})
.catch(error => {
if (error.status == 400) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTLOGINERROR)
} else if (error.status == 401) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTLOGINWRONGLOGIN)
}
this.fetchInProgress = false
return false
})
}
},
/**
* Register a new account to the database
* Log in on success
*
* @returns True on success
*/
async registerAccount(): Promise<boolean> {
const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore()
this.fetchInProgress = true
await registerAccount(this.registerData)
.then(async res => {
if (res.status == 201) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTREGISTERSUCCESSFUL)
exerciseStore.solveExercise(0, 1)
}
this.loginData = {
username: this.registerData.username,
password: this.registerData.password
}
this.fetchInProgress = false
})
.catch((error) => {
if (error.status == 400) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTREGISTERERROR)
} else if (error.status == 409) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE)
}
this.fetchInProgress = false
return false
})
return false
},
/**
* Update values of an existing account on server
*/
async updateAccount() {
const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore()
// Check for exercise 0.2 completion
let accountComplete = this.userAccount.firstName != "" && this.userAccount.lastName != "" &&
this.userAccount.addresses.length != 0 && this.userAccount.payments.length != 0
if (accountComplete) {
exerciseStore.solveExercise(0, 2)
}
// Update in backend
await updateAccount(this.userAccount, this.userAccountToken)
.then(res => {
if (res.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTUPDATESUCCESSFUL)
this.userAccount = res.data
}
})
},
/**
* Logout user
*/
logout() {
const feedbackStore = useFeedbackStore()
this.userAccount = new AccountModel()
this.userAccountId = -1
this.loggedIn = false
this.privilegeBuy = false
this.adminPanelVisible = false
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL)
},
/**
* Get all orders from current user
*/
async refreshOrders() {
this.fetchInProgress = true
await fetchUserOrders(this.userAccount.id)
.then(result => {
this.orders = result.data
this.fetchInProgress = false
})
},
async getAdresses() {
},
/**
* Remove an address from the user model
*
* @param address Address dataset to remove
*/
removeAddress(address: AddressModel) {
this.userAccount.addresses = this.userAccount.addresses.filter((addr: AddressModel) =>
addr != address
)
},
/**
* Remove an payment from the user model
*
* @param address Payment dataset to remove
*/
removePayment(payment: PaymentModel) {
this.userAccount.payments = this.userAccount.payments.filter((paym: PaymentModel) =>
paym != payment
)
},
editAccount(item: AccountModel) {
// todo
},
async deleteAccount(account: AccountModel) {
this.fetchInProgress = true
deleteAccount(account)
.then(response => {
this.fetchInProgress = false
})
}
}
})

137
src/stores/band.store.ts Normal file
View File

@@ -0,0 +1,137 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { BandApiModel } from "../data/models/acts/bandApiModel";
import { fetchAllBands, fetchBandByName, patchBand, postBand } from "../data/api/bandApi";
import { BandDetailsApiModel } from "../data/models/acts/bandDetailsApiModel";
import { useFeedbackStore } from "./feedback.store";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
import { useGenreStore } from "./genre.store";
export const useBandStore = defineStore("bandStore", {
state: () => ({
/** All available bands */
bands: ref<Array<BandApiModel>>([]),
/** All information about a single band */
band: ref<BandDetailsApiModel>(new BandDetailsApiModel()),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
/** Show or hide the edit dialog for edit a band */
showEditDialog: ref(false)
}),
actions: {
/**
* Get all bands from server
*/
async getBands() {
const genreStore = useGenreStore()
this.fetchInProgress = true
await fetchAllBands()
.then(result => {
this.bands = result.data.filter((band: BandApiModel) => {
if (genreStore.filteredGenres.length == 0) {
return true
}
for (let bandGenre of band.genres) {
for (let filteredGenres of genreStore.filteredGenres) {
if (bandGenre.name == filteredGenres.name) {
return true
}
}
}
return false
})
})
this.fetchInProgress = false
},
/**
* Get all available data about a specific band
*
* @param name Name of band
*/
async getBand(name: string) {
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
await fetchBandByName(name)
.then(result => {
this.band = result.data
this.fetchInProgress = false
})
.catch(res => {
feedbackStore.notFound = true
})
},
/**
* Prepare edit dialog for new band, opens it
*/
newBand() {
this.band = new BandDetailsApiModel()
this.showEditDialog = true
},
/**
* Edit a band. Fetch all information about the band, opens the edit dialog
*
* @param name Name of band to edit
*/
async editBand(name: string) {
await this.getBand(name)
this.showEditDialog = true
},
/**
* Save band in this store to the database
*/
saveBand() {
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
if (this.band.id == undefined) {
postBand(this.band)
.then(result => {
if (result.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.BANDSAVEDSUCCESSFUL)
this.getBands()
this.showEditDialog = false
} else {
feedbackStore.addSnackbar(BannerStateEnum.BANDSAVEDERROR)
}
})
} else {
patchBand(this.band)
.then(result => {
if (result.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.BANDSAVEDSUCCESSFUL)
this.getBands()
this.showEditDialog = false
} else {
feedbackStore.addSnackbar(BannerStateEnum.BANDSAVEDERROR)
}
})
}
},
/**
* Delete a band by it's identifier
*
* @param id Id of the band in the database
*/
deleteBand(id: number) {
// todo
}
}
})

133
src/stores/basket.store.ts Normal file
View File

@@ -0,0 +1,133 @@
import { defineStore } from "pinia";
import { useLocalStorage } from "@vueuse/core";
import { BasketItemModel } from "../data/models/ordering/basketItemModel";
import { useFeedbackStore } from "./feedback.store";
import { BannerStateEnum } from "../data/enums/bannerStateEnum";
import { AddressModel } from "../data/models/user/addressModel";
import { PaymentModel } from "../data/models/user/paymentModel";
import { ref } from "vue";
import { SelectedSeatModel } from "../data/models/ordering/selectedSeatModel";
import { calcPrice } from "@/scripts/concertScripts";
import { BandModel } from "../data/models/acts/bandModel";
import { useAccountStore } from "./account.store";
import { createOrder } from "@/data/api/orderApi";
import { useExerciseStore } from "./exercise.store";
export const useBasketStore = defineStore('basketStore', {
state: () => ({
/** Items in customers basket */
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("hackmycart/basketStore/itemsInBasket", []),
/** Address used in the order dialog */
usedAddress: ref(new AddressModel()),
/** Payment method used in the order dialog */
usedPayment: ref(new PaymentModel()),
/** Selected seats in the booking page */
selectedSeats: ref<Array<SelectedSeatModel>>([])
}),
getters: {
/**
* Calculate price of all items in the basket with discount
*
* @returns Total price of basket
*/
getTotalPrice() {
let result = 0
for (let item of this.itemsInBasket) {
result += calcPrice(item.price, item.seats.length)
}
return Math.round(result * 100) / 100
}
},
actions: {
/**
* Remove an item from the basket
*
* @param item Item to remove
*/
removeItemFromBasket(item: BasketItemModel) {
const feedbackStore = useFeedbackStore()
feedbackStore.addSnackbar(BannerStateEnum.BASKETPRODUCTREMOVED)
this.itemsInBasket = this.itemsInBasket.filter((basketItemModel: BasketItemModel) =>
basketItemModel.concert.id != item.concert.id
)
},
/**
* Move all selected seats from selectedSeats to itemsInBasket variable
*
* @param band Band of the concert
*/
moveSeatSelectionsToBasket(band: BandModel) {
for (let selectedSeat of this.selectedSeats) {
let itemInBasket: BasketItemModel =
this.itemsInBasket.find((basketItem: BasketItemModel) =>
{
return basketItem.concert.id == selectedSeat.concert.id
})
if (itemInBasket != undefined) {
itemInBasket.seats.push(selectedSeat.seat)
} else {
this.itemsInBasket.push(
new BasketItemModel(
selectedSeat.concert,
band,
selectedSeat.seat,
selectedSeat.concert.price
)
)
}
}
this.selectedSeats = []
},
/**
* Take an order to the server. Sends all articles in the basket and
* creates an order entry in the backend database
*/
async takeOrder() {
const accountStore = useAccountStore()
const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore()
await createOrder(
accountStore.userAccount.id,
this.itemsInBasket,
this.usedPayment.id,
this.usedAddress.id
)
.then(async result => {
if (result.status == 201) {
await accountStore.refreshOrders()
feedbackStore.addSnackbar(BannerStateEnum.ORDERPLACESUCCESSFUL)
// Exercise 0.3 is solved
exerciseStore.solveExercise(0, 3)
console.log(this.itemsInBasket)
for (let item of this.itemsInBasket) {
if (!item.concert.offered) {
exerciseStore.solveExercise(1, 2)
feedbackStore.addSnackbar(BannerStateEnum.EXERCISESOLVED12)
}
}
this.itemsInBasket = []
} else {
feedbackStore.addSnackbar(BannerStateEnum.ERROR)
}
})
}
}
})

107
src/stores/concert.store.ts Normal file
View File

@@ -0,0 +1,107 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { ConcertApiModel } from "../data/models/acts/concertApiModel";
import { fetchConcertById, fetchAllConcerts, fetchUpcomingConcerts } from "../data/api/concertApi";
import { ConcertDetailsApiModel } from "../data/models/acts/concertDetailsApiModel";
import { CityModel } from "@/data/models/locations/cityModel";
import { ConcertModel } from "@/data/models/acts/concertModel";
import { useFeedbackStore } from "./feedback.store";
export const useConcertStore = defineStore("concertStore", {
state: () => ({
/** All available concerts */
concerts: ref<Array<ConcertApiModel>>([]),
/** Next upcoming concerts */
upcomingConcerts: ref<Array<ConcertApiModel>>([]),
/** Enhanced data about a specific concert */
concert: ref<ConcertDetailsApiModel>(new ConcertDetailsApiModel()),
/** Array of cities for which the concerts should be filtered */
filteredCities: ref<Array<CityModel>>([]),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
/** Show or hide the edit dialog for edit a concert */
showEditDialog: ref(false)
}),
actions: {
/**
* Download all concerts from server
*/
async getConcerts() {
this.fetchInProgress = true
fetchAllConcerts()
.then(result => {
this.concerts = result.data.filter((concert: ConcertApiModel) => {
if (this.filteredCities.length == 0) {
return true
}
for (let city of this.filteredCities) {
if (city.name == concert.location.city.name) {
return true
}
}
return false
})
this.fetchInProgress = false
})
},
/**
* Get all data about a specific concert
*
* @param id ID of the concert in the database
*/
async getConcert(location: string, date: string) {
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
let id = this.concerts.find((concert: ConcertApiModel) => {
return (concert.location.urlName == location && concert.date == date)
}).id
fetchConcertById(id)
.then(result => {
this.concert = result.data
this.fetchInProgress = false
})
.catch(res => {
feedbackStore.notFound = true
})
},
/**
* Download the next four upcoming concerts from server
*/
async getUpcomingConcerts() {
this.fetchInProgress = true
fetchUpcomingConcerts(4)
.then(result => {
this.upcomingConcerts = result.data
this.fetchInProgress = false
})
},
newConcert() {
this.concert = new ConcertDetailsApiModel()
this.showEditDialog = true
},
editConcert(concert: ConcertModel) {
// todo
},
async deleteConcert(item: ConcertModel) {
// todo
}
}
})

View File

@@ -0,0 +1,117 @@
import { fetchAllExerciseGroups, updateExercise } from "@/data/api/exerciseApi";
import { defineStore } from "pinia";
import { ref } from "vue";
import { useFeedbackStore } from "./feedback.store";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
import { ExerciseModel } from "@/data/models/exercises/exerciseModel";
export const useExerciseStore = defineStore("exerciseStore", {
state: () => ({
/** All exercise groups with sub-exercises */
exercises: ref<Array<ExerciseModel>>([]),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
helpPageVisible: ref(false)
}),
actions: {
/**
* Get all exercises and exercise groups from server
*/
async getAllExercises() {
this.fetchInProgress = true
await fetchAllExerciseGroups()
.then(result => {
this.exercises = result.data
this.helpPageVisible = this.getExercise(1, 1).solved
this.fetchInProgress = false
})
},
/**
* Get a exercise by group and exercise number
*
* @param exerciseGroupNr Number of group of exercise
* @param exerciseNr Number of exercise in group
*
* @returns ExerciseModel
*/
getExercise(exerciseGroupNr: number, exerciseNr: number): ExerciseModel {
return this.exercises.find((exercise: ExerciseModel) => {
return exercise.exerciseNr == exerciseNr && exercise.exerciseGroup.groupNr == exerciseGroupNr
})
},
/**
* Mark an exercise as solved
*
* @param exerciseGroupNr Exercise group number (0-3)
* @param exerciseNr Exercise number (1-3)
*/
async solveExercise(exerciseGroupNr: number, exerciseNr: number) {
// Request all exercises from server
await this.getAllExercises()
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
// Change only if the exercise is not solved
updateExercise(exerciseGroupNr, exerciseNr, true)
.then(result => {
if (result.data.changed) {
let bannerState = BannerStateEnum.ERROR
switch(exerciseGroupNr) {
case 0: {
switch(exerciseNr) {
case 1: bannerState = BannerStateEnum.EXERCISESOLVED01; break;
case 2: bannerState = BannerStateEnum.EXERCISESOLVED02; break;
case 3: bannerState = BannerStateEnum.EXERCISESOLVED03; break;
}
break;
}
case 1: {
switch(exerciseNr) {
case 1: bannerState = BannerStateEnum.EXERCISESOLVED11; break;
case 2: bannerState = BannerStateEnum.EXERCISESOLVED12; break;
case 3: bannerState = BannerStateEnum.EXERCISESOLVED13; break;
}
break;
}
case 2: {
switch(exerciseNr) {
case 1: bannerState = BannerStateEnum.EXERCISESOLVED21; break;
case 2: bannerState = BannerStateEnum.EXERCISESOLVED22; break;
case 3: bannerState = BannerStateEnum.EXERCISESOLVED23; break;
}
break;
}
case 3: {
switch(exerciseNr) {
case 1: bannerState = BannerStateEnum.EXERCISESOLVED31; break;
case 2: bannerState = BannerStateEnum.EXERCISESOLVED32; break;
case 3: bannerState = BannerStateEnum.EXERCISESOLVED33; break;
}
break;
}
}
feedbackStore.addSnackbar(bannerState)
this.getAllExercises()
}
})
}
}
})

View File

@@ -0,0 +1,288 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { BannerStateEnum } from "../data/enums/bannerStateEnum";
import { Composer } from 'vue-i18n';
/**
* Logic of the bubble notifications
* Includes an i18n object for translation
* Includes a variable for redirecting to 404 page
*/
export const useFeedbackStore = defineStore("feedbackStore", {
state: () => ({
snackbars: ref<Array<{text: string, color: string, icon: string }>>([]),
/** Show notification banner in top right corner */
showSnackbar: ref(false),
/** Programmatically access to language translation module */
$i18n: {},
/** Band, Location or concert on URL does not exist, redirect to 404 page */
notFound: ref(false)
}),
getters: {
i18n(): Composer {
return this.$i18n.global as Composer
}
},
actions: {
addSnackbar(bannerState: BannerStateEnum) {
this.snackbars.push({
text: this.getSnackbarText(bannerState),
color: this.getSnackbarColor(bannerState),
icon: this.getSnackbarIcon(bannerState)
})
this.showSnackbar = true
},
getSnackbarText(bannerState: BannerStateEnum) {
switch (bannerState) {
////////// System feedback //////////
case BannerStateEnum.ERROR:
return this.i18n.t('bannerMessages.error')
case BannerStateEnum.BASKETPRODUCTADDED:
return this.i18n.t('bannerMessages.basketTicketAdded')
case BannerStateEnum.BASKETPRODUCTREMOVED:
return this.i18n.t("bannerMessages.basketTicketRemoved")
////////// Exercise feedback //////////
case BannerStateEnum.EXERCISESOLVED01:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [0, 1])
case BannerStateEnum.EXERCISESOLVED02:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [0, 2])
case BannerStateEnum.EXERCISESOLVED03:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [0, 3])
case BannerStateEnum.EXERCISESOLVED11:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [1, 1])
case BannerStateEnum.EXERCISESOLVED12:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [1, 2])
case BannerStateEnum.EXERCISESOLVED13:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [1, 3])
case BannerStateEnum.EXERCISESOLVED21:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [2, 1])
case BannerStateEnum.EXERCISESOLVED22:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [2, 2])
case BannerStateEnum.EXERCISESOLVED23:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [2, 3])
case BannerStateEnum.EXERCISESOLVED31:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [3, 1])
case BannerStateEnum.EXERCISESOLVED32:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [3, 2])
case BannerStateEnum.EXERCISESOLVED33:
return this.i18n.t("bannerMessages.exerciseSolvedNr", [3, 3])
////////// API Endpoint /api //////////
case BannerStateEnum.DATABASERESETSUCCESSFUL:
return this.i18n.t('bannerMessages.databaseResetSuccessful')
case BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL:
return this.i18n.t('bannerMessages.exerciseProgressResetSuccessful')
////////// API Endpoint /accounts //////////
case BannerStateEnum.ACCOUNTLOGINSUCCESSFUL:
return this.i18n.t('bannerMessages.loginSuccessful')
case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN:
return this.i18n.t('bannerMessages.wrongLogin')
case BannerStateEnum.ACCOUNTLOGINERROR:
return this.i18n.t('bannerMessages.error')
case BannerStateEnum.ACCOUNTREGISTERSUCCESSFUL:
return this.i18n.t("bannerMessages.registerSuccessful")
case BannerStateEnum.ACCOUNTREGISTERERROR:
return this.i18n.t("bannerMessages.registerSuccessful")
case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE:
return this.i18n.t("bannerMessages.usernameInUse")
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
return this.i18n.t("bannerMessages.accountUpdated")
case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL:
return this.i18n.t('bannerMessages.logoutSuccessful')
////////// API Endpoint /orders //////////
case BannerStateEnum.ORDERPLACESUCCESSFUL:
return this.i18n.t('bannerMessages.orderPlaceSuccessfull')
////////// API Endpoint /bands //////////
case BannerStateEnum.BANDDELETEERROR:
return this.i18n.t('bannerMessages.bandDeleteError')
case BannerStateEnum.BANDDELETESUCCESSFUL:
return this.i18n.t('bannerMessages.bandDeleteSuccessful')
case BannerStateEnum.BANDSAVEDERROR:
return this.i18n.t('bannerMessages.bandSavedError')
case BannerStateEnum.BANDSAVEDSUCCESSFUL:
return this.i18n.t('bannerMessages.bandSavedSuccessful')
////////// API Endpoint /genres //////////
case BannerStateEnum.GENREDELETEERROR:
return this.i18n.t('bannerMessages.genreDeleteError')
case BannerStateEnum.GENREDELETESUCCESSFUL:
return this.i18n.t('bannerMessages.genreDeleteSuccessful')
case BannerStateEnum.GENRESAVEDERROR:
return this.i18n.t('bannerMessages.genreSavedError')
case BannerStateEnum.GENRESAVEDSUCCESSFUL:
return this.i18n.t('bannerMessages.genreSavedSuccessful')
}
},
getSnackbarColor(bannerState: BannerStateEnum) {
switch (bannerState) {
case BannerStateEnum.ERROR:
case BannerStateEnum.ACCOUNTLOGINERROR:
case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN:
case BannerStateEnum.ACCOUNTREGISTERERROR:
case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE:
case BannerStateEnum.BANDDELETEERROR:
case BannerStateEnum.BANDSAVEDERROR:
case BannerStateEnum.GENREDELETEERROR:
case BannerStateEnum.GENRESAVEDERROR:
return "red"
case BannerStateEnum.BASKETPRODUCTADDED:
case BannerStateEnum.DATABASERESETSUCCESSFUL:
case BannerStateEnum.ACCOUNTLOGINSUCCESSFUL:
case BannerStateEnum.ACCOUNTREGISTERSUCCESSFUL:
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL:
case BannerStateEnum.ORDERPLACESUCCESSFUL:
case BannerStateEnum.BANDDELETESUCCESSFUL:
case BannerStateEnum.BANDSAVEDSUCCESSFUL:
case BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL:
case BannerStateEnum.GENREDELETESUCCESSFUL:
case BannerStateEnum.GENRESAVEDSUCCESSFUL:
return "green"
case BannerStateEnum.EXERCISESOLVED01:
case BannerStateEnum.EXERCISESOLVED02:
case BannerStateEnum.EXERCISESOLVED03:
case BannerStateEnum.EXERCISESOLVED11:
case BannerStateEnum.EXERCISESOLVED12:
case BannerStateEnum.EXERCISESOLVED13:
case BannerStateEnum.EXERCISESOLVED21:
case BannerStateEnum.EXERCISESOLVED22:
case BannerStateEnum.EXERCISESOLVED23:
case BannerStateEnum.EXERCISESOLVED31:
case BannerStateEnum.EXERCISESOLVED32:
case BannerStateEnum.EXERCISESOLVED33:
return "purple"
case BannerStateEnum.BASKETPRODUCTREMOVED:
return "blue"
}
},
getSnackbarIcon(bannerState: BannerStateEnum) {
switch (bannerState) {
case BannerStateEnum.ERROR:
return "mdi-alert-circle"
case BannerStateEnum.ACCOUNTLOGINERROR:
case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN:
case BannerStateEnum.ACCOUNTREGISTERERROR:
case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE:
return "mdi-account"
case BannerStateEnum.EXERCISESOLVED01:
case BannerStateEnum.EXERCISESOLVED02:
case BannerStateEnum.EXERCISESOLVED03:
case BannerStateEnum.EXERCISESOLVED11:
case BannerStateEnum.EXERCISESOLVED12:
case BannerStateEnum.EXERCISESOLVED13:
case BannerStateEnum.EXERCISESOLVED21:
case BannerStateEnum.EXERCISESOLVED22:
case BannerStateEnum.EXERCISESOLVED23:
case BannerStateEnum.EXERCISESOLVED31:
case BannerStateEnum.EXERCISESOLVED32:
case BannerStateEnum.EXERCISESOLVED33:
return "mdi-check-circle-outline"
case BannerStateEnum.DATABASERESETSUCCESSFUL:
case BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL:
return "mdi-database-refresh"
case BannerStateEnum.BASKETPRODUCTADDED:
case BannerStateEnum.BASKETPRODUCTREMOVED:
return "mdi-basket"
case BannerStateEnum.ORDERPLACESUCCESSFUL:
return "mdi-basket-check"
case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL:
return "mdi-logout"
case BannerStateEnum.ACCOUNTLOGINSUCCESSFUL:
return "mdi-login"
case BannerStateEnum.ACCOUNTREGISTERSUCCESSFUL:
return "mdi-account-plus"
case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL:
return "mdi-account-reactivate"
case BannerStateEnum.BANDDELETEERROR:
case BannerStateEnum.BANDDELETESUCCESSFUL:
case BannerStateEnum.BANDSAVEDERROR:
case BannerStateEnum.BANDSAVEDSUCCESSFUL:
return "mdi-guitar-electric"
case BannerStateEnum.GENREDELETEERROR:
case BannerStateEnum.GENREDELETESUCCESSFUL:
case BannerStateEnum.GENRESAVEDERROR:
case BannerStateEnum.GENRESAVEDSUCCESSFUL:
return "mdi-music-clef-treble"
}
}
}
})

61
src/stores/files.store.ts Normal file
View File

@@ -0,0 +1,61 @@
import { fetchFileNames, fetchFolderNames, postFile } from "@/data/api/files.api";
import { defineStore } from "pinia";
import { ref } from "vue";
export const useFilesStore = defineStore('filesStore', {
state: () => ({
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
staticFolders: ref<Array<{name: string, nrOfItems: number}>>([]),
selectedFolder: ref<{name: string, nrOfItems: number}>(),
/** List of files on the server */
staticFiles: ref<Array<{name: string, size: number, url: string}>>([]),
selectedFile: ref<{name: string, size: number, url: string}>(),
showFileUploadDialog: ref(false),
fileUpload: ref(),
fileUploadDir: ref(""),
}),
actions: {
async getStaticFolders() {
this.fetchInProgress = true
fetchFolderNames()
.then(res => {
this.staticFolders = res.data
this.fetchInProgress = false
})
},
/**
* Request all available static files on server
*/
async getStaticFiles() {
this.fetchInProgress = true
fetchFileNames(this.selectedFolder.name)
.then(res => {
this.staticFiles = res.data
this.fetchInProgress = false
})
},
async uploadFile() {
this.fetchInProgress = true
postFile(this.uploadFile, this.fileUploadDir)
.then(response => {
console.log(response)
this.showFileUploadDialog = false
this.fetchInProgress = false
})
},
}
})

112
src/stores/genre.store.ts Normal file
View File

@@ -0,0 +1,112 @@
import { deleteGenre, fetchAllGenres, patchGenre, postGenre } from "@/data/api/genreApi";
import { GenreApiModel } from "@/data/models/acts/genreApiModel";
import { defineStore } from "pinia";
import { ref } from "vue";
import { useFeedbackStore } from "./feedback.store";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
export const useGenreStore = defineStore("genreStore", {
state: () => ({
/** All available genres from server */
genres: ref<Array<GenreApiModel>>([]),
/** Currently edited genre */
genre: ref<GenreApiModel>(new GenreApiModel()),
/** Genres to filter bands for */
filteredGenres: ref<Array<GenreApiModel>>([]),
/** Show or hide edit dialog for Genre object */
showEditDialog: ref(false),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false)
}),
actions: {
/**
* Get all genres from the database
*/
getGenres() {
this.fetchInProgress = true
fetchAllGenres()
.then(response => {
this.genres = response.data
this.fetchInProgress = false
})
},
/**
* Prepare edit dialog for new genre, opens it
*/
newGenre() {
this.genre = new GenreApiModel()
this.showEditDialog = true
},
/**
* Edit a Genre object, move parameter to this.genre, opens dialog
*
* @param genre Selected Genre object
*/
editGenre(genre: GenreApiModel) {
this.genre = genre
this.showEditDialog = true
},
/**
* Save edited genre to the backend server
*/
saveGenre() {
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
if (this.genre.id == undefined) {
// Creating new Genre
postGenre(this.genre)
.then(response => {
if (response.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDSUCCESSFUL)
this.getGenres()
this.showEditDialog = false
} else {
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDERROR)
}
})
} else {
// Update existing Genre
patchGenre(this.genre)
.then(response => {
if (response.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDSUCCESSFUL)
this.getGenres()
this.showEditDialog = false
} else {
feedbackStore.addSnackbar(BannerStateEnum.GENRESAVEDERROR)
}
})
}
},
/**
* Delete a Genre object
*
* @param genre Genre to delete
*/
deleteGenre(genre: GenreApiModel) {
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
deleteGenre(genre)
.then(response => {
if (response.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.GENREDELETESUCCESSFUL)
this.getGenres()
} else {
feedbackStore.addSnackbar(BannerStateEnum.GENREDELETEERROR)
}
})
}
}
})

View File

@@ -0,0 +1,105 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { fetchAllLocations, fetchLocationByName, fetchTopLocations } from "../data/api/locationApi";
import { LocationApiModel } from "../data/models/locations/locationApiModel";
import { CityModel } from "../data/models/locations/cityModel";
import { fetchAllCities } from "../data/api/cityApi";
import { LocationDetailsApiModel } from "@/data/models/locations/locationDetailsApiModel";
import { useFeedbackStore } from "./feedback.store";
export const useLocationStore = defineStore("locationStore", {
state: () => ({
/** All available locations */
locations: ref<Array<LocationApiModel>>([]),
/** Locations with the most concerts */
topLocations: ref<Array<LocationApiModel>>([]),
/** Enhanced data about a specific location */
location: ref<LocationDetailsApiModel>(new LocationDetailsApiModel()),
/** All available cities */
cities: ref<Array<CityModel>>([]),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
showEditLocation: ref(false)
}),
actions: {
/**
* Download all cities/locations from server
*/
async getLocations() {
this.fetchInProgress = true
await fetchAllLocations()
.then(result => {
this.locations = result.data
})
await fetchAllCities()
.then(result => {
this.cities = result.data
this.fetchInProgress = false
})
},
getLocationByName(name: string) {
const feedbackStore = useFeedbackStore()
this.fetchInProgress = true
fetchLocationByName(name)
.then(result => {
this.location = result.data
this.fetchInProgress = false
})
.catch(res => {
feedbackStore.notFound = true
})
},
/**
* Get all locations in a specific city
*
* @param city City to filter for
*
* @returns Array of cities which are in the target city
*/
getLocationsByCity(city: string): Array<LocationApiModel> {
return this.locations.filter((location: LocationApiModel) => {
return location.city.name == city
})
},
/**
* Fetch top 8 locations from server
*/
async getTopLocations() {
this.fetchInProgress = true
await fetchTopLocations(8)
.then(result => {
this.topLocations = result.data
this.fetchInProgress = false
})
},
newLocation() {
this.location = new LocationDetailsApiModel()
this.showEditLocation = true
},
editLocation(item: LocationApiModel) {
this.location = item
this.showEditLocation = true
},
async deleteLocation(item: LocationApiModel) {
// todo
}
},
})

58
src/stores/order.store.ts Normal file
View File

@@ -0,0 +1,58 @@
import { fetchAllOrders, fetchUserOrders } from "@/data/api/orderApi";
import { OrderApiModel } from "@/data/models/apiEndpoints/orderApiModel";
import { AccountModel } from "@/data/models/user/accountModel";
import { defineStore } from "pinia";
import { ref } from "vue";
export const useOrderStore = defineStore("orderStore", {
state: () => ({
/** All orders of one/all users */
orders: ref<Array<OrderApiModel>>([]),
order: ref<OrderApiModel>(new OrderApiModel),
showDetailDialog: ref<boolean>(false),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false)
}),
actions: {
/**
* Get all orders from all accounts from server
*/
async getAllOrders() {
this.fetchInProgress = true
fetchAllOrders()
.then(res => {
this.orders = res.data
this.fetchInProgress = false
})
},
/**
* Get all orders from one account from server
*
* @param user User to request orders from
*/
async getOrdersOfAccount(user: AccountModel) {
this.fetchInProgress = true
fetchUserOrders(user.id)
.then(res => {
this.orders = res.data
this.fetchInProgress = false
})
},
openDetails(order: OrderApiModel) {
this.order = order
this.showDetailDialog = true
},
async deleteOrder(order: OrderApiModel) {
// todo
}
}
})

View File

@@ -0,0 +1,136 @@
import { defineStore } from "pinia";
import { useLocalStorage } from "@vueuse/core";
import { ThemeEnum } from "../data/enums/themeEnums";
import { LanguageEnum } from "../data/enums/languageEnum";
import { ref } from "vue";
import { fetchServerState,resetDatabase, resetExerciseProgress } from "@/data/api/mainApi";
import { ServerStateEnum } from "@/data/enums/serverStateEnum";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
import { useFeedbackStore } from "./feedback.store";
import { useBasketStore } from "./basket.store";
import { useExerciseStore } from "./exercise.store";
import { useAccountStore } from "./account.store";
import { AccountApiModel } from "@/data/models/user/accountApiModel";
export const usePreferencesStore = defineStore('preferencesStore', {
state: () => ({
/** Selected theme by user */
theme: useLocalStorage<ThemeEnum>("hackmycart/preferencesStore/theme", ThemeEnum.DARK),
/** Selected language by user */
language: useLocalStorage<LanguageEnum>("hackmycart/preferencesStore/language", LanguageEnum.GERMAN),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
/** State of the server */
serverState: ref<ServerStateEnum>(ServerStateEnum.PENDING),
/** Show the "Delete DB?" confirm dialog */
showDeleteDbDialog: ref(false),
/** Show the "Delete Exercise progress?" confirm dialog */
showDeleteExerciseDialog: ref(false),
/** Show the "Factory reset" confirm dialog */
showFactoryResetDialog: ref(false),
/** Marks the first run of the app */
firstStartup: useLocalStorage<Boolean>("hackmycart/preferencesStore/firstStartup", true),
/** Full name of student */
studentName: useLocalStorage<string>("hackmycart/preferencesStore/studentName", ""),
/** Matrikel number */
registrationNumber: useLocalStorage<string>("hackmycart/preferencesStore/registrationNumber", "")
}),
actions: {
/**
* Request the state of the backend server
*/
async getServerState() {
this.fetchInProgress = true
fetchServerState()
.then(result => {
if (result.status == 200) {
this.serverState = ServerStateEnum.ONLINE
} else {
this.serverState = ServerStateEnum.OFFLINE
}
this.fetchInProgress = false
})
.catch(error => {
this.serverState = ServerStateEnum.OFFLINE
this.fetchInProgress = false
})
},
/**
* Resets the database (without exercise tables)
*/
async resetDb() {
const feedbackStore = useFeedbackStore()
this.serverState = ServerStateEnum.PENDING
this.fetchInProgress = true
await resetDatabase()
.then(result => {
if (result.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.DATABASERESETSUCCESSFUL)
this.serverState = ServerStateEnum.ONLINE
}
this.fetchInProgress = false
this.showDeleteDbDialog = false
})
},
/**
* Resets the exercise progress
*/
async resetExerciseProg() {
const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore()
this.serverState = ServerStateEnum.PENDING
this.fetchInProgress = true
await resetExerciseProgress()
.then(result => {
if (result.status == 200) {
feedbackStore.addSnackbar(BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL)
this.serverState = ServerStateEnum.ONLINE
exerciseStore.getAllExercises()
}
this.fetchInProgress = false
this.showDeleteExerciseDialog = false
})
},
/**
* Reset all store values to factory state
*/
resetToFactorySettings() {
const basketStore = useBasketStore()
const accountStore = useAccountStore()
this.firstStartup = true
this.studentName = ""
this.registrationNumber = ""
this.theme = "dark"
this.language = LanguageEnum.GERMAN
basketStore.itemsInBasket = []
accountStore.userAccountToken = ""
accountStore.userAccount = new AccountApiModel()
this.showFactoryResetDialog = false
}
}
})

View File

@@ -0,0 +1,67 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { fetchBandsBySearchTerm } from "../data/api/bandApi";
import { fetchLocationsBySearchTerm } from "../data/api/locationApi";
import { fetchConcertsBySearchTerm } from "../data/api/concertApi";
import { ConcertApiModel } from "@/data/models/acts/concertApiModel";
import { useExerciseStore } from "./exercise.store";
export const useSearchStore = defineStore("searchStore", {
state: () => ({
/** Search term */
searchTerm: ref(""),
/** Band results */
bands: ref(),
/** Location results */
locations: ref(),
/** Concert results */
concerts: ref<Array<ConcertApiModel>>([]),
/** One or more searches are already performed */
alreadySearched: ref(false),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false)
}),
actions: {
/**
* Search for the term in all bands, locations, events
*/
async startSearch() {
const exerciseStore = useExerciseStore()
this.alreadySearched = true
this.fetchInProgress = true
// Exercise solutions
if (this.searchTerm.endsWith("'); SELECT * FROM Accounts; --")) {
exerciseStore.solveExercise(2, 1)
} else if (this.searchTerm.endsWith("'); SELECT * FROM AccountRoles; --")) {
exerciseStore.solveExercise(2, 2)
} else if (this.searchTerm.includes("'); UPDATE Accounts SET accountRoleId = 2 WHERE username = ")) {
exerciseStore.solveExercise(2, 3)
}
await fetchBandsBySearchTerm(this.searchTerm)
.then(result => {
this.bands = result.data
})
await fetchLocationsBySearchTerm(this.searchTerm)
.then(result => {
this.locations = result.data
})
await fetchConcertsBySearchTerm(this.searchTerm)
.then(result => {
this.concerts = result.data
})
this.fetchInProgress = false
}
}
})