695 lines
20 KiB
Go
695 lines
20 KiB
Go
package users
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
|
|
"server/internal/db"
|
|
"server/internal/mail"
|
|
"server/internal/responses"
|
|
"server/internal/systemUtils"
|
|
"server/internal/tokens"
|
|
|
|
"server/internal/validation"
|
|
)
|
|
|
|
type UserController struct {
|
|
TockenService *tokens.TockenService
|
|
}
|
|
|
|
func NewUserController(tockenService *tokens.TockenService) *UserController {
|
|
return &UserController{
|
|
TockenService: tockenService,
|
|
}
|
|
}
|
|
|
|
// Typescript: interface
|
|
type UpdateUserRequest struct {
|
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
|
Email string `json:"email" validate:"required,email"`
|
|
Password string `json:"password" validate:"omitempty,min=8,max=128"`
|
|
Roles UserRoles `json:"roles"`
|
|
Status UserStatus `json:"status"`
|
|
Types UserTypes `json:"types"`
|
|
Avatar *string `json:"avatar"`
|
|
Details *UserDetails `json:"details"`
|
|
Preferences *UserPreferences `json:"preferences"`
|
|
}
|
|
|
|
// GetUser returns a single user by UUID.
|
|
func (uc *UserController) GetUser(c fiber.Ctx) error {
|
|
user, err := loadUserByUUID(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.JSON(responses.Success(ToUserProfile(user)))
|
|
}
|
|
|
|
// CreateUser creates a user together with optional details and preferences.
|
|
func (uc *UserController) CreateUser(c fiber.Ctx) error {
|
|
var req UserCreateInput
|
|
if err := c.Bind().Body(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
if err := validation.ValidateStruct(&req); err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var existing User
|
|
if err := db.Where("email = ?", req.Email).First(&existing).Error; err == nil {
|
|
return fiber.NewError(fiber.StatusConflict, "user already exists")
|
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
|
|
}
|
|
|
|
hashedPassword, err := systemUtils.HashPassword(req.Password)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password")
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
user := User{
|
|
Email: req.Email,
|
|
Name: req.Name,
|
|
Password: hashedPassword,
|
|
Roles: func() UserRoles {
|
|
if len(req.Roles) == 0 {
|
|
return UserRoles{"user"}
|
|
}
|
|
return req.Roles
|
|
}(),
|
|
Status: func() UserStatus {
|
|
if req.Status == "" {
|
|
return UserStatusPending
|
|
}
|
|
return req.Status
|
|
}(),
|
|
Types: func() UserTypes {
|
|
if len(req.Types) == 0 {
|
|
return UserTypes{"internal"}
|
|
}
|
|
return req.Types
|
|
}(),
|
|
Avatar: req.Avatar,
|
|
UUID: uuid.NewString(),
|
|
Details: req.Details,
|
|
Preferences: req.Preferences,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
if err := db.Create(&user).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to create user")
|
|
}
|
|
|
|
if err := db.Preload("Details").Preload("Preferences").First(&user, user.ID).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to reload user")
|
|
}
|
|
|
|
return c.Status(fiber.StatusCreated).JSON(responses.Success(ToUserProfile(&user)))
|
|
}
|
|
|
|
// UpdateUser replaces user fields and synchronizes details/preferences.
|
|
func (uc *UserController) UpdateUser(c fiber.Ctx) error {
|
|
var req UpdateUserRequest
|
|
if err := c.Bind().Body(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
if err := validation.ValidateStruct(&req); err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
user, err := loadUserByUUID(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if req.Email != user.Email {
|
|
var existing User
|
|
if err := db.Select("id").Where("email = ?", req.Email).First(&existing).Error; err == nil && existing.ID != user.ID {
|
|
return fiber.NewError(fiber.StatusConflict, "user already exists")
|
|
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
|
|
}
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
user.Name = req.Name
|
|
user.Email = req.Email
|
|
user.Avatar = req.Avatar
|
|
user.UpdatedAt = now
|
|
if req.Status != "" {
|
|
user.Status = req.Status
|
|
}
|
|
if len(req.Roles) > 0 {
|
|
user.Roles = req.Roles
|
|
}
|
|
if len(req.Types) > 0 {
|
|
user.Types = req.Types
|
|
}
|
|
|
|
if err := db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Save(user).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := syncUserDetails(tx, user.ID, req.Details); err != nil {
|
|
return err
|
|
}
|
|
if err := syncUserPreferences(tx, user.ID, req.Preferences); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to update user")
|
|
}
|
|
|
|
if err := db.Preload("Details").Preload("Preferences").First(user, user.ID).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to reload user")
|
|
}
|
|
|
|
return c.JSON(responses.Success(ToUserProfile(user)))
|
|
}
|
|
|
|
// DeleteUser removes a user and linked details/preferences through cascading delete rules.
|
|
func (uc *UserController) DeleteUser(c fiber.Ctx) error {
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
user, err := loadUserByID(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := db.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 c.JSON(responses.Success(responses.SimpleResponse{Message: "user deleted"}))
|
|
}
|
|
|
|
// Login authenticates a user and issues an access/refresh token pair.
|
|
func (uc *UserController) Login(c fiber.Ctx) error {
|
|
var req LoginRequest
|
|
if err := c.Bind().Body(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
if err := validation.ValidateStruct(&req); err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var user User
|
|
if err := db.Where("email = ?", req.Username).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusUnauthorized, "invalid credentials")
|
|
}
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to fetch user")
|
|
}
|
|
match, err := systemUtils.VerifyPassword(user.Password, req.Password)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to verify credentials")
|
|
}
|
|
if !match {
|
|
return fiber.NewError(fiber.StatusUnauthorized, "invalid credentials")
|
|
}
|
|
|
|
token, err := uc.TockenService.GenerateTokenPair(user.Email)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to issue token")
|
|
}
|
|
|
|
userID := user.ID
|
|
now := time.Now().UTC()
|
|
if err := db.Where("expires_at < ?", now).Delete(&Session{}).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to purge expired sessions")
|
|
}
|
|
|
|
session := Session{
|
|
UserID: &userID,
|
|
Username: user.Email,
|
|
AccessTokenHash: tokens.HashToken(token.AccessToken),
|
|
RefreshTokenHash: tokens.HashToken(token.RefreshToken),
|
|
ExpiresAt: now.Add(uc.TockenService.RefreshExpiry()),
|
|
IPAddress: c.IP(),
|
|
UserAgent: c.Get("User-Agent"),
|
|
}
|
|
|
|
if err := db.Create(&session).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to record session")
|
|
}
|
|
|
|
c.Response().Header.Set("Auth-Token", token.AccessToken)
|
|
|
|
return c.JSON(responses.Success(token))
|
|
}
|
|
|
|
// Register creates a new user with optional roles/types/preferences.
|
|
func (uc *UserController) Register(c fiber.Ctx) error {
|
|
var req UserCreateInput
|
|
if err := c.Bind().Body(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
if err := validation.ValidateStruct(&req); err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var existing User
|
|
if err := db.Where("email = ?", req.Email).First(&existing).Error; err == nil {
|
|
return fiber.NewError(fiber.StatusConflict, "user already exists")
|
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
hashedPassword, err := systemUtils.HashPassword(req.Password)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password")
|
|
}
|
|
user := User{
|
|
Email: req.Email,
|
|
Name: req.Name,
|
|
Password: hashedPassword,
|
|
Roles: func() UserRoles {
|
|
if len(req.Roles) == 0 {
|
|
return UserRoles{"user"}
|
|
}
|
|
return req.Roles
|
|
}(),
|
|
Status: func() UserStatus {
|
|
if req.Status == "" {
|
|
return UserStatusPending
|
|
}
|
|
return req.Status
|
|
}(),
|
|
Types: func() UserTypes {
|
|
if len(req.Types) == 0 {
|
|
return UserTypes{"internal"}
|
|
}
|
|
return req.Types
|
|
}(),
|
|
Avatar: req.Avatar,
|
|
UUID: uuid.NewString(),
|
|
Details: req.Details,
|
|
Preferences: func() *UserPreferences {
|
|
if req.Preferences == nil {
|
|
return nil
|
|
}
|
|
return &UserPreferences{
|
|
UseIdle: req.Preferences.UseIdle,
|
|
IdleTimeout: req.Preferences.IdleTimeout,
|
|
UseIdlePassword: req.Preferences.UseIdlePassword,
|
|
IdlePin: req.Preferences.IdlePin,
|
|
UseDirectLogin: req.Preferences.UseDirectLogin,
|
|
UseQuadcodeLogin: req.Preferences.UseQuadcodeLogin,
|
|
SendNoticesMail: req.Preferences.SendNoticesMail,
|
|
Language: req.Preferences.Language,
|
|
}
|
|
}(),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
if err := db.Create(&user).Error; err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to create user")
|
|
}
|
|
|
|
mailService, err := mail.New()
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to initialize mail service")
|
|
}
|
|
|
|
if err := mailService.Send(c, mail.Message{
|
|
To: user.Email,
|
|
Subject: fmt.Sprintf("[%s] Registrazione completata", mailService.AppName()),
|
|
Template: "registration",
|
|
TemplateData: mail.TemplateData{
|
|
AppName: mailService.AppName(),
|
|
UserName: user.Name,
|
|
UserEmail: user.Email,
|
|
},
|
|
}); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to send registration email")
|
|
}
|
|
|
|
return c.Status(fiber.StatusCreated).JSON(responses.Success(&user))
|
|
}
|
|
|
|
func (uc *UserController) ForgotPassword(c fiber.Ctx) error {
|
|
var req ForgotPasswordRequest
|
|
if err := c.Bind().Body(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
if err := validation.ValidateStruct(&req); err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var user User
|
|
if err := db.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return c.JSON(responses.Success(fiber.Map{"sent": true}))
|
|
}
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to load user")
|
|
}
|
|
|
|
if user.Status == UserStatusDisabled {
|
|
return c.JSON(responses.Success(fiber.Map{"sent": true}))
|
|
}
|
|
|
|
resetToken, err := uc.TockenService.GenerateSecureToken()
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate reset token")
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
record := PasswordResetToken{
|
|
UserID: user.ID,
|
|
TokenHash: tokens.HashToken(resetToken),
|
|
ExpiresAt: now.Add(30 * time.Minute),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
if err := db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Where("user_id = ? OR expires_at < ? OR used_at IS NOT NULL", user.ID, now).
|
|
Delete(&PasswordResetToken{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Create(&record).Error
|
|
}); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to store reset token")
|
|
}
|
|
|
|
mailService, err := mail.New()
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to initialize mail service")
|
|
}
|
|
if err := mailService.Send(c, mail.Message{
|
|
To: user.Email,
|
|
Subject: fmt.Sprintf("[%s] Recupero password", mailService.AppName()),
|
|
Template: "password_reset",
|
|
TemplateData: mail.TemplateData{
|
|
AppName: mailService.AppName(),
|
|
UserName: user.Name,
|
|
UserEmail: user.Email,
|
|
ResetToken: resetToken,
|
|
ResetURL: mailService.ResetLink(resetToken),
|
|
},
|
|
}); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to send reset email")
|
|
}
|
|
|
|
return c.JSON(responses.Success(responses.SimpleResponse{Message: "password reset email sent"}))
|
|
}
|
|
|
|
func (uc *UserController) ResetPassword(c fiber.Ctx) error {
|
|
var req ResetPasswordRequest
|
|
if err := c.Bind().Body(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
if err := validation.ValidateStruct(&req); err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hashedPassword, err := systemUtils.HashPassword(req.Password)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password")
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
tokenHash := tokens.HashToken(req.Token)
|
|
if err := db.Transaction(func(tx *gorm.DB) error {
|
|
var resetToken PasswordResetToken
|
|
if err := tx.Where("token_hash = ?", tokenHash).First(&resetToken).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
|
|
}
|
|
return err
|
|
}
|
|
if resetToken.UsedAt != nil || now.After(resetToken.ExpiresAt) {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
|
|
}
|
|
|
|
if err := tx.Model(&User{}).Where("id = ?", resetToken.UserID).Updates(map[string]any{
|
|
"password": hashedPassword,
|
|
"updated_at": now,
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Model(&resetToken).Updates(map[string]any{
|
|
"used_at": now,
|
|
"updated_at": now,
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("user_id = ?", resetToken.UserID).Delete(&Session{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("user_id = ? AND id <> ?", resetToken.UserID, resetToken.ID).Delete(&PasswordResetToken{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
var fiberErr *fiber.Error
|
|
if errors.As(err, &fiberErr) {
|
|
return fiberErr
|
|
}
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to reset password")
|
|
}
|
|
|
|
return c.JSON(responses.Success(responses.SimpleResponse{Message: "password reset successful"}))
|
|
}
|
|
|
|
func (uc *UserController) ValidToken(c fiber.Ctx) error {
|
|
raw := strings.TrimSpace(string(c.Body()))
|
|
if raw == "" {
|
|
return fiber.NewError(fiber.StatusBadRequest, "token is required")
|
|
}
|
|
|
|
token := raw
|
|
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
|
|
if err := json.Unmarshal([]byte(raw), &token); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
}
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return fiber.NewError(fiber.StatusBadRequest, "token is required")
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
tokenHash := tokens.HashToken(token)
|
|
var resetToken PasswordResetToken
|
|
if err := db.Where("token_hash = ?", tokenHash).First(&resetToken).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
|
|
}
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to validate reset token")
|
|
}
|
|
|
|
if resetToken.UsedAt != nil || now.After(resetToken.ExpiresAt) {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
|
|
}
|
|
|
|
return c.JSON(responses.Success(responses.SimpleResponse{Message: "valid reset token"}))
|
|
}
|
|
|
|
func loadUserByID(c fiber.Ctx) (*User, error) {
|
|
id, err := strconv.Atoi(c.Params("id"))
|
|
if err != nil || id <= 0 {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, "invalid user id")
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var user User
|
|
if err := db.Preload("Details").Preload("Preferences").First(&user, 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 loadUserByUUID(c fiber.Ctx) (*User, error) {
|
|
uuid := c.Params("uuid")
|
|
if uuid == "" {
|
|
return nil, fiber.NewError(fiber.StatusBadRequest, "invalid user uuid")
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var user User
|
|
if err := db.Preload("Details").Preload("Preferences").First(&user, "uuid = ?", uuid).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 syncUserDetails(tx *gorm.DB, userID int, 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 int, 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
|
|
}
|
|
|
|
// Me returns the authenticated user's profile (short format).
|
|
func (uc *UserController) Me(c fiber.Ctx) error {
|
|
claims, ok := systemUtils.ClaimsFromCtx(c)
|
|
if !ok {
|
|
return fiber.NewError(fiber.StatusUnauthorized, "missing claims")
|
|
}
|
|
|
|
db, err := db.DBFromCtx(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var user User
|
|
if err := db.Preload("Details").Preload("Preferences").Where("email = ?", claims.Username).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")
|
|
}
|
|
|
|
return c.JSON(responses.Success(&user))
|
|
}
|
|
|
|
func (us *UserController) Refresh(c fiber.Ctx) error {
|
|
var req RefreshRequest
|
|
if err := c.Bind().Body(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
|
|
}
|
|
if req.RefreshToken == "" {
|
|
return fiber.NewError(fiber.StatusBadRequest, "refresh_token is required")
|
|
}
|
|
|
|
claims, err := us.TockenService.ParseToken(req.RefreshToken)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusUnauthorized, err.Error())
|
|
}
|
|
if claims.TokenType != tokens.TokenTypeRefresh {
|
|
return fiber.NewError(fiber.StatusUnauthorized, "refresh token required")
|
|
}
|
|
tokens, err := us.TockenService.GenerateTokenPair(claims.Username)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
|
}
|
|
return c.JSON(responses.Success(tokens))
|
|
}
|