reorganize package by logic
This commit is contained in:
parent
4fbe92cdcf
commit
656440c4b9
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/schollz/progressbar/v3"
|
"github.com/schollz/progressbar/v3"
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/api"
|
photosapi "gitlab.celogeek.com/photos/api/internal/photos/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UploadCommand struct {
|
type UploadCommand struct {
|
||||||
@ -82,8 +82,8 @@ func (c *UploadCommand) FileUpload(sum string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nbChunks := st.Size() / api.CHUNK_SIZE
|
nbChunks := st.Size() / photosapi.CHUNK_SIZE
|
||||||
if st.Size()%api.CHUNK_SIZE > 0 {
|
if st.Size()%photosapi.CHUNK_SIZE > 0 {
|
||||||
nbChunks++
|
nbChunks++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ func (c *UploadCommand) FileUpload(sum string) error {
|
|||||||
for w := uint32(0); w < c.Workers; w++ {
|
for w := uint32(0); w < c.Workers; w++ {
|
||||||
go func(w uint32) {
|
go func(w uint32) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
b := make([]byte, api.CHUNK_SIZE)
|
b := make([]byte, photosapi.CHUNK_SIZE)
|
||||||
for {
|
for {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
part := i
|
part := i
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"gitlab.celogeek.com/photos/api/internal/env"
|
"gitlab.celogeek.com/photos/api/internal/env"
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/api"
|
photosapi "gitlab.celogeek.com/photos/api/internal/photos/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -23,8 +23,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := &api.ServiceConfig{
|
config := &photosapi.ServiceConfig{
|
||||||
DB: &api.DBConfig{},
|
DB: &photosapi.DBConfig{},
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.StringVar(&config.Listen, "listen", listen, "Listen address")
|
flag.StringVar(&config.Listen, "listen", listen, "Listen address")
|
||||||
@ -52,7 +52,7 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p := api.New(config)
|
p := photosapi.New(config)
|
||||||
|
|
||||||
if err := p.Run(); err != nil {
|
if err := p.Run(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -1,16 +1,56 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/models"
|
|
||||||
"gopkg.in/validator.v2"
|
"gopkg.in/validator.v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
var (
|
||||||
|
ErrAccountExists = errors.New("account exists")
|
||||||
|
ErrAccountAuth = errors.New("login or password incorrect")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Model
|
||||||
|
type Account struct {
|
||||||
|
ID uint32 `gorm:"primary_key" json:"-"`
|
||||||
|
Login string `gorm:"unique;size:64;not null" json:"login"`
|
||||||
|
Password string `gorm:"-" json:"-"`
|
||||||
|
EncryptedPassword string `gorm:"size:44;not null" json:"-"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if a.EncryptedPassword == "" {
|
||||||
|
a.EncryptPassword()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) EncryptPassword() {
|
||||||
|
sha1 := crypto.SHA256.New()
|
||||||
|
sha1.Write([]byte(a.Password))
|
||||||
|
a.EncryptedPassword = base64.StdEncoding.EncodeToString(sha1.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccount(login string, password string) *Account {
|
||||||
|
a := &Account{
|
||||||
|
Login: login,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
a.EncryptPassword()
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service
|
||||||
type SignupOrLoginRequest struct {
|
type SignupOrLoginRequest struct {
|
||||||
Login string `validate:"min=3,max=40,regexp=^[a-zA-Z0-9]*$"`
|
Login string `validate:"min=3,max=40,regexp=^[a-zA-Z0-9]*$"`
|
||||||
Password string `validate:"min=8,max=40"`
|
Password string `validate:"min=8,max=40"`
|
||||||
@ -29,15 +69,15 @@ func (s *Service) Signup(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var accountExists int64
|
var accountExists int64
|
||||||
if err := s.DB.Model(&models.Account{}).Where("login = ?", account.Login).Count(&accountExists).Error; err != nil {
|
if err := s.DB.Model(&Account{}).Where("login = ?", account.Login).Count(&accountExists).Error; err != nil {
|
||||||
s.Error(c, http.StatusInternalServerError, err)
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if accountExists > 0 {
|
if accountExists > 0 {
|
||||||
s.Error(c, http.StatusConflict, photoserrors.ErrAccountExists)
|
s.Error(c, http.StatusConflict, ErrAccountExists)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := s.DB.Create(models.NewAccount(account.Login, account.Password)).Error; err != nil {
|
if err := s.DB.Create(NewAccount(account.Login, account.Password)).Error; err != nil {
|
||||||
s.Error(c, http.StatusConflict, err)
|
s.Error(c, http.StatusConflict, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -55,10 +95,10 @@ func (s *Service) Login(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := models.NewSession(s.DB, account.Login, account.Password)
|
session, err := NewSession(s.DB, account.Login, account.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
s.Error(c, http.StatusNotFound, photoserrors.ErrAccountAuth)
|
s.Error(c, http.StatusNotFound, ErrAccountAuth)
|
||||||
} else {
|
} else {
|
||||||
s.Error(c, http.StatusInternalServerError, err)
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
@ -72,13 +112,13 @@ func (s *Service) Login(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Logout(c *gin.Context) {
|
func (s *Service) Logout(c *gin.Context) {
|
||||||
res := s.DB.Where("token = ?", c.GetString("token")).Delete(&models.Session{})
|
res := s.DB.Where("token = ?", c.GetString("token")).Delete(&Session{})
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
s.Error(c, http.StatusInternalServerError, res.Error)
|
s.Error(c, http.StatusInternalServerError, res.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if res.RowsAffected == 0 {
|
if res.RowsAffected == 0 {
|
||||||
s.Error(c, http.StatusNotFound, photoserrors.ErrSessionNotFound)
|
s.Error(c, http.StatusNotFound, ErrSessionNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrReqMissingBody = errors.New("missing body")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) RequireBody(c *gin.Context) {
|
func (s *Service) RequireBody(c *gin.Context) {
|
||||||
if c.Request.Method == "POST" && c.Request.ContentLength == 0 {
|
if c.Request.Method == "POST" && c.Request.ContentLength == 0 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrReqMissingBody)
|
s.Error(c, http.StatusBadRequest, ErrReqMissingBody)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/models"
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
@ -33,10 +32,10 @@ func (d *DBConfig) DSN() string {
|
|||||||
|
|
||||||
func (s *Service) Migrate() {
|
func (s *Service) Migrate() {
|
||||||
tx := s.DB
|
tx := s.DB
|
||||||
tx.AutoMigrate(&models.Account{})
|
tx.AutoMigrate(&Account{})
|
||||||
tx.AutoMigrate(&models.Session{})
|
tx.AutoMigrate(&Session{})
|
||||||
tx.AutoMigrate(&models.File{})
|
tx.AutoMigrate(&File{})
|
||||||
tx.AutoMigrate(&models.FileChunk{})
|
tx.AutoMigrate(&FileChunk{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) DBConnect() {
|
func (s *Service) DBConnect() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -9,47 +9,82 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/dsoprea/go-exif/v3"
|
"github.com/dsoprea/go-exif/v3"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/jackc/pgconn"
|
"github.com/jackc/pgconn"
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/models"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Error
|
||||||
|
var (
|
||||||
|
// Store
|
||||||
|
ErrStoreBadChecksum = errors.New("checksum should be sha1 in hex format")
|
||||||
|
ErrStoreBadChunkSize = errors.New("part file size should be 1MB max")
|
||||||
|
ErrStoreMissingChunks = errors.New("part checksum missing")
|
||||||
|
ErrStoreWrongChecksum = errors.New("wrong checksum")
|
||||||
|
ErrStoreMismatchChecksum = errors.New("part files doesn't match the original checksum")
|
||||||
|
ErrStoreAlreadyExists = errors.New("original file already exists")
|
||||||
|
ErrStoreChunkAlreadyExists = errors.New("chunk file already exists")
|
||||||
|
ErrStoreMissingName = errors.New("name required")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Model
|
||||||
|
type File struct {
|
||||||
|
ID uint32 `gorm:"primary_key" json:"id"`
|
||||||
|
Name string `gorm:"not null" json:"name"`
|
||||||
|
Checksum string `gorm:"unique;size:44;not null"`
|
||||||
|
Size uint64 `gorm:"not null"`
|
||||||
|
Author *Account `gorm:"constraint:OnDelete:SET NULL,OnUpdate:CASCADE" json:"author"`
|
||||||
|
AuthorId *uint32 `json:"-"`
|
||||||
|
Chunks []*FileChunk `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileChunk struct {
|
||||||
|
FileId uint32
|
||||||
|
File *File `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
|
||||||
|
Part uint32
|
||||||
|
Checksum string `gorm:"unique;size:44;not null"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service
|
||||||
var CHUNK_SIZE int64 = 4 << 20
|
var CHUNK_SIZE int64 = 4 << 20
|
||||||
|
|
||||||
type File struct {
|
type FileRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Checksum string `json:"checksum"`
|
Checksum string `json:"checksum"`
|
||||||
Chunks []string `json:"chunks"`
|
Chunks []string `json:"chunks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) FileCreate(c *gin.Context) {
|
func (s *Service) FileCreate(c *gin.Context) {
|
||||||
file := &File{}
|
file := &FileRequest{}
|
||||||
if err := c.ShouldBindJSON(file); err != nil {
|
if err := c.ShouldBindJSON(file); err != nil {
|
||||||
s.Error(c, http.StatusInternalServerError, err)
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(file.Name) < 1 {
|
if len(file.Name) < 1 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreMissingName)
|
s.Error(c, http.StatusBadRequest, ErrStoreMissingName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(file.Checksum) != 40 {
|
if len(file.Checksum) != 40 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
|
s.Error(c, http.StatusBadRequest, ErrStoreBadChecksum)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(file.Chunks) == 0 {
|
if len(file.Chunks) == 0 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreMissingChunks)
|
s.Error(c, http.StatusBadRequest, ErrStoreMissingChunks)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chunk := range file.Chunks {
|
for _, chunk := range file.Chunks {
|
||||||
if len(chunk) != 40 {
|
if len(chunk) != 40 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
|
s.Error(c, http.StatusBadRequest, ErrStoreBadChecksum)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,13 +100,13 @@ func (s *Service) FileCreate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r != file.Checksum {
|
if r != file.Checksum {
|
||||||
s.Error(c, http.StatusExpectationFailed, photoserrors.ErrStoreMismatchChecksum)
|
s.Error(c, http.StatusExpectationFailed, ErrStoreMismatchChecksum)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := s.CurrentSession(c)
|
sess := s.CurrentSession(c)
|
||||||
|
|
||||||
f := &models.File{
|
f := &File{
|
||||||
Name: file.Name,
|
Name: file.Name,
|
||||||
Checksum: file.Checksum,
|
Checksum: file.Checksum,
|
||||||
Size: rs,
|
Size: rs,
|
||||||
@ -83,7 +118,7 @@ func (s *Service) FileCreate(c *gin.Context) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for i, chunk := range file.Chunks {
|
for i, chunk := range file.Chunks {
|
||||||
fc := &models.FileChunk{
|
fc := &FileChunk{
|
||||||
FileId: f.ID,
|
FileId: f.ID,
|
||||||
Part: uint32(i + 1),
|
Part: uint32(i + 1),
|
||||||
Checksum: chunk,
|
Checksum: chunk,
|
||||||
@ -116,7 +151,7 @@ func (s *Service) FileCreate(c *gin.Context) {
|
|||||||
|
|
||||||
func (s *Service) FileCreateChunk(c *gin.Context) {
|
func (s *Service) FileCreateChunk(c *gin.Context) {
|
||||||
if c.Request.ContentLength > CHUNK_SIZE {
|
if c.Request.ContentLength > CHUNK_SIZE {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChunkSize)
|
s.Error(c, http.StatusBadRequest, ErrStoreBadChunkSize)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,8 +162,8 @@ func (s *Service) FileCreateChunk(c *gin.Context) {
|
|||||||
sess := s.CurrentSession(c)
|
sess := s.CurrentSession(c)
|
||||||
|
|
||||||
chunk := s.Store.NewChunk(b.Bytes())
|
chunk := s.Store.NewChunk(b.Bytes())
|
||||||
if err := chunk.Save(sess); err != nil {
|
if err := chunk.Save(sess.Account.Login); err != nil {
|
||||||
if errors.Is(err, photoserrors.ErrStoreChunkAlreadyExists) {
|
if errors.Is(err, ErrStoreChunkAlreadyExists) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"checksum": chunk.Sum,
|
"checksum": chunk.Sum,
|
||||||
@ -148,7 +183,7 @@ func (s *Service) FileCreateChunk(c *gin.Context) {
|
|||||||
func (s *Service) FileChunkExists(c *gin.Context) {
|
func (s *Service) FileChunkExists(c *gin.Context) {
|
||||||
checksum := c.Param("checksum")
|
checksum := c.Param("checksum")
|
||||||
if len(checksum) != 40 {
|
if len(checksum) != 40 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
|
s.Error(c, http.StatusBadRequest, ErrStoreBadChecksum)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,12 +197,12 @@ func (s *Service) FileChunkExists(c *gin.Context) {
|
|||||||
func (s *Service) FileExists(c *gin.Context) {
|
func (s *Service) FileExists(c *gin.Context) {
|
||||||
checksum := c.Param("checksum")
|
checksum := c.Param("checksum")
|
||||||
if len(checksum) != 40 {
|
if len(checksum) != 40 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
|
s.Error(c, http.StatusBadRequest, ErrStoreBadChecksum)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileExists int64
|
var fileExists int64
|
||||||
if err := s.DB.Model(&models.File{}).Where("checksum = ?", checksum).Count(&fileExists).Error; err != nil {
|
if err := s.DB.Model(&File{}).Where("checksum = ?", checksum).Count(&fileExists).Error; err != nil {
|
||||||
s.Error(c, http.StatusInternalServerError, err)
|
s.Error(c, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -182,7 +217,7 @@ func (s *Service) FileExists(c *gin.Context) {
|
|||||||
func (s *Service) FileGet(c *gin.Context) {
|
func (s *Service) FileGet(c *gin.Context) {
|
||||||
checksum := c.Param("checksum")
|
checksum := c.Param("checksum")
|
||||||
if len(checksum) != 40 {
|
if len(checksum) != 40 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
|
s.Error(c, http.StatusBadRequest, ErrStoreBadChecksum)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if checksum == c.GetHeader("If-None-Match") {
|
if checksum == c.GetHeader("If-None-Match") {
|
||||||
@ -190,7 +225,7 @@ func (s *Service) FileGet(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &models.File{}
|
f := &File{}
|
||||||
if err := s.DB.Debug().Preload("Chunks").Where("checksum = ?", checksum).First(&f).Error; err != nil {
|
if err := s.DB.Debug().Preload("Chunks").Where("checksum = ?", checksum).First(&f).Error; err != nil {
|
||||||
s.Error(c, http.StatusBadRequest, err)
|
s.Error(c, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
@ -223,11 +258,11 @@ func (s *Service) FileGet(c *gin.Context) {
|
|||||||
func (s *Service) FileAnalyze(c *gin.Context) {
|
func (s *Service) FileAnalyze(c *gin.Context) {
|
||||||
checksum := c.Param("checksum")
|
checksum := c.Param("checksum")
|
||||||
if len(checksum) != 40 {
|
if len(checksum) != 40 {
|
||||||
s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
|
s.Error(c, http.StatusBadRequest, ErrStoreBadChecksum)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &models.File{}
|
f := &File{}
|
||||||
if err := s.DB.Debug().Preload("Chunks").Where("checksum = ?", checksum).First(&f).Error; err != nil {
|
if err := s.DB.Debug().Preload("Chunks").Where("checksum = ?", checksum).First(&f).Error; err != nil {
|
||||||
s.Error(c, http.StatusBadRequest, err)
|
s.Error(c, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -9,16 +10,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
photosstore "gitlab.celogeek.com/photos/api/internal/photos/store"
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/store"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrReqNotFound = errors.New("this route doesn't exists")
|
||||||
|
ErrStorePathNotADirectory = errors.New("store path is not a directory")
|
||||||
|
)
|
||||||
|
|
||||||
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 *photosstore.Store
|
||||||
Storage *Storage
|
Storage *Storage
|
||||||
LogOk *Logger
|
LogOk *Logger
|
||||||
LogErr *Logger
|
LogErr *Logger
|
||||||
@ -34,7 +39,7 @@ 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: &photosstore.Store{Path: config.StorePath},
|
||||||
Storage: &Storage{Path: config.StorePath},
|
Storage: &Storage{Path: config.StorePath},
|
||||||
LogOk: &Logger{os.Stdout, "Photos"},
|
LogOk: &Logger{os.Stdout, "Photos"},
|
||||||
LogErr: &Logger{os.Stderr, "Photos"},
|
LogErr: &Logger{os.Stderr, "Photos"},
|
||||||
@ -54,7 +59,7 @@ func (s *Service) SetupRoutes() {
|
|||||||
s.UploadInit()
|
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, ErrReqNotFound)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +69,7 @@ func (s *Service) PrepareStore() {
|
|||||||
s.LogErr.Fatal("Store", err)
|
s.LogErr.Fatal("Store", err)
|
||||||
}
|
}
|
||||||
if !d.IsDir() {
|
if !d.IsDir() {
|
||||||
s.LogErr.Fatal("Store", photoserrors.ErrStorePathNotADirectory)
|
s.LogErr.Fatal("Store", ErrStorePathNotADirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
var (
|
||||||
|
ErrUnexpected = errors.New("an unexpected error occur")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service
|
||||||
func (s *Service) Recovery(c *gin.Context) {
|
func (s *Service) Recovery(c *gin.Context) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
s.LogErr.Print("PANIC", err)
|
s.LogErr.Print("PANIC", err)
|
||||||
s.Error(c, http.StatusInternalServerError, photoserrors.ErrUnexpected)
|
s.Error(c, http.StatusInternalServerError, ErrUnexpected)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
c.Next()
|
c.Next()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -7,25 +7,68 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
"github.com/google/uuid"
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/models"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
var (
|
||||||
|
ErrSessionNotFound = errors.New("session not found")
|
||||||
|
ErrSessionInvalid = errors.New("session invalid")
|
||||||
|
ErrTokenMissing = errors.New("token missing")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Model
|
||||||
|
type Session struct {
|
||||||
|
ID uint32 `gorm:"primary_key"`
|
||||||
|
Token string `gorm:"size:36"`
|
||||||
|
Account *Account `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
|
||||||
|
AccountId uint32 `gorm:"not null"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
uuid, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Token = uuid.String()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSession(tx *gorm.DB, login string, password string) (*Session, error) {
|
||||||
|
account := NewAccount(login, password)
|
||||||
|
if err := tx.Where(
|
||||||
|
"login = ? and encrypted_password = ?",
|
||||||
|
account.Login,
|
||||||
|
account.EncryptedPassword,
|
||||||
|
).First(account).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
session := &Session{Account: account}
|
||||||
|
if err := tx.Create(session).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service
|
||||||
func (s *Service) RequireAuthToken(c *gin.Context) {
|
func (s *Service) RequireAuthToken(c *gin.Context) {
|
||||||
tokenAuth := c.GetHeader("Authorization")
|
tokenAuth := c.GetHeader("Authorization")
|
||||||
tokenCookie, _ := c.Cookie("photoapitoken")
|
tokenCookie, _ := c.Cookie("photoapitoken")
|
||||||
|
|
||||||
if tokenAuth != "" {
|
if tokenAuth != "" {
|
||||||
if !strings.HasPrefix(tokenAuth, "Private ") {
|
if !strings.HasPrefix(tokenAuth, "Private ") {
|
||||||
s.Error(c, http.StatusForbidden, photoserrors.ErrTokenMissing)
|
s.Error(c, http.StatusForbidden, ErrTokenMissing)
|
||||||
} else {
|
} else {
|
||||||
c.Set("token", tokenAuth[8:])
|
c.Set("token", tokenAuth[8:])
|
||||||
}
|
}
|
||||||
} else if tokenCookie != "" {
|
} else if tokenCookie != "" {
|
||||||
c.Set("token", tokenCookie)
|
c.Set("token", tokenCookie)
|
||||||
} else {
|
} else {
|
||||||
s.Error(c, http.StatusForbidden, photoserrors.ErrTokenMissing)
|
s.Error(c, http.StatusForbidden, ErrTokenMissing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,17 +78,17 @@ func (s *Service) RequireSession(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := &models.Session{}
|
sess := &Session{}
|
||||||
if err := s.DB.Preload("Account").Where("token = ?", c.GetString("token")).First(sess).Error; err != nil {
|
if err := s.DB.Preload("Account").Where("token = ?", c.GetString("token")).First(sess).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
s.Error(c, http.StatusForbidden, photoserrors.ErrSessionNotFound)
|
s.Error(c, http.StatusForbidden, ErrSessionNotFound)
|
||||||
} else {
|
} else {
|
||||||
s.Error(c, http.StatusForbidden, err)
|
s.Error(c, http.StatusForbidden, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if sess.Account == nil {
|
if sess.Account == nil {
|
||||||
s.Error(c, http.StatusInternalServerError, photoserrors.ErrSessionInvalid)
|
s.Error(c, http.StatusInternalServerError, ErrSessionInvalid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.DB.Select("updated_at").Save(sess)
|
s.DB.Select("updated_at").Save(sess)
|
||||||
@ -53,15 +96,15 @@ func (s *Service) RequireSession(c *gin.Context) {
|
|||||||
c.Set("session", sess)
|
c.Set("session", sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CurrentSession(c *gin.Context) *models.Session {
|
func (s *Service) CurrentSession(c *gin.Context) *Session {
|
||||||
return c.MustGet("session").(*models.Session)
|
return c.MustGet("session").(*Session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SessionCleaner() {
|
func (s *Service) SessionCleaner() {
|
||||||
for range time.Tick(time.Minute) {
|
for range time.Tick(time.Minute) {
|
||||||
t := time.Now().UTC().Add(-3 * time.Hour).Truncate(time.Minute)
|
t := time.Now().UTC().Add(-3 * time.Hour).Truncate(time.Minute)
|
||||||
s.LogOk.Printf("Session", "Cleaning old session < %s", t)
|
s.LogOk.Printf("Session", "Cleaning old session < %s", t)
|
||||||
if err := s.DB.Where("updated_at < ?", t).Delete(&models.Session{}).Error; err != nil {
|
if err := s.DB.Where("updated_at < ?", t).Delete(&Session{}).Error; err != nil {
|
||||||
s.LogErr.Printf("Session", "Cleaning failed: %s", err)
|
s.LogErr.Printf("Session", "Cleaning failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package photosapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package photoserrors
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Account
|
|
||||||
ErrAccountExists = errors.New("account exists")
|
|
||||||
ErrAccountAuth = errors.New("login or password incorrect")
|
|
||||||
)
|
|
@ -1,9 +0,0 @@
|
|||||||
package photoserrors
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
|
|
||||||
// Album
|
|
||||||
ErrAlbumDontExists = errors.New("album doesn't exists")
|
|
||||||
)
|
|
@ -1,9 +0,0 @@
|
|||||||
package photoserrors
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
|
|
||||||
// Panic
|
|
||||||
ErrUnexpected = errors.New("an unexpected error occur")
|
|
||||||
)
|
|
@ -1,9 +0,0 @@
|
|||||||
package photoserrors
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Request
|
|
||||||
ErrReqMissingBody = errors.New("missing body")
|
|
||||||
ErrReqNotFound = errors.New("this route doesn't exists")
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
package photoserrors
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Session
|
|
||||||
ErrSessionNotFound = errors.New("session not found")
|
|
||||||
ErrSessionInvalid = errors.New("session invalid")
|
|
||||||
ErrTokenMissing = errors.New("token missing")
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
package photoserrors
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Store
|
|
||||||
ErrStorePathNotADirectory = errors.New("store path is not a directory")
|
|
||||||
ErrStoreBadChecksum = errors.New("checksum should be sha1 in hex format")
|
|
||||||
ErrStoreBadChunkSize = errors.New("part file size should be 1MB max")
|
|
||||||
ErrStoreMissingChunks = errors.New("part checksum missing")
|
|
||||||
ErrStoreWrongChecksum = errors.New("wrong checksum")
|
|
||||||
ErrStoreMismatchChecksum = errors.New("part files doesn't match the original checksum")
|
|
||||||
ErrStoreAlreadyExists = errors.New("original file already exists")
|
|
||||||
ErrStoreChunkAlreadyExists = errors.New("chunk file already exists")
|
|
||||||
ErrStoreMissingName = errors.New("name required")
|
|
||||||
)
|
|
@ -1,40 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"encoding/base64"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Account struct {
|
|
||||||
ID uint32 `gorm:"primary_key" json:"-"`
|
|
||||||
Login string `gorm:"unique;size:64;not null" json:"login"`
|
|
||||||
Password string `gorm:"-" json:"-"`
|
|
||||||
EncryptedPassword string `gorm:"size:44;not null" json:"-"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Account) BeforeCreate(tx *gorm.DB) error {
|
|
||||||
if a.EncryptedPassword == "" {
|
|
||||||
a.EncryptPassword()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Account) EncryptPassword() {
|
|
||||||
sha1 := crypto.SHA256.New()
|
|
||||||
sha1.Write([]byte(a.Password))
|
|
||||||
a.EncryptedPassword = base64.StdEncoding.EncodeToString(sha1.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAccount(login string, password string) *Account {
|
|
||||||
a := &Account{
|
|
||||||
Login: login,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
a.EncryptPassword()
|
|
||||||
return a
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
ID uint32 `gorm:"primary_key" json:"id"`
|
|
||||||
Name string `gorm:"not null" json:"name"`
|
|
||||||
Checksum string `gorm:"unique;size:44;not null"`
|
|
||||||
Size uint64 `gorm:"not null"`
|
|
||||||
Author *Account `gorm:"constraint:OnDelete:SET NULL,OnUpdate:CASCADE" json:"author"`
|
|
||||||
AuthorId *uint32 `json:"-"`
|
|
||||||
Chunks []*FileChunk `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type FileChunk struct {
|
|
||||||
FileId uint32
|
|
||||||
File *File `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
|
|
||||||
Part uint32
|
|
||||||
Checksum string `gorm:"unique;size:44;not null"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
ID uint32 `gorm:"primary_key"`
|
|
||||||
Token string `gorm:"size:36"`
|
|
||||||
Account *Account `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
|
|
||||||
AccountId uint32 `gorm:"not null"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) BeforeCreate(tx *gorm.DB) error {
|
|
||||||
uuid, err := uuid.NewRandom()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Token = uuid.String()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSession(tx *gorm.DB, login string, password string) (*Session, error) {
|
|
||||||
account := NewAccount(login, password)
|
|
||||||
if err := tx.Where(
|
|
||||||
"login = ? and encrypted_password = ?",
|
|
||||||
account.Login,
|
|
||||||
account.EncryptedPassword,
|
|
||||||
).First(account).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
session := &Session{Account: account}
|
|
||||||
if err := tx.Create(session).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return session, nil
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package store
|
package photosstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
@ -11,8 +11,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
)
|
||||||
"gitlab.celogeek.com/photos/api/internal/photos/models"
|
|
||||||
|
var (
|
||||||
|
ErrStoreChunkAlreadyExists = errors.New("chunk file already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
@ -78,9 +80,9 @@ func (c *Chunk) Mkdir() error {
|
|||||||
return os.MkdirAll(c.Dir(), 0755)
|
return os.MkdirAll(c.Dir(), 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) Save(sess *models.Session) error {
|
func (c *Chunk) Save(login string) error {
|
||||||
if c.FileExists() {
|
if c.FileExists() {
|
||||||
return photoserrors.ErrStoreChunkAlreadyExists
|
return ErrStoreChunkAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Mkdir(); err != nil {
|
if err := c.Mkdir(); err != nil {
|
||||||
@ -98,7 +100,7 @@ func (c *Chunk) Save(sess *models.Session) error {
|
|||||||
enc := json.NewEncoder(meta)
|
enc := json.NewEncoder(meta)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
return enc.Encode(gin.H{
|
return enc.Encode(gin.H{
|
||||||
"author": sess.Account.Login,
|
"author": login,
|
||||||
"date": time.Now().UTC().Format("2006-01-02 15:04:05"),
|
"date": time.Now().UTC().Format("2006-01-02 15:04:05"),
|
||||||
"checksum": c.Sum,
|
"checksum": c.Sum,
|
||||||
"size": len(c.Bytes),
|
"size": len(c.Bytes),
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package store
|
package photosstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
photoserrors "gitlab.celogeek.com/photos/api/internal/photos/errors"
|
var (
|
||||||
|
ErrStoreMissingChunks = errors.New("part checksum missing")
|
||||||
)
|
)
|
||||||
|
|
||||||
type StoreReaderChunk struct {
|
type StoreReaderChunk struct {
|
||||||
@ -26,7 +29,7 @@ func (s *Store) NewStoreReader(chunks []string) (*StoreReader, error) {
|
|||||||
name := c.Filename()
|
name := c.Filename()
|
||||||
size := c.Size()
|
size := c.Size()
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
return nil, photoserrors.ErrStoreMissingChunks
|
return nil, ErrStoreMissingChunks
|
||||||
}
|
}
|
||||||
sr.chunks[i] = StoreReaderChunk{name, size}
|
sr.chunks[i] = StoreReaderChunk{name, size}
|
||||||
sr.Size += size
|
sr.Size += size
|
||||||
|
Loading…
x
Reference in New Issue
Block a user