From 5dbd59fe8f60bd1add7e067fa103f96c5ef02856 Mon Sep 17 00:00:00 2001 From: celogeek Date: Sat, 14 May 2022 19:13:10 +0200 Subject: [PATCH] move request/response to internal api --- cmd/photos-api-cli/error.go | 18 ----- cmd/photos-api-cli/login.go | 23 +++--- cmd/photos-api-cli/register.go | 15 ++-- cmd/photos-api-cli/upload.go | 30 +++----- internal/photos/api/account.go | 23 +++--- internal/photos/api/errors.go | 34 +++++---- internal/photos/api/main.go | 5 +- internal/photos/api/me.go | 8 ++- internal/photos/api/recovery.go | 7 +- internal/photos/api/upload.go | 36 ++++------ internal/photos/store/core.go | 122 -------------------------------- internal/photos/store/reader.go | 70 ------------------ 12 files changed, 84 insertions(+), 307 deletions(-) delete mode 100644 cmd/photos-api-cli/error.go delete mode 100644 internal/photos/store/core.go delete mode 100644 internal/photos/store/reader.go diff --git a/cmd/photos-api-cli/error.go b/cmd/photos-api-cli/error.go deleted file mode 100644 index c01190d..0000000 --- a/cmd/photos-api-cli/error.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type ResponseError struct { - Err string `json:"error"` - Details []string `json:"details"` -} - -func (u *ResponseError) Error() string { - if len(u.Details) == 0 { - return u.Err - } - return fmt.Sprintf("%s: \n - %s", u.Err, strings.Join(u.Details, "\n - ")) -} diff --git a/cmd/photos-api-cli/login.go b/cmd/photos-api-cli/login.go index ae6ecae..8de34f7 100644 --- a/cmd/photos-api-cli/login.go +++ b/cmd/photos-api-cli/login.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/go-resty/resty/v2" + photosapi "gitlab.celogeek.com/photos/api/internal/photos/api" ) type LoginCommand struct { @@ -12,15 +13,6 @@ type LoginCommand struct { Password string `short:"p" long:"password" description:"Password" required:"true"` } -type LoginRequest struct { - Login string `json:"login"` - Password string `json:"password"` -} - -type LoginResponse struct { - Token string -} - func (c *LoginCommand) Execute(args []string) error { logger.Printf("Login on %s...\n", c.Url) @@ -28,9 +20,12 @@ func (c *LoginCommand) Execute(args []string) error { resp, err := cli. R(). - SetBody(&LoginRequest{c.Login, c.Password}). - SetResult(&LoginResponse{}). - SetError(&ResponseError{}). + SetBody(&photosapi.LoginRequest{ + Login: c.Login, + Password: c.Password, + }). + SetResult(&photosapi.LoginResponse{}). + SetError(&photosapi.ErrorWithDetails{}). Post("/account/login") if err != nil { @@ -39,11 +34,11 @@ func (c *LoginCommand) Execute(args []string) error { if resp.IsError() { logger.Printf("Login failed!") - return resp.Error().(*ResponseError) + return resp.Error().(*photosapi.ErrorWithDetails) } logger.Println("Login succeed!") - if result, ok := resp.Result().(*LoginResponse); ok { + if result, ok := resp.Result().(*photosapi.LoginResponse); ok { fmt.Println(result.Token) } diff --git a/cmd/photos-api-cli/register.go b/cmd/photos-api-cli/register.go index 1a8bafe..3f70cdb 100644 --- a/cmd/photos-api-cli/register.go +++ b/cmd/photos-api-cli/register.go @@ -2,6 +2,7 @@ package main import ( "github.com/go-resty/resty/v2" + photosapi "gitlab.celogeek.com/photos/api/internal/photos/api" ) type RegisterCommand struct { @@ -10,11 +11,6 @@ type RegisterCommand struct { Password string `short:"p" long:"password" description:"Password" required:"true"` } -type RegisterRequest struct { - Login string `json:"login"` - Password string `json:"password"` -} - func (c *RegisterCommand) Execute(args []string) error { logger.Printf("Registering on %s...\n", c.Url) @@ -22,8 +18,11 @@ func (c *RegisterCommand) Execute(args []string) error { resp, err := cli. R(). - SetError(&ResponseError{}). - SetBody(&RegisterRequest{c.Login, c.Password}). + SetError(&photosapi.ErrorWithDetails{}). + SetBody(&photosapi.SignupRequest{ + Login: c.Login, + Password: c.Password, + }). Post("/account/signup") if err != nil { @@ -32,7 +31,7 @@ func (c *RegisterCommand) Execute(args []string) error { if resp.IsError() { logger.Println("Registering failed!") - return resp.Error().(*ResponseError) + return resp.Error().(*photosapi.ErrorWithDetails) } logger.Println("Registering succeed!") diff --git a/cmd/photos-api-cli/upload.go b/cmd/photos-api-cli/upload.go index 0b66cfe..6414ba9 100644 --- a/cmd/photos-api-cli/upload.go +++ b/cmd/photos-api-cli/upload.go @@ -20,33 +20,22 @@ type UploadCommand struct { Workers uint32 `short:"w" long:"workers" description:"Number of workers for uploading chunks" default:"4"` } -type UploadCreate struct { - UploadId string `json:"upload_id"` -} - -type UploadPartResult struct { - UploadId string `json:"upload_id"` - Part uint `json:"part"` - Size uint `json:"size"` - PartSha256 string `json:"sha256"` -} - func (c *UploadCommand) Cli() *resty.Client { return resty.New().SetBaseURL(c.Url).SetAuthScheme("Private").SetAuthToken(c.Token) } func (c *UploadCommand) Execute(args []string) error { cli := c.Cli() - resp, err := cli.R().SetError(&ResponseError{}).SetResult(&UploadCreate{}).Post("/upload") + resp, err := cli.R().SetError(&photosapi.ErrorWithDetails{}).SetResult(&photosapi.Upload{}).Post("/upload") if err != nil { return err } if resp.IsError() { - return resp.Error().(*ResponseError) + return resp.Error().(*photosapi.ErrorWithDetails) } - uploadId := resp.Result().(*UploadCreate).UploadId + uploadId := resp.Result().(*photosapi.Upload).Id f, err := os.Open(c.File) if err != nil { @@ -81,8 +70,7 @@ func (c *UploadCommand) Execute(args []string) error { resp, err := cli. R(). - SetError(&ResponseError{}). - SetResult(&UploadPartResult{}). + SetError(&photosapi.ErrorWithDetails{}). SetQueryParam("part", fmt.Sprint(parts)). SetQueryParam("sha256", hex.EncodeToString(partsha256.Sum(nil))). SetBody(b[:n]). @@ -94,19 +82,19 @@ func (c *UploadCommand) Execute(args []string) error { } if resp.IsError() { - return resp.Error().(*ResponseError) + return resp.Error().(*photosapi.ErrorWithDetails) } } fmt.Printf( - "Upload: %s\nParts: %d\n", + "Result:\n - Upload ID: %s\n - Parts: %d\n", uploadId, parts, ) resp, err = cli. R(). - SetError(&ResponseError{}). + SetError(&photosapi.ErrorWithDetails{}). SetPathParam("id", uploadId). SetBody(&photosapi.UploadCompleteRequest{ Sha256: hex.EncodeToString(completesha256.Sum(nil)), @@ -120,11 +108,9 @@ func (c *UploadCommand) Execute(args []string) error { } if resp.IsError() { - return resp.Error().(*ResponseError) + return resp.Error().(*photosapi.ErrorWithDetails) } - fmt.Printf("Response: %s\n", resp.Body()) - cli.R().SetPathParam("id", uploadId).Delete("/upload/{id}") return nil diff --git a/internal/photos/api/account.go b/internal/photos/api/account.go index 1daeaa9..dca457e 100644 --- a/internal/photos/api/account.go +++ b/internal/photos/api/account.go @@ -50,13 +50,22 @@ func NewAccount(login string, password string) *Account { } // Service -type SignupOrLoginRequest struct { - Login string `binding:"min=3,max=40,alphanum"` - Password string `binding:"min=8,max=40"` +type SignupRequest struct { + Login string `json:"login" binding:"required,min=3,max=40,alphanum"` + Password string `json:"password" binding:"required,min=8,max=40"` +} + +type LoginRequest struct { + Login string `json:"login" binding:"required"` + Password string `json:"password" binding:"required"` +} + +type LoginResponse struct { + Token string `json:"token"` } func (s *Service) Signup(c *gin.Context) { - var account *SignupOrLoginRequest + var account *SignupRequest if c.BindJSON(&account) != nil { return @@ -80,7 +89,7 @@ func (s *Service) Signup(c *gin.Context) { } func (s *Service) Login(c *gin.Context) { - var account *SignupOrLoginRequest + var account *LoginRequest if c.BindJSON(&account) != nil { return @@ -96,9 +105,7 @@ func (s *Service) Login(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{ - "token": session.Token, - }) + c.JSON(http.StatusOK, LoginResponse{session.Token}) } func (s *Service) Logout(c *gin.Context) { diff --git a/internal/photos/api/errors.go b/internal/photos/api/errors.go index f2d07b6..d582d67 100644 --- a/internal/photos/api/errors.go +++ b/internal/photos/api/errors.go @@ -2,6 +2,7 @@ package photosapi import ( "errors" + "fmt" "strings" "github.com/gin-gonic/gin" @@ -11,6 +12,18 @@ var ( ErrReqMissingBody = errors.New("missing body") ) +type ErrorWithDetails struct { + Err string `json:"error"` + Details []string `json:"details"` +} + +func (u *ErrorWithDetails) Error() string { + if len(u.Details) == 0 { + return u.Err + } + return fmt.Sprintf("%s: \n - %s", u.Err, strings.Join(u.Details, "\n - ")) +} + func (s *Service) HandleError(c *gin.Context) { c.Next() err := c.Errors.Last() @@ -18,20 +31,15 @@ func (s *Service) HandleError(c *gin.Context) { return } - details := err.Error() - if details == "EOF" { - details = "missing body" + errWithDetails := &ErrorWithDetails{err.Error(), nil} + + if errWithDetails.Err == "EOF" { + errWithDetails.Err = "missing body" } - switch err.Type { - case gin.ErrorTypeBind: - c.JSON(-1, gin.H{ - "error": "binding error", - "details": strings.Split(details, "\n"), - }) - default: - c.JSON(-1, gin.H{ - "error": details, - }) + if err.Type == gin.ErrorTypeBind { + errWithDetails.Err, errWithDetails.Details = "binding error", strings.Split(errWithDetails.Err, "\n") } + + c.JSON(-1, errWithDetails) } diff --git a/internal/photos/api/main.go b/internal/photos/api/main.go index 88b344b..7fb9933 100644 --- a/internal/photos/api/main.go +++ b/internal/photos/api/main.go @@ -10,7 +10,6 @@ import ( "time" "github.com/gin-gonic/gin" - photosstore "gitlab.celogeek.com/photos/api/internal/photos/store" "gorm.io/gorm" ) @@ -23,7 +22,6 @@ type Service struct { Gin *gin.Engine DB *gorm.DB Config *ServiceConfig - Store *photosstore.Store StorageTmp *Storage StorageUpload *Storage LogOk *Logger @@ -40,7 +38,6 @@ func New(config *ServiceConfig) *Service { return &Service{ Gin: gin.New(), Config: config, - Store: &photosstore.Store{Path: config.StorePath}, StorageTmp: NewStorage(config.StorePath, "tmp"), StorageUpload: NewStorage(config.StorePath, "upload"), LogOk: &Logger{os.Stdout, "Photos"}, @@ -67,7 +64,7 @@ func (s *Service) SetupRoutes() { } func (s *Service) PrepareStore() { - d, err := os.Stat(s.Store.Path) + d, err := os.Stat(s.Config.StorePath) if err != nil { s.LogErr.Fatal("Store", err) } diff --git a/internal/photos/api/me.go b/internal/photos/api/me.go index 4fece2f..01c3c36 100644 --- a/internal/photos/api/me.go +++ b/internal/photos/api/me.go @@ -6,10 +6,12 @@ import ( "github.com/gin-gonic/gin" ) +type Me struct { + User string `json:"user"` +} + func (s *Service) Me(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "user": s.CurrentSession(c).Account.Login, - }) + c.JSON(http.StatusOK, Me{s.CurrentSession(c).Account.Login}) } func (s *Service) MeInit() { diff --git a/internal/photos/api/recovery.go b/internal/photos/api/recovery.go index f9bb596..9a08371 100644 --- a/internal/photos/api/recovery.go +++ b/internal/photos/api/recovery.go @@ -2,6 +2,7 @@ package photosapi import ( "errors" + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -17,9 +18,9 @@ func (s *Service) Recovery(c *gin.Context) { defer func() { if err := recover(); err != nil { s.LogErr.Print("PANIC", err) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ - "error": ErrUnexpected.Error(), - "details": err, + c.AbortWithStatusJSON(http.StatusInternalServerError, &ErrorWithDetails{ + ErrUnexpected.Error(), + []string{fmt.Sprint(err)}, }) } }() diff --git a/internal/photos/api/upload.go b/internal/photos/api/upload.go index 70686f3..382f0d6 100644 --- a/internal/photos/api/upload.go +++ b/internal/photos/api/upload.go @@ -43,18 +43,18 @@ func (s *Service) UploadCreate(c *gin.Context) { return } - if err := s.StorageTmp.Create(sha.String()); err != nil { + upload := &Upload{sha.String()} + + if err := s.StorageTmp.Create(upload.Id); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } - c.JSON(http.StatusCreated, gin.H{ - "upload_id": sha.String(), - }) + c.JSON(http.StatusCreated, upload) } -type UploadUri struct { - Id string `uri:"upload_id" binding:"required,uuid"` +type Upload struct { + Id string `json:"upload_id" uri:"upload_id" binding:"required,uuid"` } type UploadPartQuery struct { @@ -64,7 +64,7 @@ type UploadPartQuery struct { func (s *Service) UploadPart(c *gin.Context) { var ( - upload UploadUri + upload Upload uploadPart UploadPartQuery ) @@ -93,7 +93,7 @@ func (s *Service) UploadPart(c *gin.Context) { sha := sha256.New() t := io.TeeReader(c.Request.Body, sha) - w, err := io.Copy(f, t) + _, err = io.Copy(f, t) if err != nil { f.Close() os.Remove(tmp_file) @@ -109,22 +109,18 @@ func (s *Service) UploadPart(c *gin.Context) { return } - if err = os.Rename(tmp_file, file); err != nil { + err = os.Rename(tmp_file, file) + if err != nil { os.Remove(tmp_file) c.AbortWithError(http.StatusInternalServerError, err) return } - c.JSON(http.StatusCreated, gin.H{ - "upload_id": upload.Id, - "part": uploadPart.Part, - "size": w, - "sha256": uploadPart.PartSha256, - }) + c.Status(http.StatusNoContent) } func (s *Service) UploadCancel(c *gin.Context) { - var upload UploadUri + var upload Upload if c.BindUri(&upload) != nil { return } @@ -145,7 +141,7 @@ type UploadCompleteRequest struct { func (s *Service) UploadComplete(c *gin.Context) { var ( - upload UploadUri + upload Upload uploadCompleteRequest UploadCompleteRequest ) if c.BindUri(&upload) != nil || c.BindJSON(&uploadCompleteRequest) != nil { @@ -157,11 +153,7 @@ func (s *Service) UploadComplete(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{ - "sha256": uploadCompleteRequest.Sha256, - "parts": uploadCompleteRequest.Parts, - "name": uploadCompleteRequest.Name, - }) + c.Status(http.StatusNoContent) } func (s *Service) UploadInit() { diff --git a/internal/photos/store/core.go b/internal/photos/store/core.go deleted file mode 100644 index ea86001..0000000 --- a/internal/photos/store/core.go +++ /dev/null @@ -1,122 +0,0 @@ -package photosstore - -import ( - "crypto/sha1" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/gin-gonic/gin" -) - -var ( - ErrStoreChunkAlreadyExists = errors.New("chunk file already exists") -) - -type Store struct { - Path string -} - -type Chunk struct { - *Store - Sum string - Bytes []byte -} - -func (s *Store) NewChunk(b []byte) *Chunk { - sum := sha1.New() - sum.Write(b) - sumString := hex.EncodeToString(sum.Sum(nil)) - - return &Chunk{s, sumString, b} -} - -func (s *Store) LoadChunk(sum string) (*Chunk, error) { - c := s.Chunk(sum) - if !c.FileExists() { - return nil, fmt.Errorf("chunk %s doesn't exists", sum) - } - b, err := os.ReadFile(c.Filename()) - if err != nil { - return nil, err - } - c.Bytes = b - return c, nil -} - -func (s *Store) Chunk(sum string) *Chunk { - return &Chunk{s, sum, nil} -} - -func (c *Chunk) Dir() string { - return filepath.Join(c.Path, "storage", c.Sum[0:1], c.Sum[1:2]) -} - -func (c *Chunk) Filename() string { - return filepath.Join(c.Dir(), c.Sum) -} - -func (c *Chunk) Size() int64 { - st, err := os.Stat(c.Filename()) - if err != nil { - return -1 - } - return st.Size() -} - -func (c *Chunk) FileExists() bool { - fs, err := os.Stat(c.Filename()) - if errors.Is(err, os.ErrNotExist) { - return false - } - return !fs.IsDir() -} - -func (c *Chunk) Mkdir() error { - return os.MkdirAll(c.Dir(), 0755) -} - -func (c *Chunk) Save(login string) error { - if c.FileExists() { - return ErrStoreChunkAlreadyExists - } - - if err := c.Mkdir(); err != nil { - return err - } - - if err := os.WriteFile(c.Filename(), c.Bytes, 0666); err != nil { - return err - } - - meta, err := os.Create(c.Filename() + ".json") - if err != nil { - return err - } - enc := json.NewEncoder(meta) - enc.SetIndent("", " ") - return enc.Encode(gin.H{ - "author": login, - "date": time.Now().UTC().Format("2006-01-02 15:04:05"), - "checksum": c.Sum, - "size": len(c.Bytes), - }) -} - -func (s *Store) Combine(sumb []string) (string, uint64, error) { - sum := sha1.New() - size := uint64(0) - for _, sb := range sumb { - c, err := s.LoadChunk(sb) - if err != nil { - return "", 0, err - } - sum.Write(c.Bytes) - size += uint64(len(c.Bytes)) - } - return hex.EncodeToString(sum.Sum(nil)), size, nil -} diff --git a/internal/photos/store/reader.go b/internal/photos/store/reader.go deleted file mode 100644 index 772ef4e..0000000 --- a/internal/photos/store/reader.go +++ /dev/null @@ -1,70 +0,0 @@ -package photosstore - -import ( - "errors" - "io" - "os" -) - -var ( - ErrStoreMissingChunks = errors.New("part checksum missing") -) - -type StoreReaderChunk struct { - Filename string - Size int64 -} - -type StoreReader struct { - current *os.File - chunk int - chunks []StoreReaderChunk - Size int64 -} - -func (s *Store) NewStoreReader(chunks []string) (*StoreReader, error) { - sr := &StoreReader{nil, 0, make([]StoreReaderChunk, len(chunks)), 0} - for i, chunk := range chunks { - c := s.Chunk(chunk) - name := c.Filename() - size := c.Size() - if size < 0 { - return nil, ErrStoreMissingChunks - } - sr.chunks[i] = StoreReaderChunk{name, size} - sr.Size += size - } - return sr, nil -} - -func (s *StoreReader) Read(p []byte) (n int, err error) { - if s.current == nil { - f, err := os.Open(s.chunks[s.chunk].Filename) - if err != nil { - return -1, err - } - s.current = f - } - - n, err = s.current.Read(p) - if err == io.EOF { - s.chunk++ - if s.chunk > len(s.chunks)-1 { - return - } - s.Close() - return s.Read(p) - } - return -} - -func (s *StoreReader) Close() { - if s.current != nil { - s.current.Close() - s.current = nil - } -} - -// func (s *StoreReader) Seek(offset int64, whence int) (int64, error) { - -// }