package account import ( "errors" "github.com/get-drexa/drexa/internal/auth" "github.com/get-drexa/drexa/internal/httperr" "github.com/get-drexa/drexa/internal/reqctx" "github.com/get-drexa/drexa/internal/user" "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/uptrace/bun" ) type HTTPHandler struct { accountService *Service authService *auth.Service db *bun.DB authMiddleware fiber.Handler } // registerAccountRequest represents a new account registration // @Description Request to create a new account and user type registerAccountRequest struct { // Email address for the new account Email string `json:"email" example:"newuser@example.com"` // Password for the new account (min 8 characters) Password string `json:"password" example:"securepassword123"` // Display name for the user DisplayName string `json:"displayName" example:"Jane Doe"` } // registerAccountResponse represents a successful registration // @Description Response after successful account registration type registerAccountResponse struct { // The created account Account *Account `json:"account"` // The created user User *user.User `json:"user"` // JWT access token for immediate authentication AccessToken string `json:"accessToken" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAifQ.signature"` // Base64 URL encoded refresh token RefreshToken string `json:"refreshToken" example:"dR4nD0mUu1DkZXlCeXRlc0FuZFJhbmRvbURhdGFIZXJlMTIzNDU2Nzg5MGFi"` } const currentAccountKey = "currentAccount" func CurrentAccount(c *fiber.Ctx) *Account { return c.Locals(currentAccountKey).(*Account) } func NewHTTPHandler(accountService *Service, authService *auth.Service, db *bun.DB, authMiddleware fiber.Handler) *HTTPHandler { return &HTTPHandler{accountService: accountService, authService: authService, db: db, authMiddleware: authMiddleware} } func (h *HTTPHandler) RegisterRoutes(api fiber.Router) fiber.Router { api.Get("/accounts", h.authMiddleware, h.listAccounts) api.Post("/accounts", h.registerAccount) account := api.Group("/accounts/:accountID") account.Use(h.authMiddleware) account.Use(h.accountMiddleware) account.Get("/", h.getAccount) return account } func (h *HTTPHandler) accountMiddleware(c *fiber.Ctx) error { u := reqctx.AuthenticatedUser(c).(*user.User) accountID, err := uuid.Parse(c.Params("accountID")) if err != nil { return c.SendStatus(fiber.StatusNotFound) } account, err := h.accountService.AccountByID(c.Context(), h.db, u.ID, accountID) if err != nil { if errors.Is(err, ErrAccountNotFound) { return c.SendStatus(fiber.StatusNotFound) } return httperr.Internal(err) } c.Locals(currentAccountKey, account) return c.Next() } // listAccounts lists all accounts for the authenticated user // @Summary List accounts // @Description Retrieve all accounts for the authenticated user // @Tags accounts // @Produce json // @Security BearerAuth // @Success 200 {array} Account "List of accounts for the authenticated user" // @Failure 401 {string} string "Not authenticated" // @Router /accounts [get] func (h *HTTPHandler) listAccounts(c *fiber.Ctx) error { u := reqctx.AuthenticatedUser(c).(*user.User) accounts, err := h.accountService.ListAccounts(c.Context(), h.db, u.ID) if err != nil { return httperr.Internal(err) } return c.JSON(accounts) } // getAccount retrieves account information // @Summary Get account // @Description Retrieve account details including storage usage and quota // @Tags accounts // @Produce json // @Security BearerAuth // @Param accountID path string true "Account ID" format(uuid) // @Success 200 {object} Account "Account details" // @Failure 401 {string} string "Not authenticated" // @Failure 404 {string} string "Account not found" // @Router /accounts/{accountID} [get] func (h *HTTPHandler) getAccount(c *fiber.Ctx) error { account := CurrentAccount(c) if account == nil { return c.SendStatus(fiber.StatusNotFound) } return c.JSON(account) } // registerAccount creates a new account and user // @Summary Register new account // @Description Create a new user account with email and password. Returns the account, user, and authentication tokens. // @Tags accounts // @Accept json // @Produce json // @Param request body registerAccountRequest true "Registration details" // @Success 200 {object} registerAccountResponse "Account created successfully" // @Failure 400 {string} string "Invalid request body" // @Failure 409 {string} string "Email already registered" // @Router /accounts [post] func (h *HTTPHandler) registerAccount(c *fiber.Ctx) error { req := new(registerAccountRequest) if err := c.BodyParser(req); err != nil { return c.SendStatus(fiber.StatusBadRequest) } tx, err := h.db.BeginTx(c.Context(), nil) if err != nil { return httperr.Internal(err) } defer tx.Rollback() acc, u, err := h.accountService.Register(c.Context(), tx, RegisterOptions{ Email: req.Email, Password: req.Password, DisplayName: req.DisplayName, }) if err != nil { var ae *user.AlreadyExistsError if errors.As(err, &ae) { return c.SendStatus(fiber.StatusConflict) } if errors.Is(err, ErrAccountAlreadyExists) { return c.SendStatus(fiber.StatusConflict) } return httperr.Internal(err) } result, err := h.authService.GrantForUser(c.Context(), tx, u) if err != nil { return httperr.Internal(err) } err = tx.Commit() if err != nil { return httperr.Internal(err) } return c.JSON(registerAccountResponse{ Account: acc, User: u, AccessToken: result.AccessToken, RefreshToken: result.RefreshToken, }) }