feat: add chunked desktop client release uploads
This commit is contained in:
@@ -2,7 +2,6 @@ package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -40,6 +39,17 @@ type setDesktopClientReleaseEnabledRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type desktopClientReleaseUploadInitRequest struct {
|
||||
Platform string `json:"platform" binding:"required"`
|
||||
Arch string `json:"arch"`
|
||||
Channel string `json:"channel"`
|
||||
Version string `json:"version" binding:"required"`
|
||||
Kind string `json:"kind"`
|
||||
FileName string `json:"file_name" binding:"required"`
|
||||
FileSizeBytes int64 `json:"file_size_bytes" binding:"required"`
|
||||
ChunkSizeBytes int64 `json:"chunk_size_bytes"`
|
||||
}
|
||||
|
||||
func listDesktopClientReleasesHandler(svc *app.DesktopClientReleaseService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
@@ -151,11 +161,28 @@ func resolveDesktopClientReleaseDownloadURLHandler(svc *app.DesktopClientRelease
|
||||
|
||||
func uploadDesktopClientReleaseHandler(svc *app.DesktopClientReleaseService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.ContentLength > app.MaxDesktopClientReleaseMultipartBytes() {
|
||||
response.Error(c, response.ErrBadRequest(40072, "release_file_too_large", "客户端安装包不能超过 1GB"))
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, app.MaxDesktopClientReleaseMultipartBytes())
|
||||
|
||||
fileHeader, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "request body too large") {
|
||||
response.Error(c, response.ErrBadRequest(40072, "release_file_too_large", "客户端安装包不能超过 1GB"))
|
||||
return
|
||||
}
|
||||
response.Error(c, response.ErrBadRequest(40071, "release_file_required", "请选择要上传的客户端安装包"))
|
||||
return
|
||||
}
|
||||
if c.Request.MultipartForm != nil {
|
||||
defer c.Request.MultipartForm.RemoveAll()
|
||||
}
|
||||
if fileHeader.Size > app.MaxDesktopClientReleaseUploadBytes {
|
||||
response.Error(c, response.ErrBadRequest(40072, "release_file_too_large", "客户端安装包不能超过 1GB"))
|
||||
return
|
||||
}
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40071, "release_file_open_failed", "客户端安装包读取失败"))
|
||||
@@ -163,20 +190,15 @@ func uploadDesktopClientReleaseHandler(svc *app.DesktopClientReleaseService) gin
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40071, "release_file_read_failed", "客户端安装包读取失败"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := svc.UploadOSS(c.Request.Context(), app.DesktopClientReleaseUploadInput{
|
||||
Platform: c.PostForm("platform"),
|
||||
Arch: c.PostForm("arch"),
|
||||
Channel: c.PostForm("channel"),
|
||||
Version: c.PostForm("version"),
|
||||
Kind: c.DefaultPostForm("kind", app.DesktopClientAssetKindInstaller),
|
||||
FileName: fileHeader.Filename,
|
||||
Content: content,
|
||||
Platform: c.PostForm("platform"),
|
||||
Arch: c.PostForm("arch"),
|
||||
Channel: c.PostForm("channel"),
|
||||
Version: c.PostForm("version"),
|
||||
Kind: c.DefaultPostForm("kind", app.DesktopClientAssetKindInstaller),
|
||||
FileName: fileHeader.Filename,
|
||||
ContentReader: file,
|
||||
ContentSizeBytes: fileHeader.Size,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
@@ -186,6 +208,102 @@ func uploadDesktopClientReleaseHandler(svc *app.DesktopClientReleaseService) gin
|
||||
}
|
||||
}
|
||||
|
||||
func initiateDesktopClientReleaseUploadHandler(svc *app.DesktopClientReleaseService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var body desktopClientReleaseUploadInitRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
result, err := svc.InitiateChunkedUpload(c.Request.Context(), app.DesktopClientReleaseChunkedUploadInitInput{
|
||||
Platform: body.Platform,
|
||||
Arch: body.Arch,
|
||||
Channel: body.Channel,
|
||||
Version: body.Version,
|
||||
Kind: body.Kind,
|
||||
FileName: body.FileName,
|
||||
FileSizeBytes: body.FileSizeBytes,
|
||||
ChunkSizeBytes: body.ChunkSizeBytes,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
}
|
||||
|
||||
func uploadDesktopClientReleaseChunkHandler(svc *app.DesktopClientReleaseService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.ContentLength > app.MaxDesktopClientReleaseChunkMultipartBytes() {
|
||||
response.Error(c, response.ErrBadRequest(40076, "invalid_release_upload_chunk", "上传分片不能超过 64MB"))
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, app.MaxDesktopClientReleaseChunkMultipartBytes())
|
||||
|
||||
chunkIndex, err := strconv.Atoi(c.Param("chunk_index"))
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40076, "invalid_release_upload_chunk", "上传分片序号无效"))
|
||||
return
|
||||
}
|
||||
fileHeader, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "request body too large") {
|
||||
response.Error(c, response.ErrBadRequest(40076, "invalid_release_upload_chunk", "上传分片不能超过 64MB"))
|
||||
return
|
||||
}
|
||||
response.Error(c, response.ErrBadRequest(40071, "release_file_required", "请选择要上传的客户端安装包"))
|
||||
return
|
||||
}
|
||||
if c.Request.MultipartForm != nil {
|
||||
defer c.Request.MultipartForm.RemoveAll()
|
||||
}
|
||||
if fileHeader.Size > app.MaxDesktopClientReleaseChunkSizeBytes {
|
||||
response.Error(c, response.ErrBadRequest(40076, "invalid_release_upload_chunk", "上传分片不能超过 64MB"))
|
||||
return
|
||||
}
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40071, "release_file_open_failed", "客户端安装包读取失败"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
result, err := svc.UploadChunk(c.Request.Context(), app.DesktopClientReleaseChunkUploadInput{
|
||||
UploadID: c.Param("upload_id"),
|
||||
ChunkIndex: chunkIndex,
|
||||
ContentReader: file,
|
||||
ContentSizeBytes: fileHeader.Size,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
}
|
||||
|
||||
func completeDesktopClientReleaseUploadHandler(svc *app.DesktopClientReleaseService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
result, err := svc.CompleteChunkedUpload(c.Request.Context(), c.Param("upload_id"))
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
}
|
||||
|
||||
func abortDesktopClientReleaseUploadHandler(svc *app.DesktopClientReleaseService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if err := svc.AbortChunkedUpload(c.Param("upload_id")); err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"upload_id": c.Param("upload_id")})
|
||||
}
|
||||
}
|
||||
|
||||
func checkDesktopClientReleaseHandler(svc *app.DesktopClientReleaseService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
result, err := svc.CheckLatest(c.Request.Context(), app.DesktopClientReleaseCheckInput{
|
||||
|
||||
@@ -173,6 +173,10 @@ func RegisterRoutes(d Deps) {
|
||||
|
||||
authed.GET("/desktop-client/releases", listDesktopClientReleasesHandler(d.Releases))
|
||||
authed.POST("/desktop-client/releases/upload", uploadDesktopClientReleaseHandler(d.Releases))
|
||||
authed.POST("/desktop-client/release-uploads", initiateDesktopClientReleaseUploadHandler(d.Releases))
|
||||
authed.POST("/desktop-client/release-uploads/:upload_id/chunks/:chunk_index", uploadDesktopClientReleaseChunkHandler(d.Releases))
|
||||
authed.POST("/desktop-client/release-uploads/:upload_id/complete", completeDesktopClientReleaseUploadHandler(d.Releases))
|
||||
authed.DELETE("/desktop-client/release-uploads/:upload_id", abortDesktopClientReleaseUploadHandler(d.Releases))
|
||||
authed.POST("/desktop-client/releases", createDesktopClientReleaseHandler(d.Releases))
|
||||
authed.PATCH("/desktop-client/releases/:id", updateDesktopClientReleaseHandler(d.Releases))
|
||||
authed.GET("/desktop-client/releases/:id/download-url", resolveDesktopClientReleaseDownloadURLHandler(d.Releases))
|
||||
|
||||
Reference in New Issue
Block a user