WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package resumable
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/logger"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func respond(c *gin.Context, status int, value any, err error) {
|
||||
if err == nil {
|
||||
c.JSON(status, gin.H{"success": true, "data": value})
|
||||
return
|
||||
}
|
||||
message := publicError(err)
|
||||
switch {
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
status = 404
|
||||
message = "上传不存在或不属于当前用户"
|
||||
case errors.Is(err, ErrForbidden):
|
||||
status = 403
|
||||
case errors.Is(err, ErrInvalid):
|
||||
status = 400
|
||||
case errors.Is(err, ErrConflict):
|
||||
status = 409
|
||||
case errors.Is(err, ErrQuota):
|
||||
status = 413
|
||||
default:
|
||||
var input UserError
|
||||
if errors.As(err, &input) {
|
||||
status = 400
|
||||
} else {
|
||||
status = 500
|
||||
logger.Errorf(c.Request.Context(), "Upload request failed: %v", err)
|
||||
}
|
||||
}
|
||||
c.AbortWithStatusJSON(status, gin.H{"success": false, "message": message})
|
||||
}
|
||||
|
||||
// Register requires the existing authenticated API group. Every operation also
|
||||
// resolves tenant and real-user identity and enforces upload ownership itself.
|
||||
func (s *Service) Register(group *gin.RouterGroup) {
|
||||
r := group.Group("/uploads")
|
||||
r.GET("", func(c *gin.Context) {
|
||||
rows, e := s.List(c.Request.Context(), c.Query("kind"), c.Query("target_id"))
|
||||
if e == nil && c.Query("view") == "summary" {
|
||||
for i := range rows {
|
||||
rows[i].Result = uploadResultSummary(rows[i].Result)
|
||||
}
|
||||
}
|
||||
respond(c, 200, rows, e)
|
||||
})
|
||||
r.POST("", func(c *gin.Context) {
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 40<<10)
|
||||
var in CreateInput
|
||||
if c.ShouldBindJSON(&in) != nil {
|
||||
respond(c, 400, nil, ErrInvalid)
|
||||
return
|
||||
}
|
||||
row, e := s.Create(c.Request.Context(), in)
|
||||
respond(c, 201, row, e)
|
||||
})
|
||||
r.GET("/:upload_id", func(c *gin.Context) {
|
||||
row, e := s.Get(c.Request.Context(), c.Param("upload_id"))
|
||||
respond(c, 200, row, e)
|
||||
})
|
||||
r.GET("/:upload_id/chunks", func(c *gin.Context) {
|
||||
offset, e := strconv.ParseInt(c.DefaultQuery("offset", "0"), 10, 64)
|
||||
if e != nil {
|
||||
respond(c, 400, nil, ErrInvalid)
|
||||
return
|
||||
}
|
||||
chunks, e := s.Chunks(c.Request.Context(), c.Param("upload_id"), offset)
|
||||
respond(c, 200, chunks, e)
|
||||
})
|
||||
r.PUT("/:upload_id/chunks", func(c *gin.Context) {
|
||||
offset, e := strconv.ParseInt(c.GetHeader("Upload-Offset"), 10, 64)
|
||||
if e != nil {
|
||||
respond(c, 400, nil, ErrInvalid)
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, ChunkSize)
|
||||
row, e := s.Put(c.Request.Context(), c.Param("upload_id"), offset, c.Request.ContentLength, c.GetHeader("Upload-Checksum"), c.Request.Body)
|
||||
respond(c, 200, row, e)
|
||||
})
|
||||
r.POST("/:upload_id/finalize", func(c *gin.Context) {
|
||||
row, e := s.Finalize(c.Request.Context(), c.Param("upload_id"))
|
||||
respond(c, 202, row, e)
|
||||
})
|
||||
r.DELETE("/:upload_id", func(c *gin.Context) {
|
||||
e := s.Cancel(c.Request.Context(), c.Param("upload_id"))
|
||||
respond(c, 200, gin.H{"cancelled": e == nil}, e)
|
||||
})
|
||||
}
|
||||
|
||||
// Shell polling needs progress and the extraction mode, not extracted source
|
||||
// text. Full results remain available on the owned upload's detail endpoint.
|
||||
func uploadResultSummary(result types.JSON) types.JSON {
|
||||
var summary struct {
|
||||
Selection string `json:"selection,omitempty"`
|
||||
}
|
||||
if json.Unmarshal(result, &summary) != nil || (summary.Selection != "full" && summary.Selection != "sampled") {
|
||||
return nil
|
||||
}
|
||||
encoded, _ := json.Marshal(summary)
|
||||
return types.JSON(encoded)
|
||||
}
|
||||
Reference in New Issue
Block a user