mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 14:04:08 +01:00
155 lines
4.7 KiB
Go
155 lines
4.7 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/danielgtaylor/huma/v2"
|
|
"github.com/forfarm/backend/internal/domain"
|
|
"github.com/forfarm/backend/internal/utilities"
|
|
"github.com/go-chi/chi/v5"
|
|
validation "github.com/go-ozzo/ozzo-validation/v4"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
func (a *api) registerUserRoutes(_ chi.Router, api huma.API) {
|
|
tags := []string{"user"}
|
|
prefix := "/user"
|
|
|
|
huma.Register(api, huma.Operation{
|
|
OperationID: "getSelfData",
|
|
Method: http.MethodGet,
|
|
Path: prefix + "/me",
|
|
Tags: tags,
|
|
}, a.getSelfData)
|
|
}
|
|
|
|
type getSelfDataInput struct {
|
|
Authorization string `header:"Authorization" required:"true" example:"Bearer token"`
|
|
}
|
|
|
|
type getSelfDataOutput struct {
|
|
Body struct {
|
|
User domain.User `json:"user"`
|
|
}
|
|
}
|
|
|
|
type UpdateSelfDataInput struct {
|
|
Authorization string `header:"Authorization" required:"true" example:"Bearer token"`
|
|
Body struct {
|
|
Username *string `json:"username,omitempty"`
|
|
}
|
|
}
|
|
|
|
type UpdateSelfDataOutput struct {
|
|
Body struct {
|
|
User domain.User `json:"user"`
|
|
}
|
|
}
|
|
|
|
func (a *api) getSelfData(ctx context.Context, input *getSelfDataInput) (*getSelfDataOutput, error) {
|
|
resp := &getSelfDataOutput{}
|
|
|
|
authHeader := input.Authorization
|
|
if authHeader == "" {
|
|
return nil, huma.Error401Unauthorized("No authorization header provided")
|
|
}
|
|
|
|
authToken := strings.TrimPrefix(authHeader, "Bearer ")
|
|
if authToken == "" {
|
|
return nil, huma.Error401Unauthorized("No token provided in Authorization header")
|
|
}
|
|
|
|
uuid, err := utilities.ExtractUUIDFromToken(authToken)
|
|
if err != nil {
|
|
a.logger.Warn("Failed to extract UUID from token", "error", err)
|
|
return nil, huma.Error401Unauthorized("Invalid or expired token", err)
|
|
}
|
|
|
|
user, err := a.userRepo.GetByUUID(ctx, uuid)
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrNotFound) {
|
|
a.logger.Warn("User data not found for valid token UUID", "user_uuid", uuid)
|
|
return nil, huma.Error404NotFound(fmt.Sprintf("User data not found for UUID: %s", uuid))
|
|
}
|
|
a.logger.Error("Failed to get user data by UUID", "user_uuid", uuid, "error", err)
|
|
return nil, huma.Error500InternalServerError("Failed to retrieve user data")
|
|
}
|
|
|
|
// Ensure password is not included in the response (already handled by `json:"-"`)
|
|
// user.Password = "" // Redundant if json tag is "-"
|
|
|
|
resp.Body.User = user
|
|
return resp, nil
|
|
}
|
|
|
|
func (a *api) updateSelfData(ctx context.Context, input *UpdateSelfDataInput) (*UpdateSelfDataOutput, error) {
|
|
userID, err := a.getUserIDFromHeader(input.Authorization)
|
|
if err != nil {
|
|
return nil, huma.Error401Unauthorized("Authentication failed", err)
|
|
}
|
|
|
|
user, err := a.userRepo.GetByUUID(ctx, userID)
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrNotFound) || errors.Is(err, pgx.ErrNoRows) {
|
|
a.logger.Warn("Attempt to update non-existent user", "user_uuid", userID)
|
|
return nil, huma.Error404NotFound("User not found")
|
|
}
|
|
a.logger.Error("Failed to get user for update", "user_uuid", userID, "error", err)
|
|
return nil, huma.Error500InternalServerError("Failed to retrieve user for update")
|
|
}
|
|
|
|
updated := false
|
|
if input.Body.Username != nil {
|
|
trimmedUsername := strings.TrimSpace(*input.Body.Username)
|
|
if trimmedUsername != user.Username {
|
|
err := validation.Validate(trimmedUsername,
|
|
validation.Required.Error("username cannot be empty if provided"),
|
|
validation.Length(3, 30).Error("username must be between 3 and 30 characters"),
|
|
)
|
|
if err != nil {
|
|
return nil, huma.Error422UnprocessableEntity("Invalid username", err)
|
|
}
|
|
user.Username = trimmedUsername
|
|
updated = true
|
|
}
|
|
}
|
|
// Check other field here la
|
|
|
|
if !updated {
|
|
a.logger.Info("No changes detected for user update", "user_uuid", userID)
|
|
return &UpdateSelfDataOutput{Body: struct {
|
|
User domain.User `json:"user"`
|
|
}{User: user}}, nil
|
|
}
|
|
|
|
// Validate the *entire* user object after updates (optional but good practice)
|
|
// if err := user.Validate(); err != nil {
|
|
// return nil, huma.Error422UnprocessableEntity("Validation failed after update", err)
|
|
// }
|
|
|
|
// Save updated user
|
|
err = a.userRepo.CreateOrUpdate(ctx, &user)
|
|
if err != nil {
|
|
a.logger.Error("Failed to update user in database", "user_uuid", userID, "error", err)
|
|
return nil, huma.Error500InternalServerError("Failed to save user profile")
|
|
}
|
|
|
|
a.logger.Info("User profile updated successfully", "user_uuid", userID)
|
|
|
|
updatedUser, fetchErr := a.userRepo.GetByUUID(ctx, userID)
|
|
if fetchErr != nil {
|
|
a.logger.Error("Failed to fetch user data after update", "user_uuid", userID, "error", fetchErr)
|
|
return &UpdateSelfDataOutput{Body: struct {
|
|
User domain.User `json:"user"`
|
|
}{User: user}}, nil
|
|
}
|
|
|
|
return &UpdateSelfDataOutput{Body: struct {
|
|
User domain.User `json:"user"`
|
|
}{User: updatedUser}}, nil
|
|
}
|