From 7c782268646a7efc0d54a18b8b91157389ecbabf Mon Sep 17 00:00:00 2001 From: Tobias Zoghaib Date: Tue, 26 Nov 2024 19:39:40 +0100 Subject: [PATCH] More feedback on account creation, bugfix on account creation --- backend/routes/account.routes.ts | 17 ++--- src/data/api/accountApi.ts | 65 +++++++++++++++--- src/data/enums/bannerStateEnum.ts | 19 ++++-- src/locales/de.json | 9 ++- src/locales/en.json | 7 +- src/pages/account/loginPage/loginForm.vue | 2 + src/pages/account/loginPage/registerForm.vue | 5 -- src/stores/account.store.ts | 71 +++++++++++++------- src/stores/basket.store.ts | 2 +- src/stores/feedback.store.ts | 35 +++++++--- 10 files changed, 166 insertions(+), 66 deletions(-) diff --git a/backend/routes/account.routes.ts b/backend/routes/account.routes.ts index 507c509..d95a65a 100644 --- a/backend/routes/account.routes.ts +++ b/backend/routes/account.routes.ts @@ -4,7 +4,6 @@ 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"; import { sequelize } from "../database"; import jwt from "jsonwebtoken" import { verifyToken } from "../middlewares/auth.middleware"; @@ -12,7 +11,7 @@ import { encryptString } from "../scripts/encryptScripts"; export const account = Router() -account.get("/", (req: Request, res: Response) => { +account.get("/", verifyToken, (req: Request, res: Response) => { Account.findAll({ include: [ AccountRole ] }) @@ -22,7 +21,7 @@ account.get("/", (req: Request, res: Response) => { }) // Login user -account.get("/login", async (req: Request, res: Response) => { +account.get("/account/login", async (req: Request, res: Response) => { const encryptedPassword = encryptString(String(req.query.password)) // Using raw SQL code for SQL injections! @@ -52,7 +51,7 @@ account.get("/login", async (req: Request, res: Response) => { }) -account.get("/account", verifyToken, async(req: Request, res: Response) => { +account.get("/account/data", verifyToken, async(req: Request, res: Response) => { Account.findOne({ where: { id: req["id"] @@ -66,7 +65,7 @@ account.get("/account", verifyToken, async(req: Request, res: Response) => { // Creating a new user -account.post("/", async (req: Request, res: Response) => { +account.post("/account", async (req: Request, res: Response) => { // Check if username is valid if (!validateString(req.body.username, 4)) { @@ -85,9 +84,10 @@ account.post("/", async (req: Request, res: Response) => { code: 400, message: "Password too short!" }) + return } - // Create account + // User on creation gets User role await AccountRole.findOne({ where: { name: "User" @@ -97,6 +97,7 @@ account.post("/", async (req: Request, res: Response) => { req.body["accountRoleId"] = role.id }) + // Create account Account.create(req.body) .then(account => { // Status: 201 Created @@ -110,7 +111,7 @@ account.post("/", async (req: Request, res: Response) => { }) }) -account.patch("/", verifyToken, (req: Request, res: Response) => { +account.patch("/account", verifyToken, (req: Request, res: Response) => { Account.update(req.body, { where: { id: req.body.id } @@ -157,7 +158,7 @@ account.patch("/", verifyToken, (req: Request, res: Response) => { }) }) -account.delete("/:id", (req: Request, res: Response) => { +account.delete("/account/:id", (req: Request, res: Response) => { Account.destroy({ where: { id: req.params.id diff --git a/src/data/api/accountApi.ts b/src/data/api/accountApi.ts index 76bb5db..55e7b09 100644 --- a/src/data/api/accountApi.ts +++ b/src/data/api/accountApi.ts @@ -3,34 +3,83 @@ import { AccountModel } from "../models/user/accountModel" const BASE_URL = "http://localhost:3000/accounts" -export async function fetchAllAccounts() { - return await axios.get(BASE_URL) +/** + * Fetch all accounts from server + * + * @param token Validation token of current logged in user. User needs to have the right privileges + * + * @returns Response from server with list of all account body + */ +export async function fetchAllAccounts(token: string) { + return await axios.get(BASE_URL, { + headers: { + "Authorization": token + } + }) } -export async function login(username: string, password: string) { - return await axios.get(BASE_URL + "/login?username=" + username + "&password=" + password) +/** + * Start the login process + * + * @param username Username of the account + * @param password Password of the account + * + * @returns Response from server with token body + */ +export async function getLogin(username: string, password: string) { + return await axios.get(BASE_URL + "/account/login?username=" + username + "&password=" + password) } + +/** + * Get all data about a single account + * + * @param token Validation token + * + * @returns Response from server with account body + */ export async function getAccount(token: string) { - return await axios.get(BASE_URL + "/account", { + return await axios.get(BASE_URL + "/account/data", { headers: { "Authorization": token } }) } +/** + * Register a new account in servers database + * + * @param account Account data for new dataset + * + * @returns Response from server + */ export async function registerAccount(account: AccountModel) { - return await axios.post(BASE_URL, account) + return await axios.post(BASE_URL + "/account", account) } +/** + * Update data of an account + * + * @param account Account data to update + * @param token Validation token + * + * @returns Response from server + */ export async function updateAccount(account: AccountModel, token: string) { - return await axios.patch(BASE_URL, account, { + return await axios.patch(BASE_URL + "/account", account, { headers: { "Authorization": token } }) } +/** + * Delete an account in servers database + * + * @param account Account to delete + * + * @returns Response from server + */ export async function deleteAccount(account: AccountModel) { - return await axios.delete(BASE_URL + "/" + account.id) + return await axios.delete(BASE_URL + "/account/" + account.id) } \ No newline at end of file diff --git a/src/data/enums/bannerStateEnum.ts b/src/data/enums/bannerStateEnum.ts index b777b8e..88f998f 100644 --- a/src/data/enums/bannerStateEnum.ts +++ b/src/data/enums/bannerStateEnum.ts @@ -1,12 +1,14 @@ export enum BannerStateEnum { ////////// System feedback ////////// - // Some error + // Unknown error ERROR, - BASKETPRODUCTADDED, + // Ticket added to basket + BASKETTICKETADDED, - BASKETPRODUCTREMOVED, + // Ticket removed from basket + BASKETTICKETREMOVED, ////////// Exercise feedback ////////// @@ -63,11 +65,20 @@ export enum BannerStateEnum { ACCOUNTREGISTERERROR, // Status: 409 Conflict - ACCOUNTREGISTERUSERNAMEINUSE, + ACCOUNTREGISTERUSERNAMEORMAILINUSE, // Status: 200 OK ACCOUNTUPDATESUCCESSFUL, + // Local check on unvalid username + ACCOUNTUSERNAMETOOSHORT, + + // Local check on unvalid password + ACCOUNTPASSWORDTOOSHORT, + + // Local check on unvalid mail address + ACCOUNTMAILADDRESSUNVALID, + // No status code, runs in local cache ACCOUNTLOGOUTSUCCESSFUL, diff --git a/src/locales/de.json b/src/locales/de.json index 14cde2b..f6da564 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -173,7 +173,6 @@ "exerciseProgressResetSuccessful": "Aufgabenfortschritt erfolgreich zurück gesetzt!", "registerSuccessful": "Account erfolgreich erstellt!", "registerError": "Fehler beim Erstellen des Accounts", - "usernameInUse": "Der Accountname ist bereits vergeben!", "accountUpdated": "Account erfolgreich aktualisiert", "logoutSuccessful": "Logout erfolgreich", "orderPlaceSuccessfull": "Bestellung erfolgreich aufgegeben", @@ -187,7 +186,11 @@ "genreDeleteError": "Fehler beim Löschen des Genres", "genreDeleteSuccessful": "Genre erfolgreich gelöscht", "genreSavedError": "Fehler beim Speichern des Genres", - "genreSavedSuccessful": "Genre erfolgreich gespeichert" + "genreSavedSuccessful": "Genre erfolgreich gespeichert", + "accountPasswordTooShort": "Passwort ist zu kurz", + "accountUsernameTooShort": "Username ist zu kurz", + "accountMailAddressUnvalid": "Mail-Adresse ungültig", + "usernameOrMailInUse": "Der Accountname und/oder die Mail-Adresse sind bereits vergeben!" }, "misc": { "404": { @@ -218,7 +221,7 @@ "validation": { "required": "Darf nicht leer bleiben", "noDigitsAllowed": "Zahlen sind nicht erlaubt", - "notEnoughChars": "Nicht wenige Zeichen", + "notEnoughChars": "Nicht genug Zeichen", "tooMuchChars": "Zu viele Zeichen", "onlyDigitsAllowed": "Nur Zahlen erlaubt", "digitsAtStartNeeded": "Muss mit einer Zahl beginnen" diff --git a/src/locales/en.json b/src/locales/en.json index f03cca6..0a999c6 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -173,7 +173,6 @@ "exerciseProgressResetSuccessful": "Exercise progress successfully resetted!", "registerSuccessful": "Account successfully created!", "registerError": "Error on register account", - "usernameInUse": "The username is already in use!", "accountUpdated": "Account successfully updated", "logoutSuccessful": "Logout successfull", "orderPlaceSuccessfull": "Order successfully placed", @@ -187,7 +186,11 @@ "genreDeleteError": "Error on deleting Genre", "genreDeleteSuccessful": "Genre successfully deleted", "genreSavedError": "Error on saving genre", - "genreSavedSuccessful": "Genre successfully saved" + "genreSavedSuccessful": "Genre successfully saved", + "accountPasswordTooShort": "Password too short", + "accountUsernameTooShort": "Username too short", + "accountMailAddressUnvalid": "Mail-Address unvalid", + "usernameOrMailInUse": "The username and/or the mail address are already in use!" }, "misc": { "404": { diff --git a/src/pages/account/loginPage/loginForm.vue b/src/pages/account/loginPage/loginForm.vue index 0d45411..0690c71 100644 --- a/src/pages/account/loginPage/loginForm.vue +++ b/src/pages/account/loginPage/loginForm.vue @@ -33,6 +33,7 @@ async function startLogin() { v-model="accountStore.loginData.username" variant="outlined" clearable + hide-details @keyup.enter="startLogin" id="txt-username" /> @@ -48,6 +49,7 @@ async function startLogin() { variant="outlined" v-model="accountStore.loginData.password" clearable + hide-details @keyup.enter="startLogin" id="txt-password" /> diff --git a/src/pages/account/loginPage/registerForm.vue b/src/pages/account/loginPage/registerForm.vue index d9dc393..d4783ae 100644 --- a/src/pages/account/loginPage/registerForm.vue +++ b/src/pages/account/loginPage/registerForm.vue @@ -3,11 +3,9 @@ import cardView from '@/components/basics/cardView.vue'; import outlinedButton from '@/components/basics/outlinedButton.vue'; import { useAccountStore } from '@/stores/account.store'; import { getEmailRules, getPasswordRules, getStringRules } from '@/scripts/validationRules'; -import { useRouter } from 'vue-router'; const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false }) const accountStore = useAccountStore() -const router = useRouter() async function registerAccount() { accountStore.registerAccount() @@ -31,7 +29,6 @@ async function registerAccount() { prepend-icon="mdi-account" v-model="accountStore.registerData.username" clearable - hide-details variant="outlined" :rules="getStringRules()" /> @@ -46,7 +43,6 @@ async function registerAccount() { type="password" v-model="accountStore.registerData.password" clearable - hide-details variant="outlined" :rules="getPasswordRules()" /> @@ -61,7 +57,6 @@ async function registerAccount() { v-model="accountStore.registerData.email" :rules="getEmailRules()" variant="outlined" - hide-details clearable /> diff --git a/src/stores/account.store.ts b/src/stores/account.store.ts index 875ae83..5b60f6a 100644 --- a/src/stores/account.store.ts +++ b/src/stores/account.store.ts @@ -1,7 +1,7 @@ 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 { deleteAccount, fetchAllAccounts, getAccount, getLogin, 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"; @@ -38,10 +38,13 @@ export const useAccountStore = defineStore("accountStore", { }), actions: { + /** + * Fetch all accounts on the database + */ async getAllAccounts() { this.fetchInProgress = true - fetchAllAccounts() + fetchAllAccounts(this.userAccountToken) .then(response => { this.accounts = response.data this.fetchInProgress = false @@ -68,7 +71,7 @@ export const useAccountStore = defineStore("accountStore", { } else { - await login(this.loginData.username, this.loginData.password) + await getLogin(this.loginData.username, this.loginData.password) .then(async result => { this.userAccountToken = result.data.token @@ -100,6 +103,9 @@ export const useAccountStore = defineStore("accountStore", { } }, + /** + * Reload account information about current logged in user + */ async refreshAccount() { getAccount(this.userAccountToken) .then(response => { @@ -123,32 +129,42 @@ export const useAccountStore = defineStore("accountStore", { 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) - } + if (this.registerData.username == null || this.registerData.username.length < 4) { + feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTUSERNAMETOOSHORT) + } else if (this.registerData.password == null || this.registerData.password.length < 8) { + feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTPASSWORDTOOSHORT) + } 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) + } + else { + 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.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 + }) + .catch((error) => { + if (error.status == 400) { + feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTREGISTERERROR) + } else if (error.status == 409) { + feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTREGISTERUSERNAMEORMAILINUSE) + } - this.fetchInProgress = false - return false - }) + this.fetchInProgress = false + return false + }) + } - return false + this.fetchInProgress = false + return false }, /** @@ -229,6 +245,11 @@ export const useAccountStore = defineStore("accountStore", { ) }, + /** + * Delete user account + * + * @param account Account which should be deleted + */ async deleteAccount(account: AccountModel) { this.fetchInProgress = true diff --git a/src/stores/basket.store.ts b/src/stores/basket.store.ts index 4576449..4163641 100644 --- a/src/stores/basket.store.ts +++ b/src/stores/basket.store.ts @@ -53,7 +53,7 @@ export const useBasketStore = defineStore('basketStore', { */ removeItemFromBasket(item: BasketItemModel) { const feedbackStore = useFeedbackStore() - feedbackStore.addSnackbar(BannerStateEnum.BASKETPRODUCTREMOVED) + feedbackStore.addSnackbar(BannerStateEnum.BASKETTICKETREMOVED) this.itemsInBasket = this.itemsInBasket.filter((basketItemModel: BasketItemModel) => basketItemModel.concert.id != item.concert.id diff --git a/src/stores/feedback.store.ts b/src/stores/feedback.store.ts index b50c682..3a1077c 100644 --- a/src/stores/feedback.store.ts +++ b/src/stores/feedback.store.ts @@ -46,10 +46,10 @@ export const useFeedbackStore = defineStore("feedbackStore", { case BannerStateEnum.ERROR: return this.i18n.t('bannerMessages.error') - case BannerStateEnum.BASKETPRODUCTADDED: + case BannerStateEnum.BASKETTICKETADDED: return this.i18n.t('bannerMessages.basketTicketAdded') - case BannerStateEnum.BASKETPRODUCTREMOVED: + case BannerStateEnum.BASKETTICKETREMOVED: return this.i18n.t("bannerMessages.basketTicketRemoved") @@ -127,8 +127,8 @@ export const useFeedbackStore = defineStore("feedbackStore", { case BannerStateEnum.ACCOUNTREGISTERERROR: return this.i18n.t("bannerMessages.registerSuccessful") - case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE: - return this.i18n.t("bannerMessages.usernameInUse") + case BannerStateEnum.ACCOUNTREGISTERUSERNAMEORMAILINUSE: + return this.i18n.t("bannerMessages.usernameOrMailInUse") case BannerStateEnum.ACCOUNTUPDATESUCCESSFUL: return this.i18n.t("bannerMessages.accountUpdated") @@ -136,6 +136,15 @@ export const useFeedbackStore = defineStore("feedbackStore", { case BannerStateEnum.ACCOUNTLOGOUTSUCCESSFUL: return this.i18n.t('bannerMessages.logoutSuccessful') + case BannerStateEnum.ACCOUNTPASSWORDTOOSHORT: + return this.i18n.t('bannerMessages.accountPasswordTooShort') + + case BannerStateEnum.ACCOUNTUSERNAMETOOSHORT: + return this.i18n.t('bannerMessages.accountUsernameTooShort') + + case BannerStateEnum.ACCOUNTMAILADDRESSUNVALID: + return this.i18n.t('bannerMessages.accountMailAddressUnvalid') + ////////// API Endpoint /orders ////////// @@ -180,14 +189,17 @@ export const useFeedbackStore = defineStore("feedbackStore", { case BannerStateEnum.ACCOUNTLOGINERROR: case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN: case BannerStateEnum.ACCOUNTREGISTERERROR: - case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE: + case BannerStateEnum.ACCOUNTREGISTERUSERNAMEORMAILINUSE: + case BannerStateEnum.ACCOUNTPASSWORDTOOSHORT: + case BannerStateEnum.ACCOUNTUSERNAMETOOSHORT: + case BannerStateEnum.ACCOUNTMAILADDRESSUNVALID: case BannerStateEnum.BANDDELETEERROR: case BannerStateEnum.BANDSAVEDERROR: case BannerStateEnum.GENREDELETEERROR: case BannerStateEnum.GENRESAVEDERROR: return "red" - case BannerStateEnum.BASKETPRODUCTADDED: + case BannerStateEnum.BASKETTICKETADDED: case BannerStateEnum.DATABASERESETSUCCESSFUL: case BannerStateEnum.ACCOUNTLOGINSUCCESSFUL: case BannerStateEnum.ACCOUNTREGISTERSUCCESSFUL: @@ -215,7 +227,7 @@ export const useFeedbackStore = defineStore("feedbackStore", { case BannerStateEnum.EXERCISESOLVED32: return "purple" - case BannerStateEnum.BASKETPRODUCTREMOVED: + case BannerStateEnum.BASKETTICKETREMOVED: return "blue" } }, @@ -229,7 +241,10 @@ export const useFeedbackStore = defineStore("feedbackStore", { case BannerStateEnum.ACCOUNTLOGINERROR: case BannerStateEnum.ACCOUNTLOGINWRONGLOGIN: case BannerStateEnum.ACCOUNTREGISTERERROR: - case BannerStateEnum.ACCOUNTREGISTERUSERNAMEINUSE: + case BannerStateEnum.ACCOUNTPASSWORDTOOSHORT: + case BannerStateEnum.ACCOUNTUSERNAMETOOSHORT: + case BannerStateEnum.ACCOUNTMAILADDRESSUNVALID: + case BannerStateEnum.ACCOUNTREGISTERUSERNAMEORMAILINUSE: return "mdi-account" case BannerStateEnum.EXERCISESOLVED01: @@ -250,8 +265,8 @@ export const useFeedbackStore = defineStore("feedbackStore", { case BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL: return "mdi-database-refresh" - case BannerStateEnum.BASKETPRODUCTADDED: - case BannerStateEnum.BASKETPRODUCTREMOVED: + case BannerStateEnum.BASKETTICKETADDED: + case BannerStateEnum.BASKETTICKETREMOVED: return "mdi-basket" case BannerStateEnum.ORDERPLACESUCCESSFUL: