Compare commits

..

2 Commits

Author SHA1 Message Date
Alex
c34c46cbc2 backend: add: DeleteUser, fix: validation 2025-02-08 18:28:07 +01:00
Alex
32a473fe29 Frontend: licence_number>number, password update enabled 2025-02-08 18:26:34 +01:00
13 changed files with 120 additions and 52 deletions

View File

@@ -33,7 +33,7 @@ interface BankAccount {
interface Licence { interface Licence {
id: number | -1; id: number | -1;
status: number | -1; status: number | -1;
licence_number: string | ''; number: string | '';
issued_date: string | ''; issued_date: string | '';
expiration_date: string | ''; expiration_date: string | '';
country: string | ''; country: string | '';
@@ -49,6 +49,7 @@ interface User {
email: string | ''; email: string | '';
first_name: string | ''; first_name: string | '';
last_name: string | ''; last_name: string | '';
password: string | '';
phone: string | ''; phone: string | '';
notes: string | ''; notes: string | '';
address: string | ''; address: string | '';

View File

@@ -18,6 +18,7 @@
email: '', email: '',
first_name: '', first_name: '',
last_name: '', last_name: '',
password: '',
phone: '', phone: '',
address: '', address: '',
zip_code: '', zip_code: '',
@@ -43,7 +44,7 @@
licence: { licence: {
id: 0, id: 0,
status: 1, status: 1,
licence_number: '', number: '',
issued_date: '', issued_date: '',
expiration_date: '', expiration_date: '',
country: '', country: '',
@@ -83,14 +84,6 @@
// $: isNewUser = user === null; // $: isNewUser = user === null;
$: isLoading = user === undefined; $: isLoading = user === undefined;
$: {
console.log('incomingUser:', user);
}
// Add debug logging for user
$: {
console.log('processed user:', user);
}
/** @type {App.Locals['licence_categories']} */ /** @type {App.Locals['licence_categories']} */
export let licence_categories; export let licence_categories;
@@ -345,7 +338,7 @@
name="user[licence][number]" name="user[licence][number]"
type="text" type="text"
label={$t('licence_number')} label={$t('licence_number')}
bind:value={localUser.licence.licence_number} bind:value={localUser.licence.number}
placeholder={$t('placeholder.licence_number')} placeholder={$t('placeholder.licence_number')}
toUpperCase={true} toUpperCase={true}
/> />

View File

@@ -3,17 +3,20 @@ import { toRFC3339 } from './helpers';
/** /**
* Converts FormData to a nested object structure * Converts FormData to a nested object structure
* @param {FormData} formData - The FormData object to convert * @param {FormData} formData - The FormData object to convert
* @returns {{ user: Partial<App.Locals['user']> }} Nested object representation of the form data * @returns {{ user: Partial<App.Locals['user']>,password2: string }} Nested object representation of the form data
*/ */
export function formDataToObject(formData) { export function formDataToObject(formData) {
/** @type { Partial<App.Locals['user']> } */ /** @type { Partial<App.Locals['user']> } */
const object = {}; const object = {};
let password2 = '';
console.log('Form data entries:'); console.log('Form data entries:');
for (const [key, value] of formData.entries()) { for (const [key, value] of formData.entries()) {
console.log('Key:', key, 'Value:', value); console.log('Key:', key, 'Value:', value);
} if (key == 'password2') {
for (const [key, value] of formData.entries()) { password2 = String(value);
continue;
}
/** @type {string[]} */ /** @type {string[]} */
const keys = key.match(/\[([^\]]+)\]/g)?.map((k) => k.slice(1, -1)) || [key]; const keys = key.match(/\[([^\]]+)\]/g)?.map((k) => k.slice(1, -1)) || [key];
console.log('Processed keys:', keys); console.log('Processed keys:', keys);
@@ -51,17 +54,17 @@ export function formDataToObject(formData) {
} }
} }
return { user: object }; return { user: object, password2: password2 };
} }
/** /**
* Processes the raw form data into the expected user data structure * Processes the raw form data into the expected user data structure
* @param {{ user: Partial<App.Locals['user']> } } rawData - The raw form data object * @param {{ user: Partial<App.Locals['user']>, password2: string} } rawData - The raw form data object
* @returns {{ user: Partial<App.Locals['user']> }} Processed user data * @returns {{ user: Partial<App.Locals['user']> }} Processed user data
*/ */
export function processFormData(rawData) { export function processFormData(rawData) {
/** @type {{ user: Partial<App.Locals['user']> }} */ /** @type {{ user: Partial<App.Locals['user']> }} */
const processedData = { let processedData = {
user: { user: {
id: Number(rawData.user.id) || 0, id: Number(rawData.user.id) || 0,
status: Number(rawData.user.status), status: Number(rawData.user.status),
@@ -93,7 +96,7 @@ export function processFormData(rawData) {
licence: { licence: {
id: Number(rawData.user.licence?.id) || 0, id: Number(rawData.user.licence?.id) || 0,
status: Number(rawData.user.licence?.status), status: Number(rawData.user.licence?.status),
licence_number: String(rawData.user.licence?.licence_number || ''), number: String(rawData.user.licence?.number || ''),
issued_date: toRFC3339(rawData.user.licence?.issued_date), issued_date: toRFC3339(rawData.user.licence?.issued_date),
expiration_date: toRFC3339(rawData.user.licence?.expiration_date), expiration_date: toRFC3339(rawData.user.licence?.expiration_date),
country: String(rawData.user.licence?.country || ''), country: String(rawData.user.licence?.country || ''),
@@ -112,6 +115,15 @@ export function processFormData(rawData) {
} }
}; };
if (
rawData.user.password &&
rawData.password2 &&
rawData.user.password === rawData.password2 &&
rawData.user.password.trim() !== ''
) {
processedData.user.password = rawData.user.password;
}
// Remove undefined or null properties // Remove undefined or null properties
const cleanUpdateData = JSON.parse(JSON.stringify(processedData), (key, value) => const cleanUpdateData = JSON.parse(JSON.stringify(processedData), (key, value) =>
value !== null && value !== '' ? value : undefined value !== null && value !== '' ? value : undefined

View File

@@ -36,7 +36,7 @@ export const actions = {
console.log('Is creating: ', isCreating); console.log('Is creating: ', isCreating);
// console.dir(formData); // console.dir(formData);
console.dir(processedData.user.membership); console.dir(processedData.user.membership);
const apiURL = `${BASE_API_URI}/backend/users/update/`; const apiURL = `${BASE_API_URI}/backend/users/upsert/`;
/** @type {RequestInit} */ /** @type {RequestInit} */
const requestUpdateOptions = { const requestUpdateOptions = {

View File

@@ -34,7 +34,7 @@ export const actions = {
console.dir(processedData.user.membership); console.dir(processedData.user.membership);
const isCreating = !processedData.user.id || processedData.user.id === 0; const isCreating = !processedData.user.id || processedData.user.id === 0;
console.log('Is creating: ', isCreating); console.log('Is creating: ', isCreating);
const apiURL = `${BASE_API_URI}/backend/users/update`; const apiURL = `${BASE_API_URI}/backend/users/upsert`;
/** @type {RequestInit} */ /** @type {RequestInit} */
const requestOptions = { const requestOptions = {
@@ -59,6 +59,6 @@ export const actions = {
console.log('Server success response:', response); console.log('Server success response:', response);
locals.user = response; locals.user = response;
userDatesFromRFC3339(locals.user); userDatesFromRFC3339(locals.user);
throw redirect(303, `/auth/about/${response.id}`); throw redirect(303, `/auth/admin/users`);
} }
}; };

View File

@@ -223,6 +223,7 @@
{subscriptions} {subscriptions}
{licence_categories} {licence_categories}
on:cancel={close} on:cancel={close}
on:close={close}
/> />
</Modal> </Modal>
{/if} {/if}

View File

@@ -95,6 +95,36 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser.Safe()}) c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser.Safe()})
} }
func (uc *UserController) DeleteUser(c *gin.Context) {
requestUser, err := uc.extractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw")
return
}
type deleteData = struct {
ID uint `json:"id"`
LastName string `json:"lastname"`
}
var deletedUser deleteData
if err := c.ShouldBindJSON(&deletedUser); err != nil {
utils.HandleValidationError(c, err)
return
}
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) && deletedUser.ID != requestUser.ID {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to delete user", http.StatusForbidden, "user", "server.error.unauthorized")
return
}
if err := uc.Service.DeleteUser(deletedUser.LastName, deletedUser.ID); err != nil {
utils.RespondWithError(c, err, "Error during user deletion", http.StatusInternalServerError, "user", "server.error.internal_server_error")
return
}
}
func (uc *UserController) extractUserFromContext(c *gin.Context) (*models.User, error) { func (uc *UserController) extractUserFromContext(c *gin.Context) (*models.User, error) {
tokenString, err := c.Cookie("jwt") tokenString, err := c.Cookie("jwt")

View File

@@ -6,8 +6,8 @@ type BankAccount struct {
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"` MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"`
Bank string `json:"bank_name" binding:"omitempty,alphanumunicode,safe_content"` Bank string `json:"bank_name" binding:"safe_content"`
AccountHolderName string `json:"account_holder_name" binding:"omitempty,alphaunicode,safe_content"` AccountHolderName string `json:"account_holder_name" binding:"safe_content"`
IBAN string `json:"iban"` IBAN string `json:"iban"`
BIC string `json:"bic"` BIC string `json:"bic"`
MandateReference string `gorm:"not null" json:"mandate_reference"` MandateReference string `gorm:"not null" json:"mandate_reference"`

View File

@@ -21,10 +21,15 @@ type UserRepositoryInterface interface {
SetVerificationToken(verification *models.Verification) (uint, error) SetVerificationToken(verification *models.Verification) (uint, error)
IsVerified(userID *uint) (bool, error) IsVerified(userID *uint) (bool, error)
GetVerificationOfToken(token *string) (*models.Verification, error) GetVerificationOfToken(token *string) (*models.Verification, error)
DeleteUser(id uint) error
} }
type UserRepository struct{} type UserRepository struct{}
func (ur *UserRepository) DeleteUser(id uint) error {
return database.DB.Delete(&models.User{}, "id = ?", id).Error
}
func PasswordExists(userID *uint) (bool, error) { func PasswordExists(userID *uint) (bool, error) {
var user models.User var user models.User
result := database.DB.Select("password").First(&user, userID) result := database.DB.Select("password").First(&user, userID)
@@ -60,7 +65,7 @@ func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
return err return err
} }
// Update the user's main fields // Update the user's main fields
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(user) result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password").Updates(user)
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error
} }
@@ -68,6 +73,14 @@ func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
return errors.ErrNoRowsAffected return errors.ErrNoRowsAffected
} }
if user.Password != "" {
if err := tx.Model(&models.User{}).
Where("id = ?", user.ID).
Update("Password", user.Password).Error; err != nil {
return err
}
}
// Update the Membership if provided // Update the Membership if provided
if user.Membership.ID != 0 { if user.Membership.ID != 0 {
if err := tx.Model(&existingUser.Membership).Updates(user.Membership).Error; err != nil { if err := tx.Model(&existingUser.Membership).Updates(user.Membership).Error; err != nil {

View File

@@ -26,9 +26,10 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
{ {
userRouter.GET("/current", userController.CurrentUserHandler) userRouter.GET("/current", userController.CurrentUserHandler)
userRouter.POST("/logout", userController.LogoutHandler) userRouter.POST("/logout", userController.LogoutHandler)
userRouter.PATCH("/update", userController.UpdateHandler) userRouter.PATCH("/upsert", userController.UpdateHandler)
userRouter.POST("/update", userController.RegisterUser) userRouter.POST("/upsert", userController.RegisterUser)
userRouter.GET("/all", userController.GetAllUsers) userRouter.GET("/all", userController.GetAllUsers)
userRouter.DELETE("/delete", userController.DeleteUser)
} }
membershipRouter := router.Group("/backend/membership") membershipRouter := router.Group("/backend/membership")

View File

@@ -24,6 +24,7 @@ type UserServiceInterface interface {
GetUsers(where map[string]interface{}) (*[]models.User, error) GetUsers(where map[string]interface{}) (*[]models.User, error)
VerifyUser(token *string) (*models.User, error) VerifyUser(token *string) (*models.User, error)
UpdateUser(user *models.User) (*models.User, error) UpdateUser(user *models.User) (*models.User, error)
DeleteUser(lastname string, id uint) error
} }
type UserService struct { type UserService struct {
@@ -31,6 +32,22 @@ type UserService struct {
Licences repositories.LicenceInterface Licences repositories.LicenceInterface
} }
func (service *UserService) DeleteUser(lastname string, id uint) error {
if id == 0 || lastname == "" {
return errors.ErrNoData
}
user, err := service.GetUserByID(id)
if err != nil {
return err
}
if user == nil {
return errors.ErrUserNotFound
}
return service.Repo.DeleteUser(id)
}
func (service *UserService) UpdateUser(user *models.User) (*models.User, error) { func (service *UserService) UpdateUser(user *models.User) (*models.User, error) {
if user.ID == 0 { if user.ID == 0 {

View File

@@ -2,7 +2,6 @@ package validation
import ( import (
"GoMembership/internal/models" "GoMembership/internal/models"
"strconv"
"time" "time"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
@@ -10,7 +9,8 @@ import (
func validateDriverslicence(sl validator.StructLevel) { func validateDriverslicence(sl validator.StructLevel) {
dl := sl.Current().Interface().(models.User).Licence dl := sl.Current().Interface().(models.User).Licence
if !validateLicence(dl.Number) { // if !vValidateLicence(dl.Number) {
if dl.Number == "" {
sl.ReportError(dl.Number, "licence_number", "", "invalid", "") sl.ReportError(dl.Number, "licence_number", "", "invalid", "")
} }
if dl.IssuedDate.After(time.Now()) { if dl.IssuedDate.After(time.Now()) {
@@ -21,32 +21,33 @@ func validateDriverslicence(sl validator.StructLevel) {
} }
} }
func validateLicence(fieldValue string) bool { // seems like not every country has to have an licence id and it seems that germany changed their id generation type..
if len(fieldValue) != 11 { // func validateLicence(fieldValue string) bool {
return false // if len(fieldValue) != 11 {
} // return false
// }
id, tenthChar := string(fieldValue[:9]), string(fieldValue[9]) // id, tenthChar := string(fieldValue[:9]), string(fieldValue[9])
if tenthChar == "X" { // if tenthChar == "X" {
tenthChar = "10" // tenthChar = "10"
} // }
tenthValue, _ := strconv.ParseInt(tenthChar, 10, 8) // tenthValue, _ := strconv.ParseInt(tenthChar, 10, 8)
// for readability // // for readability
weights := []int{9, 8, 7, 6, 5, 4, 3, 2, 1} // weights := []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
sum := 0 // sum := 0
for i := 0; i < 9; i++ { // for i := 0; i < 9; i++ {
char := string(id[i]) // char := string(id[i])
value, _ := strconv.ParseInt(char, 36, 64) // value, _ := strconv.ParseInt(char, 36, 64)
sum += int(value) * weights[i] // sum += int(value) * weights[i]
} // }
calcCheckDigit := sum % 11 // calcCheckDigit := sum % 11
if calcCheckDigit != int(tenthValue) { // if calcCheckDigit != int(tenthValue) {
return false // return false
} // }
return true // return true
} // }

View File

@@ -27,7 +27,6 @@ func validateUser(sl validator.StructLevel) {
sl.ReportError(user.DateOfBirth, "DateOfBirth", "dateofbirth", "age", "") sl.ReportError(user.DateOfBirth, "DateOfBirth", "dateofbirth", "age", "")
} }
// validate subscriptionModel // validate subscriptionModel
logger.Error.Printf("User SubscriptionModel.Name: %#v", user.Membership.SubscriptionModel.Name)
if user.Membership.SubscriptionModel.Name == "" { if user.Membership.SubscriptionModel.Name == "" {
sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "") sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "")
} else { } else {