145 lines
4.1 KiB
Go
145 lines
4.1 KiB
Go
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
|
|
}
|
|
}
|