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:
2026-07-05 23:30:17 +08:00
parent 28e17a5b08
commit 63ede5c237
34 changed files with 1028 additions and 438 deletions
+50 -5
View File
@@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"strconv"
"time"
"github.com/gin-gonic/gin"
@@ -52,6 +53,24 @@ type updateFileRequest struct {
ParentID *string `json:"parent_id"`
}
type fileInfoResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
ParentID *string `json:"parent_id"`
Name string `json:"name"`
Size int64 `json:"size"`
MimeType string `json:"mime_type"`
IsDir bool `json:"is_dir"`
Hash string `json:"hash,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type fileListResponse struct {
Files []fileInfoResponse `json:"files"`
Total int64 `json:"total"`
}
// Upload handles POST /api/v1/files.
// If the content type is multipart/form-data, it uploads a file.
// If the content type is application/json, it creates a directory.
@@ -81,7 +100,7 @@ func (h *FileHandler) Upload(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, dir)
c.JSON(http.StatusCreated, toFileInfoResponse(dir))
return
}
@@ -130,7 +149,7 @@ func (h *FileHandler) Upload(c *gin.Context) {
return
}
c.JSON(http.StatusCreated, info)
c.JSON(http.StatusCreated, toFileInfoResponse(info))
}
func nextUploadFilePart(reader *multipart.Reader) (*multipart.Part, string, error) {
@@ -217,7 +236,7 @@ func (h *FileHandler) List(c *gin.Context) {
return
}
c.JSON(http.StatusOK, list)
c.JSON(http.StatusOK, toFileListResponse(list))
}
// Get handles GET /api/v1/files/:id.
@@ -231,7 +250,7 @@ func (h *FileHandler) Get(c *gin.Context) {
return
}
c.JSON(http.StatusOK, info)
c.JSON(http.StatusOK, toFileInfoResponse(info))
}
// Download handles GET /api/v1/files/:id/content.
@@ -299,7 +318,7 @@ func (h *FileHandler) Update(c *gin.Context) {
return
}
c.JSON(http.StatusOK, info)
c.JSON(http.StatusOK, toFileInfoResponse(info))
}
// Delete handles DELETE /api/v1/files/:id.
@@ -314,3 +333,29 @@ func (h *FileHandler) Delete(c *gin.Context) {
c.Status(http.StatusNoContent)
}
func toFileInfoResponse(info *service.FileInfo) fileInfoResponse {
return fileInfoResponse{
ID: info.ID,
UserID: info.UserID,
ParentID: info.ParentID,
Name: info.Name,
Size: info.Size,
MimeType: info.MimeType,
IsDir: info.IsDir,
Hash: info.Hash,
CreatedAt: info.CreatedAt,
UpdatedAt: info.UpdatedAt,
}
}
func toFileListResponse(list *service.FileList) fileListResponse {
items := make([]fileInfoResponse, 0, len(list.Files))
for i := range list.Files {
items = append(items, toFileInfoResponse(&list.Files[i]))
}
return fileListResponse{
Files: items,
Total: list.Total,
}
}