package handlers import ( "errors" "net/http" "git.threesix.ai/jordan/slack-auth-1770277926/pkg/app" "git.threesix.ai/jordan/slack-auth-1770277926/pkg/auth" "git.threesix.ai/jordan/slack-auth-1770277926/pkg/httperror" "git.threesix.ai/jordan/slack-auth-1770277926/pkg/httpresponse" "git.threesix.ai/jordan/slack-auth-1770277926/pkg/logging" "git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/domain" "git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/service" ) // User handles HTTP requests for user/auth resources. type User struct { svc *service.UserService logger *logging.Logger } // NewUser creates a new User handler with injected dependencies. func NewUser(svc *service.UserService, logger *logging.Logger) *User { return &User{ svc: svc, logger: logger.WithComponent("UserHandler"), } } // RegisterRequest is the request body for user registration. type RegisterRequest struct { Email string `json:"email" validate:"required,email,max=254"` Password string `json:"password" validate:"required,min=8,max=72"` } // LoginRequest is the request body for user login. type LoginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required"` } // UserResponse is the response for a user resource (excludes password). type UserResponse struct { ID string `json:"id"` Email string `json:"email"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // AuthResponse is the response for authentication endpoints. type AuthResponse struct { User UserResponse `json:"user"` Token string `json:"token"` } // toUserResponse converts a domain user to an API response. func toUserResponse(u *domain.User) UserResponse { return UserResponse{ ID: u.ID.String(), Email: u.Email, CreatedAt: u.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: u.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // Register creates a new user account. func (h *User) Register(w http.ResponseWriter, r *http.Request) error { var req RegisterRequest if err := app.BindAndValidate(r, &req); err != nil { return err } output, err := h.svc.Register(r.Context(), service.RegisterInput{ Email: req.Email, Password: req.Password, }) if err != nil { return mapUserDomainError(err) } httpresponse.Created(w, r, AuthResponse{ User: toUserResponse(output.User), Token: output.Token, }) return nil } // Login authenticates a user and returns a JWT token. func (h *User) Login(w http.ResponseWriter, r *http.Request) error { var req LoginRequest if err := app.BindAndValidate(r, &req); err != nil { return err } output, err := h.svc.Login(r.Context(), service.LoginInput{ Email: req.Email, Password: req.Password, }) if err != nil { return mapUserDomainError(err) } httpresponse.OK(w, r, AuthResponse{ User: toUserResponse(output.User), Token: output.Token, }) return nil } // Me returns the current authenticated user's profile. func (h *User) Me(w http.ResponseWriter, r *http.Request) error { // Get authenticated user from context authUser := auth.GetUser(r.Context()) if authUser == nil { return httperror.Unauthorized("authentication required") } // Fetch full user data from service user, err := h.svc.GetByID(r.Context(), domain.UserID(authUser.ID)) if err != nil { return mapUserDomainError(err) } httpresponse.OK(w, r, toUserResponse(user)) return nil } // mapUserDomainError converts domain errors to HTTP errors. func mapUserDomainError(err error) error { switch { case errors.Is(err, domain.ErrUserNotFound): return httperror.NotFound("user not found") case errors.Is(err, domain.ErrDuplicateUser): return httperror.Conflict("user with this email already exists") case errors.Is(err, domain.ErrInvalidEmail): return httperror.BadRequest("invalid email address") case errors.Is(err, domain.ErrInvalidPassword): return httperror.BadRequest("password must be between 8 and 72 characters") case errors.Is(err, domain.ErrInvalidCredentials): return httperror.Unauthorized("invalid email or password") default: return err } }