From aa5f7f2f9e75346b28bf0d0ef0aeee0daea77714 Mon Sep 17 00:00:00 2001 From: Chakib Benziane Date: Thu, 21 Mar 2019 18:44:40 +0100 Subject: [PATCH] full single file upload working --- bit4sat.sqbpro | 1 + db/db.go | 12 ++++ go.mod | 6 ++ go.sum | 29 ++++++++ storage/storage.go | 28 +++++++- storage/upload_ctrl.go | 142 +++++++++++++++++++++++++++++++++++----- storage/upload_model.go | 116 +++++++++++++++++++++++++++++++- utils/gin_errors.go | 19 ++++++ utils/paths.go | 8 +-- utils/paths_test.go | 35 ++++++++++ 10 files changed, 371 insertions(+), 25 deletions(-) create mode 100644 bit4sat.sqbpro create mode 100644 utils/gin_errors.go create mode 100644 utils/paths_test.go diff --git a/bit4sat.sqbpro b/bit4sat.sqbpro new file mode 100644 index 0000000..a34092e --- /dev/null +++ b/bit4sat.sqbpro @@ -0,0 +1 @@ +
SELECT EXISTS (SELECT upload_id FROM upload WHERE upload_id = '1Ijbco3pYIrl2VzTslERxszEHG1');SELECT * FROM upload JOIN upload_status ON upload.status = upload_status.type;
diff --git a/db/db.go b/db/db.go index d044050..ac23664 100644 --- a/db/db.go +++ b/db/db.go @@ -68,6 +68,18 @@ func (d *Database) Open() error { return nil } +func RollbackTx(tx *sqlx.Tx, callback func()) { + log.Println("Rolling back transactions !") + err := tx.Rollback() + if err != nil { + log.Fatal(err) + } + + if callback != nil { + callback() + } +} + func init() { DB = &Database{} DB.Open() diff --git a/go.mod b/go.mod index 70d6b79..fc00f64 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,14 @@ require ( github.com/gin-gonic/gin v1.3.0 github.com/golang/protobuf v1.3.1 // indirect github.com/jmoiron/sqlx v1.2.0 + github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-isatty v0.0.7 // indirect github.com/mattn/go-sqlite3 v1.10.0 github.com/segmentio/ksuid v1.0.2 + github.com/stretchr/testify v1.3.0 // indirect github.com/ugorji/go/codec v0.0.0-20190315113641-a70535d8491c // indirect + golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 // indirect + golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54 // indirect + google.golang.org/appengine v1.5.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index efed9ac..743531b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/cors v0.0.0-20190301062745-f9e10995c85a h1:zBycVvXa03SIX+jdMv8wGu9TMDMWdN8EhaR1FoeKHNo= github.com/gin-contrib/cors v0.0.0-20190301062745-f9e10995c85a/go.mod h1:pL2kNE+DgDU+eQ+dary5bX0Z6LPP8nR6Mqs1iejILw4= @@ -6,13 +8,21 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= @@ -20,23 +30,42 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/segmentio/ksuid v1.0.2 h1:9yBfKyw4ECGTdALaF09Snw3sLJmYIX6AbPJrAy6MrDc= github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs= github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20190315113641-a70535d8491c h1:20Yyqg0mvFOyG3m7ejVqHEa03CBa3hTttx9jiYLkjYU= github.com/ugorji/go/codec v0.0.0-20190315113641-a70535d8491c/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54 h1:xe1/2UUJRmA9iDglQSlkx8c5n3twv58+K0mPpC2zmhA= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= diff --git a/storage/storage.go b/storage/storage.go index 4d4ba0e..c5bb3a8 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,23 +1,49 @@ package storage import ( + "io" "log" + "mime/multipart" "os" + "path/filepath" "git.sp4ke.com/sp4ke/bit4sat-server.git/utils" ) const ( - StoragePath = "storage" + StoragePath = "file-storage" StoragePathEnv = "BIT4SAT_STORAGE_PATH" ) +var ( + RuntimeStoragePath string +) + +func GetStoreDestination(filename string) string { + return filepath.Join(RuntimeStoragePath, filename) +} + +func StoreFormFile(src multipart.File, filename string) error { + fd, err := os.Create(GetStoreDestination(filename)) + defer fd.Close() + if err != nil { + return err + } + + src.Seek(0, 0) + _, err = io.Copy(fd, src) + + return err +} + func init() { path, set := os.LookupEnv(StoragePathEnv) if !set { path = StoragePath } + RuntimeStoragePath = path + err := utils.Mkdir(path) if err != nil { log.Fatal(err) diff --git a/storage/upload_ctrl.go b/storage/upload_ctrl.go index f70c05e..208840b 100644 --- a/storage/upload_ctrl.go +++ b/storage/upload_ctrl.go @@ -1,9 +1,15 @@ package storage import ( + "crypto/sha256" + "encoding/hex" "fmt" + "io" + "log" "net/http" + "os" + "git.sp4ke.com/sp4ke/bit4sat-server.git/db" "git.sp4ke.com/sp4ke/bit4sat-server.git/utils" "github.com/gin-gonic/gin" "github.com/segmentio/ksuid" @@ -15,21 +21,25 @@ func (ctrl UploadCtrl) New(c *gin.Context) { uploadForm := UploadForm{} if err := c.ShouldBindJSON(&uploadForm); err != nil { - c.JSON(http.StatusNotAcceptable, gin.H{ - "status": http.StatusNotAcceptable, - "error": "could not parse form", - }) - c.Abort() + utils.JSONErr(c, http.StatusNotAcceptable, + "could not parse form") return } // Create unique id id := ksuid.New() + tx, err := db.DB.Handle.Beginx() + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + for _, file := range uploadForm.Files { up := &Upload{} up.ID = id.String() + log.Println(file.Name) up.FileName, up.FileExt = utils.CleanFileName(file.Name) up.FileSize = file.Size @@ -37,18 +47,20 @@ func (ctrl UploadCtrl) New(c *gin.Context) { up.SHA256 = file.SHA256 up.Status = UpNew - err := up.Write() + err := up.TxWrite(tx) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "error": "could not start new upload", - "msg": err.Error(), - }) - c.Abort() + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + db.RollbackTx(tx, nil) return } } + err = tx.Commit() + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + c.JSON(http.StatusOK, gin.H{ "status": http.StatusOK, "result": gin.H{ @@ -59,19 +71,113 @@ func (ctrl UploadCtrl) New(c *gin.Context) { func (ctrl UploadCtrl) Upload(c *gin.Context) { + // Check that upload ID exists + id := c.Param("id") + + exists, err := IdExists(id) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + if !exists { + utils.JSONErr(c, http.StatusNotFound, "id not found") + return + } + form, err := c.MultipartForm() if err != nil { - c.JSON(http.StatusNotAcceptable, gin.H{ - "status": http.StatusNotAcceptable, - "error": err.Error(), - }) - c.Abort() + utils.JSONErr(c, http.StatusNotAcceptable, + fmt.Sprintf("Could not parse form: %s", err.Error())) return } + files := form.File["upload[]"] + tx, err := db.DB.Handle.Beginx() + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + for _, file := range files { - fmt.Println(file.Filename) + log.Printf("Handling %s", file.Filename) + + // Get the file's sha256 + hasher := sha256.New() + + formFd, err := file.Open() + defer formFd.Close() + + if err != nil { + utils.JSONErr( + c, http.StatusInternalServerError, + fmt.Sprintf("Could not read file %s", file.Filename), + ) + db.RollbackTx(tx, nil) + return + } + + _, err = io.Copy(hasher, formFd) + if err != nil { + utils.JSONErr(c, http.StatusInternalServerError, + "") + db.RollbackTx(tx, nil) + return + } + + sum256 := hex.EncodeToString(hasher.Sum(nil)) + + // Get the file's metadata from upload table + up, err := GetByHashID(sum256, id) + if err != nil { + utils.JSONErrPriv(c, http.StatusNotFound, err) + db.RollbackTx(tx, nil) + return + } + + log.Printf("Found received file's %s metadata in local database\n", up.FileName) + //log.Println(up) + + // Store file + if up.Status == UpStored { + utils.JSONErr(c, http.StatusConflict, + fmt.Sprintf("%s already uploaded", up.FileName)) + return + } + // + // + log.Println("Storing file") + err = StoreFormFile(formFd, up.SHA256+up.FileExt) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + db.RollbackTx(tx, nil) + return + } + + log.Printf("%s stored at %s", up.FileName, + GetStoreDestination(up.SHA256+up.FileExt)) + + // Setting status to stored + log.Println("Updating upload status to stored") + up.Status = UpStored + err = up.TxSetState(tx, UpStored) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + db.RollbackTx(tx, func() { + err := os.Remove(GetStoreDestination(up.SHA256 + up.FileExt)) + if err != nil { + log.Println(err) + } + }) + return + } + } + + err = tx.Commit() + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return } c.JSON(http.StatusOK, gin.H{ diff --git a/storage/upload_model.go b/storage/upload_model.go index 4adf713..ff6b5a8 100644 --- a/storage/upload_model.go +++ b/storage/upload_model.go @@ -1,10 +1,12 @@ package storage import ( + "database/sql" "errors" "log" "git.sp4ke.com/sp4ke/bit4sat-server.git/db" + "github.com/jmoiron/sqlx" "github.com/mattn/go-sqlite3" ) @@ -19,22 +21,52 @@ const ( file_type TEXT DEFAULT '', file_size INTEGER NOT NULL, file_ext TEXT DEFAULT '', - status INTEGER DEFAULT 0 + status INTEGER DEFAULT 0, + FOREIGN KEY (status) REFERENCES upload_status(type), + UNIQUE (upload_id, sha256) ); ` + DBUploadView = ` +CREATE VIEW IF NOT EXISTS "upload_with_status" AS SELECT * FROM upload JOIN upload_status ON +upload.status = upload_status.type + ` + + DBUploadStatusSchema = `CREATE TABLE IF NOT EXISTS upload_status + ( + type INTEGER PRIMARY KEY, + status TEXT NOT NULL UNIQUE + ) + ` + QNewUpload = `INSERT INTO upload (upload_id, sha256, file_name, file_type, file_size, file_ext, status) VALUES (:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext, :status)` + + QSetStatus = `UPDATE upload SET status = :status WHERE upload_id = :upload_id ` + + QGetByHashID = `SELECT * FROM upload + WHERE + sha256 = ? + AND + upload_id = ?` ) const ( UpNew = iota + UpStored UpWaitingPayment UpReady ) +var UploadStatus = map[int]string{ + UpNew: "new upload", + UpStored: "stored", + UpWaitingPayment: "waiting payment", + UpReady: "ready", +} + var ( ErrDoesNotExist = errors.New("does not exist") ErrAlreadyExists = errors.New("already exists") @@ -50,6 +82,62 @@ type Upload struct { Status int `db:"status"` } +// Returns true if id exists in DB +func IdExists(id string) (exists bool, err error) { + qUploadExists := ` + SELECT EXISTS (SELECT upload_id FROM upload where upload_id = ?) + ` + err = DB.Handle.Get(&exists, qUploadExists, id) + + // No result found is also no result + if err == sql.ErrNoRows { + err = nil + } + + return +} + +// Get a file by upload id and hash +func GetByHashID(sha256 string, id string) (*Upload, error) { + var up Upload + err := DB.Handle.Get(&up, QGetByHashID, sha256, id) + + if err == sql.ErrNoRows { + return nil, ErrDoesNotExist + } + + if err != nil { + return nil, err + } + + return &up, nil +} + +func (u *Upload) TxSetState(tx *sqlx.Tx, status int) error { + _, err := tx.NamedExec(QSetStatus, u) + + if err != nil { + return err + } + + return nil +} + +func (u *Upload) TxWrite(tx *sqlx.Tx) error { + + _, err := tx.NamedExec(QNewUpload, u) + sqlErr, isSqlErr := err.(sqlite3.Error) + if isSqlErr && sqlErr.Code == sqlite3.ErrConstraint { + return ErrAlreadyExists + } + + if err != nil { + return err + } + + return nil +} + func (u *Upload) Write() error { _, err := DB.Handle.NamedExec(QNewUpload, u) sqlErr, isSqlErr := err.(sqlite3.Error) @@ -70,4 +158,30 @@ func init() { log.Fatal(err) } + _, err = DB.Handle.Exec(DBUploadView) + if err != nil { + log.Fatal(err) + } + + _, err = DB.Handle.Exec(DBUploadStatusSchema) + if err != nil { + log.Fatal(err) + } + + // Populate status types + query := `INSERT INTO upload_status (type, status) VALUES(?,?)` + for k, v := range UploadStatus { + _, err := DB.Handle.Exec(query, k, v) + if err != nil { + sqlErr, ok := err.(sqlite3.Error) + if ok && sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique { + log.Panic(err) + } + + if !ok { + log.Panic(err) + } + } + } + } diff --git a/utils/gin_errors.go b/utils/gin_errors.go new file mode 100644 index 0000000..d9f9f80 --- /dev/null +++ b/utils/gin_errors.go @@ -0,0 +1,19 @@ +package utils + +import ( + "log" + + "github.com/gin-gonic/gin" +) + +func JSONErr(c *gin.Context, status int, msg string) { + c.AbortWithStatusJSON(status, gin.H{ + "status": status, + "error": msg, + }) +} + +func JSONErrPriv(c *gin.Context, status int, err error) { + log.Println(err) + JSONErr(c, status, "") +} diff --git a/utils/paths.go b/utils/paths.go index c725682..bd0f62f 100644 --- a/utils/paths.go +++ b/utils/paths.go @@ -1,7 +1,6 @@ package utils import ( - "fmt" "os" "path/filepath" "strings" @@ -19,11 +18,10 @@ func Mkdir(path ...string) error { func CleanFileName(path string) (name string, ext string) { - base := filepath.Base(path) - ext = filepath.Ext(base) - fmt.Println(ext) + name = filepath.Base(path) + ext = filepath.Ext(name) if len(ext) > 0 { - name = strings.Split(base, ".")[0] + name = strings.Split(name, ".")[0] } return diff --git a/utils/paths_test.go b/utils/paths_test.go new file mode 100644 index 0000000..11247cc --- /dev/null +++ b/utils/paths_test.go @@ -0,0 +1,35 @@ +package utils + +import "testing" + +type CleanName struct { + name string + ext string +} + +func TestCleanFileName(t *testing.T) { + + tests := map[string]CleanName{ + "noext": CleanName{"noext", ""}, + "with.ext": CleanName{"with", ".ext"}, + "path/with.ext": CleanName{"with", ".ext"}, + "path/noext": CleanName{"noext", ""}, + } + + for name, expected := range tests { + + t.Run(name, func(t *testing.T) { + + resName, resExt := CleanFileName(name) + + if resName != expected.name { + t.Fail() + } + + if resExt != expected.ext { + t.Fail() + } + + }) + } +}