file-system/internal/api/endpoints/share_endpoint.go
向宁 3a18ca0579 feat: add directory structure and file sharing support
- PostgreSQL metadata overlay layer on top of existing S3 storage
- 3 new tables: folders, files, share_links
- Folder CRUD: create, get with children, tree, rename, delete (cascade)
- File operations: upload to folder, move between folders
- Share links: create with optional password/expiry/download limit, public access
- S3 compensation on PG write failure
- Existing 14 endpoints untouched
2026-05-20 20:26:19 +08:00

104 lines
2.9 KiB
Go

package endpoints
import (
"net/http"
"time"
"rag/file-system/internal/api/handlers"
"rag/file-system/internal/api/requests"
"rag/file-system/internal/api/validators"
"rag/file-system/internal/domain/model"
"rag/file-system/internal/infrastructure/mediator"
"rag/file-system/internal/middleware"
"github.com/gin-gonic/gin"
)
type ShareEndpoint struct {
Mediator *mediator.Mediator
ShareValidator *validators.ShareValidator
}
func NewShareEndpoint(m *mediator.Mediator, sv *validators.ShareValidator) *ShareEndpoint {
return &ShareEndpoint{Mediator: m, ShareValidator: sv}
}
func (e *ShareEndpoint) CreateShare(c *gin.Context) {
var req requests.CreateShareRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := e.ShareValidator.ValidateCreate(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ownerID := c.GetString(middleware.ContextKeyUserID)
var expiresAt *time.Time
if req.ExpiresAt != nil && *req.ExpiresAt != "" {
t, err := time.Parse(time.RFC3339, *req.ExpiresAt)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "过期时间格式错误,请使用 ISO 8601 格式"})
return
}
expiresAt = &t
}
cmd := handlers.CreateShareCommand{
ResourceType: req.ResourceType,
ResourceID: req.ResourceID,
Password: req.Password,
ExpiresAt: expiresAt,
MaxDownloads: req.MaxDownloads,
CreatedBy: ownerID,
}
result, err := mediator.Send[handlers.CreateShareCommand, *model.ShareLink](e.Mediator, c.Request.Context(), cmd)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"data": result})
}
func (e *ShareEndpoint) GetShareInfo(c *gin.Context) {
token := c.Param("token")
query := handlers.GetShareInfoQuery{Token: token}
result, err := mediator.Send[handlers.GetShareInfoQuery, *model.ShareInfo](e.Mediator, c.Request.Context(), query)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"data": result})
}
func (e *ShareEndpoint) DownloadShare(c *gin.Context) {
token := c.Param("token")
var req requests.ShareDownloadRequest
_ = c.ShouldBindJSON(&req)
query := handlers.DownloadShareQuery{Token: token, Password: req.Password}
result, err := mediator.Send[handlers.DownloadShareQuery, string](e.Mediator, c.Request.Context(), query)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"url": result})
}
func (e *ShareEndpoint) DeleteShare(c *gin.Context) {
shareID := c.Param("id")
ownerID := c.GetString(middleware.ContextKeyUserID)
cmd := handlers.DeleteShareCommand{ShareID: shareID, CreatedBy: ownerID}
result, err := mediator.Send[handlers.DeleteShareCommand, string](e.Mediator, c.Request.Context(), cmd)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"message": result})
}