package users import ( "encoding/base64" "errors" "server/internal/db" "server/internal/systemUtils" "time" "github.com/gofiber/fiber/v3" "github.com/google/uuid" "gorm.io/gorm" ) func repositoryDB() (*gorm.DB, error) { database, err := db.GetDB() if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "database unavailable") } return database, nil } func CreateUser(createUserRequest UserCreateRequest) (*User, error) { database, err := repositoryDB() if err != nil { return nil, err } var existing User if err := database.Where("email = ?", createUserRequest.Email).First(&existing).Error; err == nil { return nil, fiber.NewError(fiber.StatusConflict, "user already exists") } else if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to check user") } hashedPassword, err := systemUtils.HashPassword(createUserRequest.Password) if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to secure password") } now := time.Now().UTC() user := User{ Email: createUserRequest.Email, Name: createUserRequest.Name, Password: hashedPassword, Permission: createUserRequest.Permission, Status: func() UserStatus { if createUserRequest.Status == "" { return UserStatusPending } return createUserRequest.Status }(), Type: func() UserType { if createUserRequest.Type == "" { return UserType("internal") } return createUserRequest.Type }(), Avatar: func() *string { if createUserRequest.Avatar == nil { return nil } return createUserRequest.Avatar }(), ID: uuid.NewString(), Details: func() *UserDetails { if createUserRequest.Details == nil { return nil } return createUserRequest.Details }(), Preferences: func() *UserPreferences { if createUserRequest.Preferences == nil { return nil } return createUserRequest.Preferences }(), CreatedAt: &now, UpdatedAt: &now, } if err := database.Create(&user).Error; err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to create user") } return GetUserByID(user.ID) } func UpdateUser(updateUserRequest UpdateUserRequest) (*User, error) { database, err := repositoryDB() if err != nil { return nil, err } user, err := GetUserByID(updateUserRequest.ID) if err != nil { return nil, err } now := time.Now().UTC() user.Name = updateUserRequest.Name user.Email = updateUserRequest.Email user.UpdatedAt = &now user.Permission = updateUserRequest.Permission if updateUserRequest.Status != "" { user.Status = updateUserRequest.Status } if updateUserRequest.Type != "" { user.Type = updateUserRequest.Type } user.Permission = updateUserRequest.Permission if err := database.Transaction(func(tx *gorm.DB) error { if err := tx.Save(user).Error; err != nil { return err } return nil }); err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to update user") } return GetUserByID(user.ID) } func UpdateUserDetails(updateUserDetails UpdateUserDetailsRequest) error { database, err := repositoryDB() if err != nil { return err } if updateUserDetails.UserID == "" { return fiber.NewError(fiber.StatusBadRequest, "invalid user id") } input := &UserDetails{ UserID: updateUserDetails.UserID, Title: updateUserDetails.Title, FirstName: updateUserDetails.FirstName, LastName: updateUserDetails.LastName, Address: updateUserDetails.Address, City: updateUserDetails.City, ZipCode: updateUserDetails.ZipCode, Country: updateUserDetails.Country, Phone: updateUserDetails.Phone, } if err := database.Transaction(func(tx *gorm.DB) error { return syncUserDetails(tx, updateUserDetails.UserID, input) }); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to update user details") } return nil } func UpdateUserPreferences(updateUserPreferences UpdateUserPreferencesRequest) error { database, err := repositoryDB() if err != nil { return err } if updateUserPreferences.UserID == "" { return fiber.NewError(fiber.StatusBadRequest, "invalid user id") } input := &UserPreferences{ UserID: updateUserPreferences.UserID, UseIdle: updateUserPreferences.UseIdle, IdleTimeout: updateUserPreferences.IdleTimeout, UseIdlePassword: updateUserPreferences.UseIdlePassword, IdlePin: updateUserPreferences.IdlePin, UseDirectLogin: updateUserPreferences.UseDirectLogin, UseQuadcodeLogin: updateUserPreferences.UseQuadcodeLogin, SendNoticesMail: updateUserPreferences.SendNoticesMail, Language: updateUserPreferences.Language, } if err := database.Transaction(func(tx *gorm.DB) error { return syncUserPreferences(tx, updateUserPreferences.UserID, input) }); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to update user preferences") } return nil } func UpdateUserAvatar(updateUserAvatarRequest UpdateUserAvatarRequest) (*User, error) { database, err := repositoryDB() if err != nil { return nil, err } user, err := GetUserByID(updateUserAvatarRequest.ID) if err != nil { return nil, err } avatar := base64.StdEncoding.EncodeToString(updateUserAvatarRequest.Img) now := time.Now().UTC() if err := database.Model(user).Updates(map[string]any{ "avatar": avatar, "updated_at": now, }).Error; err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to update user avatar") } return GetUserByID(user.ID) } func UpdateUserPassword(req UpdatePasswordRequest, id string) error { database, err := repositoryDB() if err != nil { return err } user := User{} if err := database.Where("uuid = ?", id).First(&user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "user not found") } return fiber.NewError(fiber.StatusInternalServerError, "failed to load user") } hashedPassword, err := systemUtils.HashPassword(req.Password) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password") } now := time.Now().UTC() if err := database.Model(&user).Updates(map[string]any{ "password": hashedPassword, "updated_at": now, }).Error; err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to update password") } if err := database.Where("user_id = ?", user.ID).Delete(&Session{}).Error; err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to revoke sessions") } return nil } func GetUserByID(id string) (*User, error) { if id == "" { return nil, fiber.NewError(fiber.StatusBadRequest, "invalid user id") } database, err := repositoryDB() if err != nil { return nil, err } var user User if err := database.Preload("Details").Preload("Preferences").First(&user, "id = ?", id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "user not found") } return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to load user") } return &user, nil } func DeleteUser(uuid string) error { database, err := repositoryDB() if err != nil { return err } user, err := GetUserByID(uuid) if err != nil { return err } if err := database.Transaction(func(tx *gorm.DB) error { if err := tx.Where("user_id = ?", user.ID).Delete(&UserDetails{}).Error; err != nil { return err } if err := tx.Where("user_id = ?", user.ID).Delete(&UserPreferences{}).Error; err != nil { return err } return tx.Delete(user).Error }); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to delete user") } return nil } func syncUserDetails(tx *gorm.DB, userID string, input *UserDetails) error { if input == nil { return tx.Where("user_id = ?", userID).Delete(&UserDetails{}).Error } var details UserDetails if err := tx.Where("user_id = ?", userID).First(&details).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { details = UserDetails{UserID: userID} } else { return err } } details.Title = input.Title details.FirstName = input.FirstName details.LastName = input.LastName details.Address = input.Address details.City = input.City details.ZipCode = input.ZipCode details.Country = input.Country details.Phone = input.Phone if details.ID == 0 { return tx.Create(&details).Error } return tx.Save(&details).Error } func syncUserPreferences(tx *gorm.DB, userID string, input *UserPreferences) error { if input == nil { return tx.Where("user_id = ?", userID).Delete(&UserPreferences{}).Error } var preferences UserPreferences if err := tx.Where("user_id = ?", userID).First(&preferences).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { preferences = UserPreferences{UserID: userID} } else { return err } } preferences.UseIdle = input.UseIdle preferences.IdleTimeout = input.IdleTimeout preferences.UseIdlePassword = input.UseIdlePassword preferences.IdlePin = input.IdlePin preferences.UseDirectLogin = input.UseDirectLogin preferences.UseQuadcodeLogin = input.UseQuadcodeLogin preferences.SendNoticesMail = input.SendNoticesMail preferences.Language = input.Language if preferences.ID == 0 { return tx.Create(&preferences).Error } return tx.Save(&preferences).Error }