refactor(api): enforce protocol-neutral service boundaries
- refactor: move HTTP status and DTO concerns out of model and service layers into API and handler code. - feat: add admin service boundary and route auth through services instead of direct repository access. - test: add architecture and error tests covering package boundaries, redaction, and service behavior. - docs: record the protocol-neutral boundary decision and update architecture and roadmap notes.
This commit is contained in:
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -26,10 +27,29 @@ type createPasskeyRequest struct {
|
||||
Label string `json:"label" binding:"required"`
|
||||
}
|
||||
|
||||
type accountResponse struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
type passkeyResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Type string `json:"type"`
|
||||
Label string `json:"label"`
|
||||
LastUsedAt *time.Time `json:"last_used_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type createdPasskeyResponse struct {
|
||||
ID string `json:"id"`
|
||||
Raw string `json:"raw"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
// GetAccount handles GET /api/v1/account.
|
||||
func (h *AccountHandler) GetAccount(c *gin.Context) {
|
||||
userID := middleware.MustGetUserID(c)
|
||||
c.JSON(http.StatusOK, gin.H{"user_id": userID})
|
||||
c.JSON(http.StatusOK, accountResponse{UserID: userID})
|
||||
}
|
||||
|
||||
// ListPasskeys handles GET /api/v1/account/passkeys.
|
||||
@@ -46,7 +66,7 @@ func (h *AccountHandler) ListPasskeys(c *gin.Context) {
|
||||
creds = []model.Credential{}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, creds)
|
||||
c.JSON(http.StatusOK, toPasskeyResponses(creds))
|
||||
}
|
||||
|
||||
// CreatePasskey handles POST /api/v1/account/passkeys.
|
||||
@@ -66,7 +86,11 @@ func (h *AccountHandler) CreatePasskey(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, pk)
|
||||
c.JSON(http.StatusCreated, createdPasskeyResponse{
|
||||
ID: pk.ID,
|
||||
Raw: pk.Raw,
|
||||
Label: pk.Label,
|
||||
})
|
||||
}
|
||||
|
||||
// RevokePasskey handles DELETE /api/v1/account/passkeys/:id.
|
||||
@@ -86,3 +110,18 @@ func (h *AccountHandler) RevokePasskey(c *gin.Context) {
|
||||
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func toPasskeyResponses(creds []model.Credential) []passkeyResponse {
|
||||
items := make([]passkeyResponse, 0, len(creds))
|
||||
for i := range creds {
|
||||
items = append(items, passkeyResponse{
|
||||
ID: creds[i].ID,
|
||||
UserID: creds[i].UserID,
|
||||
Type: creds[i].Type,
|
||||
Label: creds[i].Label,
|
||||
LastUsedAt: creds[i].LastUsedAt,
|
||||
CreatedAt: creds[i].CreatedAt,
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user