2025-11-30 17:12:50 +00:00
package account
import (
"errors"
"github.com/get-drexa/drexa/internal/auth"
2025-11-30 19:19:33 +00:00
"github.com/get-drexa/drexa/internal/httperr"
2025-12-04 00:48:43 +00:00
"github.com/get-drexa/drexa/internal/reqctx"
2025-11-30 17:12:50 +00:00
"github.com/get-drexa/drexa/internal/user"
2025-12-27 19:27:08 +00:00
"github.com/get-drexa/drexa/internal/virtualfs"
2025-11-30 17:12:50 +00:00
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/uptrace/bun"
)
type HTTPHandler struct {
accountService * Service
authService * auth . Service
2025-12-27 19:27:08 +00:00
vfs * virtualfs . VirtualFS
2025-11-30 17:12:50 +00:00
db * bun . DB
authMiddleware fiber . Handler
2025-12-16 00:41:30 +00:00
cookieConfig auth . CookieConfig
2025-11-30 17:12:50 +00:00
}
2025-12-13 22:44:37 +00:00
// registerAccountRequest represents a new account registration
// @Description Request to create a new account and user
2025-11-30 17:12:50 +00:00
type registerAccountRequest struct {
2025-12-13 22:44:37 +00:00
// 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" `
2025-12-16 00:41:30 +00:00
// How to deliver tokens: "cookie" (set HTTP-only cookies) or "body" (include in response)
TokenDelivery string ` json:"tokenDelivery" example:"body" enums:"cookie,body" `
2025-11-30 17:12:50 +00:00
}
2025-12-13 22:44:37 +00:00
// registerAccountResponse represents a successful registration
// @Description Response after successful account registration
2025-11-30 17:12:50 +00:00
type registerAccountResponse struct {
2025-12-13 22:44:37 +00:00
// The created account
Account * Account ` json:"account" `
// The created user
User * user . User ` json:"user" `
2025-12-16 00:41:30 +00:00
// JWT access token for immediate authentication (only included when tokenDelivery is "body")
AccessToken string ` json:"accessToken,omitempty" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAifQ.signature" `
// Base64 URL encoded refresh token (only included when tokenDelivery is "body")
RefreshToken string ` json:"refreshToken,omitempty" example:"dR4nD0mUu1DkZXlCeXRlc0FuZFJhbmRvbURhdGFIZXJlMTIzNDU2Nzg5MGFi" `
2025-11-30 17:12:50 +00:00
}
2025-12-27 19:27:08 +00:00
func NewHTTPHandler ( accountService * Service , authService * auth . Service , vfs * virtualfs . VirtualFS , db * bun . DB , authMiddleware fiber . Handler , cookieConfig auth . CookieConfig ) * HTTPHandler {
2025-12-16 00:41:30 +00:00
return & HTTPHandler { accountService : accountService , authService : authService , db : db , authMiddleware : authMiddleware , cookieConfig : cookieConfig }
2025-11-30 17:12:50 +00:00
}
2025-12-27 19:27:08 +00:00
func ( h * HTTPHandler ) RegisterRoutes ( api fiber . Router ) * ScopedRouter {
2025-12-15 00:13:10 +00:00
api . Get ( "/accounts" , h . authMiddleware , h . listAccounts )
2025-11-30 17:12:50 +00:00
api . Post ( "/accounts" , h . registerAccount )
account := api . Group ( "/accounts/:accountID" )
account . Use ( h . authMiddleware )
account . Use ( h . accountMiddleware )
account . Get ( "/" , h . getAccount )
2025-12-27 19:27:08 +00:00
return & ScopedRouter { virtualfs . ScopedRouter { account } }
2025-11-30 17:12:50 +00:00
}
func ( h * HTTPHandler ) accountMiddleware ( c * fiber . Ctx ) error {
2025-12-04 00:48:43 +00:00
u := reqctx . AuthenticatedUser ( c ) . ( * user . User )
2025-11-30 17:12:50 +00:00
accountID , err := uuid . Parse ( c . Params ( "accountID" ) )
if err != nil {
return c . SendStatus ( fiber . StatusNotFound )
}
2025-12-04 00:48:43 +00:00
account , err := h . accountService . AccountByID ( c . Context ( ) , h . db , u . ID , accountID )
2025-11-30 17:12:50 +00:00
if err != nil {
if errors . Is ( err , ErrAccountNotFound ) {
return c . SendStatus ( fiber . StatusNotFound )
}
2025-11-30 19:19:33 +00:00
return httperr . Internal ( err )
2025-11-30 17:12:50 +00:00
}
2025-12-27 19:27:08 +00:00
root , err := h . vfs . FindRootDirectory ( c . Context ( ) , h . db , account . ID )
if err != nil {
return httperr . Internal ( err )
}
scope := & virtualfs . Scope {
AccountID : account . ID ,
RootNodeID : root . ID ,
AllowedOps : virtualfs . AllAllowedOps ,
AllowedNodes : nil ,
ActorKind : virtualfs . ScopeActorAccount ,
ActorID : u . ID ,
}
reqctx . SetVFSAccessScope ( c , scope )
reqctx . SetCurrentAccount ( c , account )
2025-11-30 17:12:50 +00:00
return c . Next ( )
}
2025-12-15 00:13:10 +00:00
// 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 )
}
2025-12-13 22:44:37 +00:00
// 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]
2025-11-30 17:12:50 +00:00
func ( h * HTTPHandler ) getAccount ( c * fiber . Ctx ) error {
2025-12-27 19:27:08 +00:00
account , ok := reqctx . CurrentAccount ( c ) . ( * Account )
if ! ok || account == nil {
2025-11-30 17:12:50 +00:00
return c . SendStatus ( fiber . StatusNotFound )
}
return c . JSON ( account )
}
2025-12-13 22:44:37 +00:00
// registerAccount creates a new account and user
// @Summary Register new account
2025-12-16 00:41:30 +00:00
// @Description Create a new user account with email and password. Returns the account, user, and authentication tokens. Tokens can be delivered via HTTP-only cookies or in the response body based on the tokenDelivery field.
2025-12-13 22:44:37 +00:00
// @Tags accounts
// @Accept json
// @Produce json
// @Param request body registerAccountRequest true "Registration details"
// @Success 200 {object} registerAccountResponse "Account created successfully"
2025-12-16 00:41:30 +00:00
// @Failure 400 {string} string "Invalid request body or token delivery method"
2025-12-13 22:44:37 +00:00
// @Failure 409 {string} string "Email already registered"
// @Router /accounts [post]
2025-11-30 17:12:50 +00:00
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 {
2025-11-30 19:19:33 +00:00
return httperr . Internal ( err )
2025-11-30 17:12:50 +00:00
}
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 )
}
2025-11-30 19:19:33 +00:00
return httperr . Internal ( err )
2025-11-30 17:12:50 +00:00
}
2025-12-03 00:07:39 +00:00
result , err := h . authService . GrantForUser ( c . Context ( ) , tx , u )
2025-11-30 17:12:50 +00:00
if err != nil {
2025-11-30 19:19:33 +00:00
return httperr . Internal ( err )
2025-11-30 17:12:50 +00:00
}
err = tx . Commit ( )
if err != nil {
2025-11-30 19:19:33 +00:00
return httperr . Internal ( err )
2025-11-30 17:12:50 +00:00
}
2025-12-16 00:41:30 +00:00
switch req . TokenDelivery {
default :
return c . Status ( fiber . StatusBadRequest ) . JSON ( fiber . Map { "error" : "invalid token delivery method" } )
case auth . TokenDeliveryCookie :
auth . SetAuthCookies ( c , result . AccessToken , result . RefreshToken , h . cookieConfig )
return c . JSON ( registerAccountResponse {
Account : acc ,
User : u ,
} )
case auth . TokenDeliveryBody :
return c . JSON ( registerAccountResponse {
Account : acc ,
User : u ,
AccessToken : result . AccessToken ,
RefreshToken : result . RefreshToken ,
} )
}
2025-11-30 17:12:50 +00:00
}