upload part
This commit is contained in:
parent
54afe3ebcf
commit
4fbe92cdcf
@ -15,12 +15,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
Gin *gin.Engine
|
Gin *gin.Engine
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
Config *ServiceConfig
|
Config *ServiceConfig
|
||||||
Store *store.Store
|
Store *store.Store
|
||||||
LogOk *Logger
|
Storage *Storage
|
||||||
LogErr *Logger
|
LogOk *Logger
|
||||||
|
LogErr *Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceConfig struct {
|
type ServiceConfig struct {
|
||||||
@ -31,11 +32,12 @@ type ServiceConfig struct {
|
|||||||
|
|
||||||
func New(config *ServiceConfig) *Service {
|
func New(config *ServiceConfig) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
Gin: gin.New(),
|
Gin: gin.New(),
|
||||||
Config: config,
|
Config: config,
|
||||||
Store: &store.Store{Path: config.StorePath},
|
Store: &store.Store{Path: config.StorePath},
|
||||||
LogOk: &Logger{os.Stdout, "Photos"},
|
Storage: &Storage{Path: config.StorePath},
|
||||||
LogErr: &Logger{os.Stderr, "Photos"},
|
LogOk: &Logger{os.Stdout, "Photos"},
|
||||||
|
LogErr: &Logger{os.Stderr, "Photos"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +51,7 @@ func (s *Service) SetupRoutes() {
|
|||||||
s.AccountInit()
|
s.AccountInit()
|
||||||
s.MeInit()
|
s.MeInit()
|
||||||
s.FileInit()
|
s.FileInit()
|
||||||
|
s.UploadInit()
|
||||||
|
|
||||||
s.Gin.NoRoute(func(c *gin.Context) {
|
s.Gin.NoRoute(func(c *gin.Context) {
|
||||||
s.Error(c, http.StatusNotFound, photoserrors.ErrReqNotFound)
|
s.Error(c, http.StatusNotFound, photoserrors.ErrReqNotFound)
|
||||||
|
40
internal/photos/api/storage.go
Normal file
40
internal/photos/api/storage.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
StorageTmp = "tmp"
|
||||||
|
StorageUpload = "upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Storage) Join(paths ...string) string {
|
||||||
|
return filepath.Join(s.Path, filepath.Join(paths...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) Create(paths ...string) error {
|
||||||
|
return os.MkdirAll(s.Join(paths...), 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) Exists(paths ...string) bool {
|
||||||
|
f, err := os.Stat(s.Join(paths...))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return f.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) Delete(paths ...string) error {
|
||||||
|
if s.Exists(paths...) {
|
||||||
|
return os.RemoveAll(s.Join(paths...))
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%s doesn't exists", s.Join(paths...))
|
||||||
|
}
|
||||||
|
}
|
117
internal/photos/api/upload.go
Normal file
117
internal/photos/api/upload.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) UploadCreate(c *gin.Context) {
|
||||||
|
sha, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Storage.Create(StorageTmp, sha.String()); err != nil {
|
||||||
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
|
"status": "success",
|
||||||
|
"upload_id": sha.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadPartRequest struct {
|
||||||
|
UploadId string `uri:"upload_id" binding:"required,uuid"`
|
||||||
|
Part uint `uri:"part" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UploadPart(c *gin.Context) {
|
||||||
|
var uploadPart UploadPartRequest
|
||||||
|
if err := c.ShouldBindUri(&uploadPart); err != nil {
|
||||||
|
s.Error(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.Storage.Exists(StorageTmp, uploadPart.UploadId) {
|
||||||
|
s.Error(c, http.StatusNotFound, errors.New("upload id doesn't exists"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(
|
||||||
|
s.Storage.Join(StorageTmp, uploadPart.UploadId, fmt.Sprint(uploadPart.Part)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
defer c.Request.Body.Close()
|
||||||
|
|
||||||
|
sha := sha256.New()
|
||||||
|
t := io.TeeReader(c.Request.Body, sha)
|
||||||
|
w, err := io.Copy(f, t)
|
||||||
|
if err != nil {
|
||||||
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
|
"upload_id": uploadPart.UploadId,
|
||||||
|
"part": uploadPart.Part,
|
||||||
|
"size": w,
|
||||||
|
"sha256": hex.EncodeToString(sha.Sum(nil)),
|
||||||
|
"status": "success",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UploadCancel(c *gin.Context) {
|
||||||
|
upload_id := c.Param("upload_id")
|
||||||
|
|
||||||
|
if err := s.Storage.Delete(StorageTmp, upload_id); err != nil {
|
||||||
|
s.Error(c, http.StatusNotFound, errors.New("upload id doesn't exists"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": "success",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadCompleteRequest struct {
|
||||||
|
SHA256 string `json:"sha256" binding:"required,lowercase,alphanum,len=64"`
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Parts uint `json:"parts" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UploadComplete(c *gin.Context) {
|
||||||
|
var uploadCompleteRequest UploadCompleteRequest
|
||||||
|
if err := c.ShouldBindJSON(&uploadCompleteRequest); err != nil {
|
||||||
|
s.Error(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"sha256": uploadCompleteRequest.SHA256,
|
||||||
|
"parts": uploadCompleteRequest.Parts,
|
||||||
|
"name": uploadCompleteRequest.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UploadInit() {
|
||||||
|
upload := s.Gin.Group("/upload")
|
||||||
|
upload.GET("/create", s.UploadCreate)
|
||||||
|
upload.POST("/part/:upload_id/:part", s.UploadPart)
|
||||||
|
upload.GET("/cancel/:upload_id", s.UploadCancel)
|
||||||
|
upload.POST("/complete/:upload_id", s.UploadComplete)
|
||||||
|
}
|
@ -13,4 +13,3 @@ then
|
|||||||
createdb photos
|
createdb photos
|
||||||
createuser photos
|
createuser photos
|
||||||
fi
|
fi
|
||||||
tail -f data/db.log
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user