package http import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/rwadurian/mpc-system/services/account/application/ports" "github.com/rwadurian/mpc-system/services/account/application/use_cases" "github.com/rwadurian/mpc-system/services/account/domain/value_objects" ) // AccountHTTPHandler handles HTTP requests for accounts type AccountHTTPHandler struct { createAccountUC *use_cases.CreateAccountUseCase getAccountUC *use_cases.GetAccountUseCase updateAccountUC *use_cases.UpdateAccountUseCase listAccountsUC *use_cases.ListAccountsUseCase getAccountSharesUC *use_cases.GetAccountSharesUseCase deactivateShareUC *use_cases.DeactivateShareUseCase loginUC *use_cases.LoginUseCase refreshTokenUC *use_cases.RefreshTokenUseCase generateChallengeUC *use_cases.GenerateChallengeUseCase initiateRecoveryUC *use_cases.InitiateRecoveryUseCase completeRecoveryUC *use_cases.CompleteRecoveryUseCase getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase cancelRecoveryUC *use_cases.CancelRecoveryUseCase } // NewAccountHTTPHandler creates a new AccountHTTPHandler func NewAccountHTTPHandler( createAccountUC *use_cases.CreateAccountUseCase, getAccountUC *use_cases.GetAccountUseCase, updateAccountUC *use_cases.UpdateAccountUseCase, listAccountsUC *use_cases.ListAccountsUseCase, getAccountSharesUC *use_cases.GetAccountSharesUseCase, deactivateShareUC *use_cases.DeactivateShareUseCase, loginUC *use_cases.LoginUseCase, refreshTokenUC *use_cases.RefreshTokenUseCase, generateChallengeUC *use_cases.GenerateChallengeUseCase, initiateRecoveryUC *use_cases.InitiateRecoveryUseCase, completeRecoveryUC *use_cases.CompleteRecoveryUseCase, getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase, cancelRecoveryUC *use_cases.CancelRecoveryUseCase, ) *AccountHTTPHandler { return &AccountHTTPHandler{ createAccountUC: createAccountUC, getAccountUC: getAccountUC, updateAccountUC: updateAccountUC, listAccountsUC: listAccountsUC, getAccountSharesUC: getAccountSharesUC, deactivateShareUC: deactivateShareUC, loginUC: loginUC, refreshTokenUC: refreshTokenUC, generateChallengeUC: generateChallengeUC, initiateRecoveryUC: initiateRecoveryUC, completeRecoveryUC: completeRecoveryUC, getRecoveryStatusUC: getRecoveryStatusUC, cancelRecoveryUC: cancelRecoveryUC, } } // RegisterRoutes registers HTTP routes func (h *AccountHTTPHandler) RegisterRoutes(router *gin.RouterGroup) { accounts := router.Group("/accounts") { accounts.POST("", h.CreateAccount) accounts.GET("", h.ListAccounts) accounts.GET("/:id", h.GetAccount) accounts.PUT("/:id", h.UpdateAccount) accounts.GET("/:id/shares", h.GetAccountShares) accounts.DELETE("/:id/shares/:shareId", h.DeactivateShare) } auth := router.Group("/auth") { auth.POST("/challenge", h.GenerateChallenge) auth.POST("/login", h.Login) auth.POST("/refresh", h.RefreshToken) } recovery := router.Group("/recovery") { recovery.POST("", h.InitiateRecovery) recovery.GET("/:id", h.GetRecoveryStatus) recovery.POST("/:id/complete", h.CompleteRecovery) recovery.POST("/:id/cancel", h.CancelRecovery) } } // CreateAccountRequest represents the request for creating an account type CreateAccountRequest struct { Username string `json:"username" binding:"required"` Email string `json:"email" binding:"required,email"` Phone *string `json:"phone"` PublicKey string `json:"publicKey" binding:"required"` KeygenSessionID string `json:"keygenSessionId" binding:"required"` ThresholdN int `json:"thresholdN" binding:"required,min=1"` ThresholdT int `json:"thresholdT" binding:"required,min=1"` Shares []ShareInput `json:"shares" binding:"required,min=1"` } // ShareInput represents a share in the request type ShareInput struct { ShareType string `json:"shareType" binding:"required"` PartyID string `json:"partyId" binding:"required"` PartyIndex int `json:"partyIndex" binding:"required,min=0"` DeviceType *string `json:"deviceType"` DeviceID *string `json:"deviceId"` } // CreateAccount handles account creation func (h *AccountHTTPHandler) CreateAccount(c *gin.Context) { var req CreateAccountRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } keygenSessionID, err := uuid.Parse(req.KeygenSessionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid keygen session ID"}) return } shares := make([]ports.ShareInput, len(req.Shares)) for i, s := range req.Shares { shares[i] = ports.ShareInput{ ShareType: value_objects.ShareType(s.ShareType), PartyID: s.PartyID, PartyIndex: s.PartyIndex, DeviceType: s.DeviceType, DeviceID: s.DeviceID, } } output, err := h.createAccountUC.Execute(c.Request.Context(), ports.CreateAccountInput{ Username: req.Username, Email: req.Email, Phone: req.Phone, PublicKey: []byte(req.PublicKey), KeygenSessionID: keygenSessionID, ThresholdN: req.ThresholdN, ThresholdT: req.ThresholdT, Shares: shares, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "account": output.Account, "shares": output.Shares, }) } // GetAccount handles getting account by ID func (h *AccountHTTPHandler) GetAccount(c *gin.Context) { idStr := c.Param("id") accountID, err := value_objects.AccountIDFromString(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"}) return } output, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{ AccountID: &accountID, }) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "account": output.Account, "shares": output.Shares, }) } // UpdateAccountRequest represents the request for updating an account type UpdateAccountRequest struct { Phone *string `json:"phone"` } // UpdateAccount handles account updates func (h *AccountHTTPHandler) UpdateAccount(c *gin.Context) { idStr := c.Param("id") accountID, err := value_objects.AccountIDFromString(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"}) return } var req UpdateAccountRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } output, err := h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{ AccountID: accountID, Phone: req.Phone, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, output.Account) } // ListAccounts handles listing accounts func (h *AccountHTTPHandler) ListAccounts(c *gin.Context) { var offset, limit int if o := c.Query("offset"); o != "" { // Parse offset } if l := c.Query("limit"); l != "" { // Parse limit } output, err := h.listAccountsUC.Execute(c.Request.Context(), use_cases.ListAccountsInput{ Offset: offset, Limit: limit, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "accounts": output.Accounts, "total": output.Total, }) } // GetAccountShares handles getting account shares func (h *AccountHTTPHandler) GetAccountShares(c *gin.Context) { idStr := c.Param("id") accountID, err := value_objects.AccountIDFromString(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"}) return } output, err := h.getAccountSharesUC.Execute(c.Request.Context(), accountID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "shares": output.Shares, }) } // DeactivateShare handles share deactivation func (h *AccountHTTPHandler) DeactivateShare(c *gin.Context) { idStr := c.Param("id") accountID, err := value_objects.AccountIDFromString(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"}) return } shareID := c.Param("shareId") err = h.deactivateShareUC.Execute(c.Request.Context(), ports.DeactivateShareInput{ AccountID: accountID, ShareID: shareID, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "share deactivated"}) } // GenerateChallengeRequest represents the request for generating a challenge type GenerateChallengeRequest struct { Username string `json:"username" binding:"required"` } // GenerateChallenge handles challenge generation func (h *AccountHTTPHandler) GenerateChallenge(c *gin.Context) { var req GenerateChallengeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } output, err := h.generateChallengeUC.Execute(c.Request.Context(), use_cases.GenerateChallengeInput{ Username: req.Username, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "challengeId": output.ChallengeID, "challenge": output.Challenge, "expiresAt": output.ExpiresAt, }) } // LoginRequest represents the request for login type LoginRequest struct { Username string `json:"username" binding:"required"` Challenge string `json:"challenge" binding:"required"` Signature string `json:"signature" binding:"required"` } // Login handles user login func (h *AccountHTTPHandler) Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } output, err := h.loginUC.Execute(c.Request.Context(), ports.LoginInput{ Username: req.Username, Challenge: []byte(req.Challenge), Signature: []byte(req.Signature), }) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "account": output.Account, "accessToken": output.AccessToken, "refreshToken": output.RefreshToken, }) } // RefreshTokenRequest represents the request for refreshing tokens type RefreshTokenRequest struct { RefreshToken string `json:"refreshToken" binding:"required"` } // RefreshToken handles token refresh func (h *AccountHTTPHandler) RefreshToken(c *gin.Context) { var req RefreshTokenRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } output, err := h.refreshTokenUC.Execute(c.Request.Context(), use_cases.RefreshTokenInput{ RefreshToken: req.RefreshToken, }) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "accessToken": output.AccessToken, "refreshToken": output.RefreshToken, }) } // InitiateRecoveryRequest represents the request for initiating recovery type InitiateRecoveryRequest struct { AccountID string `json:"accountId" binding:"required"` RecoveryType string `json:"recoveryType" binding:"required"` OldShareType *string `json:"oldShareType"` } // InitiateRecovery handles recovery initiation func (h *AccountHTTPHandler) InitiateRecovery(c *gin.Context) { var req InitiateRecoveryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } accountID, err := value_objects.AccountIDFromString(req.AccountID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"}) return } input := ports.InitiateRecoveryInput{ AccountID: accountID, RecoveryType: value_objects.RecoveryType(req.RecoveryType), } if req.OldShareType != nil { st := value_objects.ShareType(*req.OldShareType) input.OldShareType = &st } output, err := h.initiateRecoveryUC.Execute(c.Request.Context(), input) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "recoverySession": output.RecoverySession, }) } // GetRecoveryStatus handles getting recovery status func (h *AccountHTTPHandler) GetRecoveryStatus(c *gin.Context) { id := c.Param("id") output, err := h.getRecoveryStatusUC.Execute(c.Request.Context(), use_cases.GetRecoveryStatusInput{ RecoverySessionID: id, }) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, output.RecoverySession) } // CompleteRecoveryRequest represents the request for completing recovery type CompleteRecoveryRequest struct { NewPublicKey string `json:"newPublicKey" binding:"required"` NewKeygenSessionID string `json:"newKeygenSessionId" binding:"required"` NewShares []ShareInput `json:"newShares" binding:"required,min=1"` } // CompleteRecovery handles recovery completion func (h *AccountHTTPHandler) CompleteRecovery(c *gin.Context) { id := c.Param("id") var req CompleteRecoveryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } newKeygenSessionID, err := uuid.Parse(req.NewKeygenSessionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid keygen session ID"}) return } newShares := make([]ports.ShareInput, len(req.NewShares)) for i, s := range req.NewShares { newShares[i] = ports.ShareInput{ ShareType: value_objects.ShareType(s.ShareType), PartyID: s.PartyID, PartyIndex: s.PartyIndex, DeviceType: s.DeviceType, DeviceID: s.DeviceID, } } output, err := h.completeRecoveryUC.Execute(c.Request.Context(), ports.CompleteRecoveryInput{ RecoverySessionID: id, NewPublicKey: []byte(req.NewPublicKey), NewKeygenSessionID: newKeygenSessionID, NewShares: newShares, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, output.Account) } // CancelRecovery handles recovery cancellation func (h *AccountHTTPHandler) CancelRecovery(c *gin.Context) { id := c.Param("id") err := h.cancelRecoveryUC.Execute(c.Request.Context(), use_cases.CancelRecoveryInput{ RecoverySessionID: id, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "recovery cancelled"}) }