From 849d5ebff531cd8d2624498b33296dad0d87e114 Mon Sep 17 00:00:00 2001 From: Chakib Benziane Date: Tue, 2 Apr 2019 15:53:52 +0200 Subject: [PATCH] mvp paid upload (missing download link) --- api/handlers.go | 82 +++++++++--- api/routes.go | 7 +- ln/api.go | 33 +++-- ln/invoice.go | 15 +++ main.go | 1 + storage/status_test.go | 28 ++++ storage/upload_ctrl.go | 188 +++++++++++++++++++------- storage/upload_model.go | 288 +++++++++++++++++++++++++++------------- utils/gin_errors.go | 2 +- web/src/Pay.vue | 88 ++++++++++++ web/src/Timer.js | 63 +++++++++ web/src/Timer.vue | 46 +++++++ web/src/Upload.vue | 60 +++++++++ 13 files changed, 731 insertions(+), 170 deletions(-) create mode 100644 storage/status_test.go create mode 100644 web/src/Pay.vue create mode 100644 web/src/Timer.js create mode 100644 web/src/Timer.vue create mode 100644 web/src/Upload.vue diff --git a/api/handlers.go b/api/handlers.go index 86d1419..1b6f1aa 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -1,20 +1,60 @@ package api import ( - "encoding/json" "fmt" "log" "net/http" - "git.sp4ke.com/sp4ke/bit4sat/bus" "git.sp4ke.com/sp4ke/bit4sat/db" "git.sp4ke.com/sp4ke/bit4sat/ln" "git.sp4ke.com/sp4ke/bit4sat/storage" "git.sp4ke.com/sp4ke/bit4sat/utils" + "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/mediocregopher/radix/v3" ) +func sessionHandler(c *gin.Context) { + session := sessions.Default(c) + + lastUp := session.Get(storage.SessLastUploadName) + if lastUp != nil { + + // Get upload status + key := fmt.Sprintf("upload_status_%s", lastUp.(string)) + + var upStatus storage.UpStatus + err := db.DB.Redis.Do(radix.FlatCmd(&upStatus, "GET", key)) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, + fmt.Errorf("error finding upload status %s", lastUp)) + fmt.Println(err) + return + } + + invoice, err := storage.GetUploadInvoice(lastUp.(string)) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + if invoice.Id != "" { + c.JSON(http.StatusOK, gin.H{ + "uploadId": lastUp, + "status": upStatus, + "invoice": invoice, + }) + return + } + + } + + c.JSON(http.StatusOK, gin.H{ + "uploadId": 0, + }) + return +} + func invoiceCbHandler(c *gin.Context) { invoice := ln.Invoice{} @@ -43,9 +83,11 @@ func invoiceCbHandler(c *gin.Context) { } invoice.UploadId = uploadId - // - // Set upload status to UpWaitingStorage (paid) - err = storage.SetUploadStatus(invoice.UploadId, storage.UpWaitingStorage) + + // Invoice paid !!!! + + // Set upload status to paid + err = storage.SetUploadStatus(invoice.UploadId, storage.UpPaid) if err != nil { panic(err) } @@ -57,24 +99,24 @@ func invoiceCbHandler(c *gin.Context) { } // publish invoice was updated to upload_id_paid channel - msg := bus.Message{} - msg.Type = bus.PaymentReceived - msg.UploadId = invoice.UploadId - msg.Data = gin.H{ - "invoice": invoice, - } + //msg := bus.Message{} + //msg.Type = bus.PaymentReceived + //msg.UploadId = invoice.UploadId + //msg.Data = gin.H{ + //"invoice": invoice, + //} - msgJson, err := json.Marshal(msg) - if err != nil { - panic(err) - } + //msgJson, err := json.Marshal(msg) + //if err != nil { + //panic(err) + //} - log.Printf("Notifying upload paid %s", invoice.UploadId) - key := fmt.Sprintf("%s_%s", bus.UploadUpdateChannelPrefix, - invoice.UploadId) + //log.Printf("Notifying upload paid %s", invoice.UploadId) + //key := fmt.Sprintf("%s_%s", bus.UploadUpdateChannelPrefix, + //invoice.UploadId) - err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH", - key, msgJson)) + //err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH", + //key, msgJson)) return } diff --git a/api/routes.go b/api/routes.go index ae6e6d2..ee5eaf2 100644 --- a/api/routes.go +++ b/api/routes.go @@ -3,7 +3,6 @@ package api import ( "git.sp4ke.com/sp4ke/bit4sat/config" "git.sp4ke.com/sp4ke/bit4sat/storage" - "git.sp4ke.com/sp4ke/bit4sat/ws" "github.com/gin-contrib/cors" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/redis" @@ -20,14 +19,18 @@ type API struct { func (api *API) Run() { + // Get last session if it exists + api.router.GET("/api/session", sessionHandler) + uploadRoute := api.router.Group("/api/upload") { uploadRoute.POST("", UploadCtrl.New) uploadRoute.PUT(":id", UploadCtrl.Upload) + uploadRoute.GET("/check/:id", UploadCtrl.CheckStatus) } // Websocket server - api.router.GET("/ws", ws.Serve) + //api.router.GET("/ws", ws.Serve) // LN charge callback api.router.POST("/"+config.ChargeCallbackEndpoint, invoiceCbHandler) diff --git a/ln/api.go b/ln/api.go index 6c94a76..6315ead 100644 --- a/ln/api.go +++ b/ln/api.go @@ -34,11 +34,14 @@ func Info() (gin.H, error) { } // Shoudl be called in goroutine -func PollPaidInvoice(in *Invoice, paidChan chan<- *Invoice) { +func PollPaidInvoice(in *Invoice, invoiceChan chan<- *Invoice, errorChan chan<- error) { + + invoice := Invoice{} + invoice.UploadId = in.UploadId reqParams := url.Values{} // Timeout in seconds - reqParams.Set("timeout", strconv.Itoa(30)) + reqParams.Set("timeout", strconv.Itoa(60)) reqURI := fmt.Sprintf("%s/%s/wait?%s", getUrl(PollInvoiceEndpoint), in.Id, @@ -47,6 +50,7 @@ func PollPaidInvoice(in *Invoice, paidChan chan<- *Invoice) { log.Printf("polling to %s", reqURI) var err error + var jsonDec *json.Decoder resp := &http.Response{} for { @@ -55,6 +59,7 @@ func PollPaidInvoice(in *Invoice, paidChan chan<- *Invoice) { if err != nil { log.Printf("Error: ", err) } + jsonDec = json.NewDecoder(resp.Body) if resp.StatusCode == http.StatusPaymentRequired { log.Printf("invoice %s not yet paid", in.Id) @@ -63,26 +68,32 @@ func PollPaidInvoice(in *Invoice, paidChan chan<- *Invoice) { if resp.StatusCode == http.StatusGone { log.Printf("invoice expired ", in.Id) + invoice.Expired = true + break } if resp.StatusCode == http.StatusOK { break - } + } else { + log.Println("else !") + + // This section handles unknown answer from ln-charge + log.Printf("InvoicePoll Error: unknown resopnse %s", resp.Status) + jsonDec.Decode(&invoice) - //unknownData := gin.H{} - //jsonDec := json.NewDecoder(resp.Body) - //jsonDec.Decode(&data) - log.Printf("InvoicePoll Error: unknown resopnse %s", resp.Status) + close(invoiceChan) + errorChan <- fmt.Errorf("%s: %s", resp.Status, invoice) + + return + } } - invoice := Invoice{} - invoice.UploadId = in.UploadId - jsonDec := json.NewDecoder(resp.Body) jsonDec.Decode(&invoice) log.Printf("Invoice paid %s", invoice) - paidChan <- &invoice + invoiceChan <- &invoice + log.Printf("quit polling %s", in.UploadId) } func UploadInvoice(amount float32, curr Currency, uploadId string) (*Invoice, error) { diff --git a/ln/invoice.go b/ln/invoice.go index 9f1b490..ac92f1b 100644 --- a/ln/invoice.go +++ b/ln/invoice.go @@ -1,6 +1,7 @@ package ln import ( + "encoding/json" "fmt" "strconv" "time" @@ -54,6 +55,11 @@ func (t *timestamp) UnmarshalJSON(in []byte) error { return nil } +func (t timestamp) MarshalJSON() ([]byte, error) { + str := strconv.Itoa(int(time.Time(t).Unix())) + return []byte(str), nil +} + func (t timestamp) String() string { return time.Time(t).Format(time.RFC3339) } @@ -71,4 +77,13 @@ type Invoice struct { PaidAt timestamp `json:"paid_at"` CreatedAt timestamp `json:"created_at"` ExpiresAt timestamp `json:"expires_at"` + Expired bool `json:"-"` +} + +func (i Invoice) MarshalBinary() ([]byte, error) { + return json.Marshal(i) +} + +func (i *Invoice) UnmarshalBinary(b []byte) error { + return json.Unmarshal(b, &i) } diff --git a/main.go b/main.go index 064e5fc..1384fc2 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( ) func main() { + defer db.DB.Sql.Close() api := api.NewAPI() diff --git a/storage/status_test.go b/storage/status_test.go new file mode 100644 index 0000000..3af0e19 --- /dev/null +++ b/storage/status_test.go @@ -0,0 +1,28 @@ +package storage + +import ( + "testing" +) + +func TestUploadStatus(t *testing.T) { + + // start with new status + // + //status := UpPaid | UpStored + //j, _ := json.Marshal(status) + //fmt.Printf("%s", j) + + //fmt.Printf("%32b\n", UpPaid|UpStored) + + //err := SetUploadStatus("1", status) + //if err != nil { + //t.Error(err) + //} + + // Set to stored + //status |= UpStored + //fmt.Println(status) + //if !status.IsStored() { + //t.Error() + //} +} diff --git a/storage/upload_ctrl.go b/storage/upload_ctrl.go index a144187..4027f93 100644 --- a/storage/upload_ctrl.go +++ b/storage/upload_ctrl.go @@ -3,21 +3,21 @@ package storage import ( "crypto/sha256" "encoding/hex" - "encoding/json" "fmt" "io" "log" "net/http" "os" - "git.sp4ke.com/sp4ke/bit4sat/bus" "git.sp4ke.com/sp4ke/bit4sat/db" "git.sp4ke.com/sp4ke/bit4sat/ln" "git.sp4ke.com/sp4ke/bit4sat/utils" - "git.sp4ke.com/sp4ke/bit4sat/ws" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" - "github.com/mediocregopher/radix/v3" +) + +const ( + SessLastUploadName = "last-upload" ) type UploadCtrl struct{} @@ -54,7 +54,6 @@ func (ctrl UploadCtrl) New(c *gin.Context) { up.FileSize = file.Size up.FileType = file.Type up.SHA256 = file.SHA256 - up.Status = UpNew err := up.TxWrite(tx) if err != nil { @@ -70,30 +69,19 @@ func (ctrl UploadCtrl) New(c *gin.Context) { return } - // Register this uploadId to the client's websocket update channel - // websocket on the pubsub channel update_websocket_[websocket id] - - session := sessions.Default(c) - webSocketId := session.Get(ws.WebsocketIdName) - if webSocketId == nil { - utils.JSONErrPriv(c, http.StatusInternalServerError, - fmt.Errorf("no session id found for web socket")) + // Set upload status to new + err = SetUploadStatus(uploadId, UpNew) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } - msg := bus.Message{ - UploadId: uploadId, - } - msg.Type = bus.SetUploadId - jsonMsg, err := json.Marshal(msg) + // Upload seems valid, handle new upload procedure + session := sessions.Default(c) - channelName := fmt.Sprintf("%s_%s", bus.WebsocketPubSubPrefix, webSocketId) - - if err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH", - channelName, jsonMsg)); err != nil { - utils.JSONErrPriv(c, http.StatusInternalServerError, err) - return - } + // Set the uploadId as last upload for session + session.Set(SessLastUploadName, uploadId) + session.Save() // If not free upload generate a an invoice // TODO: check if upload fee is free @@ -116,12 +104,6 @@ func (ctrl UploadCtrl) New(c *gin.Context) { } - err = SetUploadStatus(uploadId, UpWaitingPayment) - if err != nil { - utils.JSONErrPriv(c, http.StatusInternalServerError, err) - return - } - // Start watching upload payment status for this client //err = WatchUploadPayment(invoice) @@ -131,14 +113,10 @@ func (ctrl UploadCtrl) New(c *gin.Context) { //return //} - result := gin.H{ - "upload_id": uploadId, - "invoice": invoice, - } - c.JSON(http.StatusOK, gin.H{ - "status": "waiting for payment", - "result": result, + "status": UpNew, + "invoice": invoice, + "upload_id": uploadId, }) return @@ -155,7 +133,107 @@ func (ctrl UploadCtrl) New(c *gin.Context) { }) } +} +func (ctrl UploadCtrl) CheckStatus(c *gin.Context) { + uploadId := c.Param("id") + + exists, err := IdExists(uploadId) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + if !exists { + utils.JSONErr(c, http.StatusNotFound, "id not found") + return + } + + uploadStatus, err := GetUploadStatus(uploadId) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + if uploadStatus&UpNew != 0 { + c.JSON(http.StatusCreated, gin.H{ + "status": uploadStatus, + }) + return + } + + if uploadStatus.WaitPay() { + + // we should be in waiting payment meaning we have an invoice + invoice, err := GetUploadInvoice(uploadId) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + invoiceChan := make(chan *ln.Invoice) + errorChan := make(chan error) + + log.Println("starting go routine") + // If waiting payment, wait until invoice is paid + go ln.PollPaidInvoice(invoice, invoiceChan, errorChan) + + // Block until payment done or error + log.Println("blocking") + select { + case invoice := <-invoiceChan: + // if expired return with expired response + if invoice.Expired { + err := SetUploadStatus(invoice.UploadId, UpPayExpired) + if err != nil { + log.Printf("Redis error: %s", err) + } + + c.JSON(http.StatusGone, gin.H{ + "status": uploadStatus, + "invoice": invoice, + }) + + return + } + + //////////////// + ///// Invoice was paid + //////////////// + + uploadStatus |= UpPaid + err := SetUploadStatus(invoice.UploadId, uploadStatus) + if err != nil { + log.Printf("Redis error: %s", err) + } + + // Update Upload Invoice + err = SetUploadInvoice(invoice.UploadId, invoice) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + // invoice was paid + c.JSON(http.StatusAccepted, gin.H{ + "status": uploadStatus, + "invoice": invoice, + }) + + return + + case err := <-errorChan: + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + + } + + } else { + c.JSON(http.StatusOK, gin.H{ + "status": uploadStatus, + }) + } + + return } func (ctrl UploadCtrl) Upload(c *gin.Context) { @@ -174,6 +252,21 @@ func (ctrl UploadCtrl) Upload(c *gin.Context) { return } + // Check if payment done + // NOTE: we pre upload and remove the files if the payment was expired + //uploadStatus, err := GetUploadStatus(uploadId) + //if err != nil { + //utils.JSONErrPriv(c, http.StatusInternalServerError, err) + //return + //} + + //if uploadStatus < UpWaitingStorage { + + //utils.JSONErr(c, http.StatusPaymentRequired, + //fmt.Sprintf("invoice not paid")) + //return + //} + form, err := c.MultipartForm() if err != nil { utils.JSONErr(c, http.StatusNotAcceptable, @@ -226,10 +319,9 @@ func (ctrl UploadCtrl) Upload(c *gin.Context) { } log.Printf("Found received file's %s metadata in local database\n", up.FileName) - //log.Println(up) - // Store file - if up.Status == UpStored { + // If file already stored for this upload report conflict + if up.Stored { utils.JSONErr(c, http.StatusConflict, fmt.Sprintf("%s already uploaded", up.FileName)) return @@ -248,11 +340,13 @@ func (ctrl UploadCtrl) Upload(c *gin.Context) { GetStoreDestination(up.SHA256+up.FileExt)) // Setting status to stored - log.Println("Updating upload status to stored") - up.Status = UpStored - err = up.TxSetState(tx, UpStored) + log.Println("Updating file upload stored status") + + err = up.TxSetFileStored(tx) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) + + // Rollback and remove the file db.RollbackTx(tx, func() { err := os.Remove(GetStoreDestination(up.SHA256 + up.FileExt)) if err != nil { @@ -269,6 +363,7 @@ func (ctrl UploadCtrl) Upload(c *gin.Context) { return } + // Set the whole transaction as stored err = SetUploadStatus(uploadId, UpStored) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) @@ -276,8 +371,9 @@ func (ctrl UploadCtrl) Upload(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "status": http.StatusOK, - "result": "uploaded", + "status": http.StatusOK, + "result": "uploaded", + "store_status": UpStored, }) } diff --git a/storage/upload_model.go b/storage/upload_model.go index 2ced60a..a65d082 100644 --- a/storage/upload_model.go +++ b/storage/upload_model.go @@ -2,12 +2,13 @@ package storage import ( "database/sql" + "encoding/binary" "encoding/json" "errors" "fmt" "log" + "math/bits" - "git.sp4ke.com/sp4ke/bit4sat/bus" "git.sp4ke.com/sp4ke/bit4sat/db" "git.sp4ke.com/sp4ke/bit4sat/ln" "github.com/jmoiron/sqlx" @@ -29,41 +30,18 @@ const ( file_type varchar(255) DEFAULT '', file_size integer NOT NULL, file_ext varchar(255) DEFAULT '', - fee integer NOT NULL DEFAULT 0, - status integer DEFAULT 0 REFERENCES upload_status(type), + ask_fee integer NOT NULL DEFAULT 0, + stored boolean DEFAULT '0', UNIQUE (upload_id, sha256) ); ` - DBUploadView = ` -CREATE OR REPLACE VIEW upload_with_status -AS SELECT - upload.upload_id, - upload.sha256, - upload_status.status, - upload.file_name, - upload.file_type, - upload.file_size, - upload.file_ext, - upload.fee - FROM upload -JOIN upload_status ON -upload.status = upload_status.type - ` - - DBUploadStatusSchema = `CREATE TABLE IF NOT EXISTS upload_status - ( - type INT NOT NULL PRIMARY KEY, - status CHAR(255) NOT NULL UNIQUE - ) - ` - QNewUpload = `INSERT INTO upload - (upload_id, sha256, file_name, file_type, file_size, file_ext, status, fee) + (upload_id, sha256, file_name, file_type, file_size, file_ext, stored, ask_fee) VALUES - (:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext, :status, :fee)` + (:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext, :stored, :ask_fee)` - QSetStatus = `UPDATE upload SET status = :status WHERE upload_id = :upload_id ` + QSetStored = `UPDATE upload SET stored = :stored WHERE upload_id = :upload_id ` QGetByHashID = `SELECT upload_id, sha256, @@ -71,27 +49,150 @@ upload.status = upload_status.type file_type, file_size, file_ext, - fee, - status + ask_fee, + stored FROM upload WHERE sha256 = $1 AND upload_id = $2` ) +type UpStatus uint32 + +func (st UpStatus) MarshalJSON() ([]byte, error) { + res := map[string]string{} + res["pay_status"] = st.PrintPayStatus() + res["store_status"] = st.PrintStoreStatus() + + return json.Marshal(res) +} + +func (st *UpStatus) UnmarshalBinary(b []byte) error { + fmt.Printf("%#v\n", b) + + // first 4 bits are reseved + // Single byte will be for 0 value + if len(b) == 1 { + *st = 0 + return nil + } + + view := binary.BigEndian.Uint32(b) + view = bits.Reverse32(view) + + fmt.Printf("%32b\n", view) + log.Printf("%d\n", view) + + // 1. Convert 3 last bytes to int + + //res, err := strconv.ParseUint(string(b), 16, ) + //if err != nil { + //return err + //} + //fmt.Println(res) + + //buf := bytes.NewBuffer(b) + + //firstByte := uint8(b[0]) + + //err := binary.Read(buf, binary.) + + *st = UpStatus(view) + + return nil +} + +// Get list of positions set +func (st UpStatus) GetFlagPositions() []int { + var i uint32 + + var setBits []int + + for i = 31; i > 0; i-- { + if st&(1< waiting payment (optional) --> waiting storage (client upload) -// --> Sotred --> Ready (sharing url sent to client and acked) -var UploadStatus = map[int]string{ - UpNew: "new upload", - UpWaitingPayment: "waiting payment", - UpWaitingStorage: "waiting storage", - UpStored: "stored", - UpReady: "ready", +var UploadStatus = map[UpStatus]string{ + UpNew: "new upload", + + // Payment + UpPayExpired: "expired", + UpPaid: "paid", + WaitPay: "waiting", + + // Storage + WaitStore: "waiting storage", + UpStored: "stored", } var ( @@ -101,13 +202,14 @@ var ( type Upload struct { ID string `db:"upload_id"` + Free bool `db:"-"` // is this a free upload SHA256 string `db:"sha256"` FileName string `db:"file_name"` FileType string `db:"file_type"` FileSize int64 `db:"file_size"` FileExt string `db:"file_ext"` - Status int `db:"status"` - Fee int `db:"fee"` + Stored bool `db:"stored"` + AskFee int `db:"ask_fee"` // fee asked for download } // TODO: sync from redis to db @@ -115,9 +217,53 @@ type Upload struct { //} -func SetUploadStatus(id string, status int) error { +func GetUploadInvoice(uploadId string) (*ln.Invoice, error) { + invoice := ln.Invoice{} + + uploadInvoiceKey := fmt.Sprintf("upload_%s_invoice", uploadId) + + err := DB.Redis.Do(radix.FlatCmd(&invoice, "GET", uploadInvoiceKey)) + if err != nil { + return nil, err + } + + return &invoice, nil +} + +func SetUploadStatus(id string, status UpStatus) error { + log.Printf("setting upload status for %s", id) + key := fmt.Sprintf("upload_status_%s", id) - return DB.Redis.Do(radix.FlatCmd(nil, "SET", key, status)) + + if status == UpNew { + return DB.Redis.Do(radix.FlatCmd(nil, "SETBIT", key, 31, 0)) + } + + log.Println("setting upload status for bit positions ", status.GetFlagPositions()) + // get bit positions + + for _, offset := range status.GetFlagPositions() { + log.Printf("setting bit at position %d", offset) + err := DB.Redis.Do(radix.FlatCmd(nil, + "SETBIT", key, offset, 1)) + if err != nil { + return err + } + } + + log.Println("done set bit") + + return nil +} + +func GetUploadStatus(id string) (status UpStatus, err error) { + log.Println("Getting upload status") + + key := fmt.Sprintf("upload_status_%s", id) + + err = DB.Redis.Do(radix.FlatCmd(&status, "GET", key)) + + return } func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error { @@ -138,28 +284,11 @@ func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error { return DB.Redis.Do(radix.FlatCmd(nil, "SET", invoiceUploadKey, uploadId)) } -func WatchUploadPayment(invoice *ln.Invoice) error { - - log.Println("watching invoice ", invoice) - - invoiceJson, err := json.Marshal(invoice) - if err != nil { - return err - } - - return DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH", bus.WatchInvoicesChannelName, invoiceJson)) - - //TODO: Notify client on websocket - - return nil -} - // Returns true if id exists in DB func IdExists(id string) (exists bool, err error) { key := fmt.Sprintf("upload_status_%s", id) err = DB.Redis.Do(radix.Cmd(&exists, "EXISTS", key)) - return } @@ -180,8 +309,9 @@ func GetByHashID(sha256 string, id string) (*Upload, error) { return &up, nil } -func (u *Upload) TxSetState(tx *sqlx.Tx, status int) error { - _, err := tx.NamedExec(QSetStatus, u) +func (u *Upload) TxSetFileStored(tx *sqlx.Tx) error { + u.Stored = true + _, err := tx.NamedExec(QSetStored, u) if err != nil { return err @@ -226,30 +356,8 @@ func (u *Upload) Write() error { } func init() { - _, err := DB.Sql.Exec(DBUploadStatusSchema) + _, err := DB.Sql.Exec(DBUploadSchema) if err != nil { log.Fatal(err) } - - _, err = DB.Sql.Exec(DBUploadSchema) - if err != nil { - log.Fatal(err) - } - - _, err = DB.Sql.Exec(DBUploadView) - if err != nil { - log.Fatal(err) - } - - // Populate status types - query := `INSERT INTO upload_status (type, status) - VALUES($1,$2) ON CONFLICT (type) DO UPDATE - SET status = $3` - for k, v := range UploadStatus { - _, err := DB.Sql.Exec(query, k, v, v) - if err != nil { - log.Panic(err) - } - } - } diff --git a/utils/gin_errors.go b/utils/gin_errors.go index d9f9f80..c0e9972 100644 --- a/utils/gin_errors.go +++ b/utils/gin_errors.go @@ -15,5 +15,5 @@ func JSONErr(c *gin.Context, status int, msg string) { func JSONErrPriv(c *gin.Context, status int, err error) { log.Println(err) - JSONErr(c, status, "") + JSONErr(c, status, "server error") } diff --git a/web/src/Pay.vue b/web/src/Pay.vue new file mode 100644 index 0000000..fb3921e --- /dev/null +++ b/web/src/Pay.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/web/src/Timer.js b/web/src/Timer.js new file mode 100644 index 0000000..b053a86 --- /dev/null +++ b/web/src/Timer.js @@ -0,0 +1,63 @@ +export default class Timer { + // Recevies a date in the future + constructor(expiresAt){ + let self = this; + this.secsLeft = 0; + this.minsLeft = 0; + this.hoursLeft= 0; + this.done = false; + + // Convert epoch timestamp to date + if (typeof(expiresAt) === 'number'){ + this.deadline = new Date(expiresAt); + } else if (expiresAt instanceof Date){ + this.deadline = expiresAt + } else { + throw("need instance of Date or unix timestamp") + } + + + // Timer done + if (this.deadline - new Date() <= 0) { + this.done = true; + } else { + this.secsLeft = new Date(this.deadline - new Date()).getSeconds(); + this.minsLeft = new Date(this.deadline - new Date()).getMinutes(); + this.hoursLeft = new Date(this.deadline - new Date()).getHours(); + + // Run ticker + this.interval = setInterval(function(){ + self.tick() + }, 1000) + } + + } + + + get secs () { + return this.secsLeft; + } + + get mins(){ + return this.minsLeft; + } + + get hours(){ + return this.hoursLeft; + } + + tick() { + if (this.deadline - new Date() <= 0) { + this.done; + clearInterval(this.interval); + return + } + this.secsLeft = new Date(this.deadline - new Date()).getSeconds(); + this.minsLeft = new Date(this.deadline - new Date()).getMinutes(); + this.hoursLeft = new Date(this.deadline - new Date()).getHours(); + } + + + + +} diff --git a/web/src/Timer.vue b/web/src/Timer.vue new file mode 100644 index 0000000..a8080b8 --- /dev/null +++ b/web/src/Timer.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/web/src/Upload.vue b/web/src/Upload.vue new file mode 100644 index 0000000..16acacb --- /dev/null +++ b/web/src/Upload.vue @@ -0,0 +1,60 @@ + + +