diff --git a/api/handlers.go b/api/handlers.go index 1b6f1aa..9e65b8c 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -73,6 +73,7 @@ func invoiceCbHandler(c *gin.Context) { // get upload id related to invoice var uploadId string invoiceUploadKey := fmt.Sprintf("invoice_%s_upload", invoice.Id) + err := db.DB.Redis.Do(radix.FlatCmd(&uploadId, "GET", invoiceUploadKey)) if err != nil { panic(err) @@ -82,41 +83,19 @@ func invoiceCbHandler(c *gin.Context) { return } - invoice.UploadId = uploadId - // Invoice paid !!!! // Set upload status to paid - err = storage.SetUploadStatus(invoice.UploadId, storage.UpPaid) + err = storage.SetUploadStatus(uploadId, storage.UpPaid) if err != nil { panic(err) } // // Update Upload Invoice - err = storage.SetUploadInvoice(invoice.UploadId, &invoice) + err = storage.SetUploadInvoice(uploadId, &invoice) if err != nil { panic(err) } - // 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, - //} - - //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) - - //err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH", - //key, msgJson)) - return } diff --git a/api/routes.go b/api/routes.go index ee5eaf2..4536873 100644 --- a/api/routes.go +++ b/api/routes.go @@ -26,7 +26,9 @@ func (api *API) Run() { { uploadRoute.POST("", UploadCtrl.New) uploadRoute.PUT(":id", UploadCtrl.Upload) + //TODO: make a poll version + this one uploadRoute.GET("/check/:id", UploadCtrl.CheckStatus) + uploadRoute.GET("/poll/:id", UploadCtrl.PollStatus) } // Websocket server diff --git a/ln/api.go b/ln/api.go index 6315ead..19f75a6 100644 --- a/ln/api.go +++ b/ln/api.go @@ -34,17 +34,32 @@ func Info() (gin.H, error) { } // Shoudl be called in goroutine -func PollPaidInvoice(in *Invoice, invoiceChan chan<- *Invoice, errorChan chan<- error) { +func CheckInvoice(id string) (*Invoice, error) { + invoice := Invoice{} + reqUri := fmt.Sprintf("%s/%s", getUrl(InvoiceEndpoint), id) + log.Printf("checking invoice %s", reqUri) + + resp, err := http.Get(reqUri) + if err != nil { + return nil, err + } + + jsonDec := json.NewDecoder(resp.Body) + err = jsonDec.Decode(&invoice) + + return &invoice, err +} + +func PollPaidInvoice(invoiceId string, invoiceChan chan<- *Invoice, errorChan chan<- error) { invoice := Invoice{} - invoice.UploadId = in.UploadId reqParams := url.Values{} // Timeout in seconds reqParams.Set("timeout", strconv.Itoa(60)) reqURI := fmt.Sprintf("%s/%s/wait?%s", - getUrl(PollInvoiceEndpoint), - in.Id, + getUrl(InvoiceEndpoint), + invoiceId, reqParams.Encode()) log.Printf("polling to %s", reqURI) @@ -62,17 +77,18 @@ func PollPaidInvoice(in *Invoice, invoiceChan chan<- *Invoice, errorChan chan<- jsonDec = json.NewDecoder(resp.Body) if resp.StatusCode == http.StatusPaymentRequired { - log.Printf("invoice %s not yet paid", in.Id) + log.Printf("invoice %s not yet paid", invoiceId) continue } if resp.StatusCode == http.StatusGone { - log.Printf("invoice expired ", in.Id) + log.Printf("invoice expired %s", invoiceId) invoice.Expired = true break } if resp.StatusCode == http.StatusOK { + log.Printf("Invoice paid %s", invoice) break } else { log.Println("else !") @@ -90,13 +106,12 @@ func PollPaidInvoice(in *Invoice, invoiceChan chan<- *Invoice, errorChan chan<- } jsonDec.Decode(&invoice) - log.Printf("Invoice paid %s", invoice) invoiceChan <- &invoice - log.Printf("quit polling %s", in.UploadId) + log.Printf("quit polling %s", invoiceId) } -func UploadInvoice(amount float32, curr Currency, uploadId string) (*Invoice, error) { +func NewInvoiceForUpload(amount float32, curr Currency, uploadId string) (*Invoice, error) { webhookUrl := fmt.Sprintf("http://%s:%s/%s", config.ApiHost, config.ApiPort, config.ChargeCallbackEndpoint) @@ -130,7 +145,6 @@ func UploadInvoice(amount float32, curr Currency, uploadId string) (*Invoice, er } invoice := Invoice{} - invoice.UploadId = uploadId jsonDec := json.NewDecoder(resp.Body) jsonDec.Decode(&invoice) diff --git a/ln/invoice.go b/ln/invoice.go index ac92f1b..8245f3b 100644 --- a/ln/invoice.go +++ b/ln/invoice.go @@ -18,9 +18,8 @@ const ( ) const ( - InfoEndpoint = "info" - InvoiceEndpoint = "invoice" - PollInvoiceEndpoint = "invoice" + InfoEndpoint = "info" + InvoiceEndpoint = "invoice" ) var ( @@ -44,7 +43,14 @@ var ( type timestamp time.Time func (t *timestamp) UnmarshalJSON(in []byte) error { + + if string(in) == "null" { + *t = timestamp(time.Unix(0, 0)) + return nil + } + val, err := strconv.Atoi(string(in)) + if err != nil { return fmt.Errorf("cannot unmarshal timestamp int") } @@ -66,7 +72,6 @@ func (t timestamp) String() string { type Invoice struct { Id string `json:"id"` - UploadId string `json:"upload_id"` Description string `json:"description"` Msatoshi string `json:"msatoshi"` Payreq string `json:"payreq"` diff --git a/storage/upload_ctrl.go b/storage/upload_ctrl.go index 4027f93..da3c79c 100644 --- a/storage/upload_ctrl.go +++ b/storage/upload_ctrl.go @@ -88,16 +88,14 @@ func (ctrl UploadCtrl) New(c *gin.Context) { if true { // New invoice for 10$ - invoice, err := ln.UploadInvoice(100, ln.CurSat, uploadId) + invoice, err := ln.NewInvoiceForUpload(100, ln.CurSat, uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } - invoice.UploadId = uploadId - // Update Upload Invoice - err = SetUploadInvoice(invoice.UploadId, invoice) + err = SetUploadInvoice(uploadId, invoice) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return @@ -144,6 +142,56 @@ func (ctrl UploadCtrl) CheckStatus(c *gin.Context) { return } + if !exists { + utils.JSONErr(c, http.StatusNotFound, "upload id not found") + return + } + + // Get upload status + uploadStatus, err := GetUploadStatus(uploadId) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + if uploadStatus.IsNew() { + c.JSON(http.StatusOK, gin.H{ + "status": uploadStatus, + }) + return + } + + // Get invoice id for upload + invoiceId, err := GetUploadInvoiceId(uploadId) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + // Get invoice + invoice, err := ln.CheckInvoice(invoiceId) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + c.JSON(InvoiceHttpCode(invoice), gin.H{ + "upload_id": uploadId, + "invoice": invoice, + "status": uploadStatus, + }) + +} + +func (ctrl UploadCtrl) PollStatus(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 @@ -162,29 +210,30 @@ func (ctrl UploadCtrl) CheckStatus(c *gin.Context) { return } - if uploadStatus.WaitPay() { + // we are not in UpNew state so we should have an invoice + invoice, err := GetUploadInvoice(uploadId) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } - // 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 - } + if uploadStatus.WaitPay() { 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) + go ln.PollPaidInvoice(invoice.Id, 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) + err := SetUploadStatus(uploadId, UpPayExpired) if err != nil { log.Printf("Redis error: %s", err) } @@ -202,13 +251,13 @@ func (ctrl UploadCtrl) CheckStatus(c *gin.Context) { //////////////// uploadStatus |= UpPaid - err := SetUploadStatus(invoice.UploadId, uploadStatus) + err := SetUploadStatus(uploadId, uploadStatus) if err != nil { log.Printf("Redis error: %s", err) } // Update Upload Invoice - err = SetUploadInvoice(invoice.UploadId, invoice) + err = SetUploadInvoice(uploadId, invoice) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return @@ -224,12 +273,12 @@ func (ctrl UploadCtrl) CheckStatus(c *gin.Context) { case err := <-errorChan: utils.JSONErrPriv(c, http.StatusInternalServerError, err) - } } else { c.JSON(http.StatusOK, gin.H{ - "status": uploadStatus, + "status": uploadStatus, + "invoice": invoice, }) } diff --git a/storage/upload_model.go b/storage/upload_model.go index a65d082..faa66fb 100644 --- a/storage/upload_model.go +++ b/storage/upload_model.go @@ -65,7 +65,7 @@ func (st UpStatus) MarshalJSON() ([]byte, error) { } func (st *UpStatus) UnmarshalBinary(b []byte) error { - fmt.Printf("%#v\n", b) + //fmt.Printf("%#v\n", b) // first 4 bits are reseved // Single byte will be for 0 value @@ -76,23 +76,8 @@ func (st *UpStatus) UnmarshalBinary(b []byte) error { 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.) + //fmt.Printf("%32b\n", view) + //log.Printf("%d\n", view) *st = UpStatus(view) @@ -135,6 +120,10 @@ func (st UpStatus) PrintPayStatus() string { return UploadStatus[WaitPay] } +func (st UpStatus) IsNew() bool { + return (st & UpNew) != 0 +} + func (st UpStatus) WaitPay() bool { return (!st.Paid()) && (!st.Expired()) } @@ -230,8 +219,14 @@ func GetUploadInvoice(uploadId string) (*ln.Invoice, error) { return &invoice, nil } +func GetUploadInvoiceId(uploadId string) (string, error) { + invoice, err := GetUploadInvoice(uploadId) + + return invoice.Id, err +} + func SetUploadStatus(id string, status UpStatus) error { - log.Printf("setting upload status for %s", id) + //log.Printf("setting upload status for %s", id) key := fmt.Sprintf("upload_status_%s", id) @@ -239,11 +234,11 @@ func SetUploadStatus(id string, status UpStatus) error { return DB.Redis.Do(radix.FlatCmd(nil, "SETBIT", key, 31, 0)) } - log.Println("setting upload status for bit positions ", status.GetFlagPositions()) + //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) + //log.Printf("setting bit at position %d", offset) err := DB.Redis.Do(radix.FlatCmd(nil, "SETBIT", key, offset, 1)) if err != nil { @@ -257,7 +252,7 @@ func SetUploadStatus(id string, status UpStatus) error { } func GetUploadStatus(id string) (status UpStatus, err error) { - log.Println("Getting upload status") + //log.Println("Getting upload status") key := fmt.Sprintf("upload_status_%s", id) diff --git a/storage/utils.go b/storage/utils.go new file mode 100644 index 0000000..0debc71 --- /dev/null +++ b/storage/utils.go @@ -0,0 +1,20 @@ +package storage + +import ( + "net/http" + + "git.sp4ke.com/sp4ke/bit4sat/ln" +) + +func InvoiceHttpCode(invoice *ln.Invoice) int { + if invoice.Expired { + return http.StatusGone + } + + if invoice.Status == "unpaid" { + return http.StatusPaymentRequired + } + + return http.StatusOK + +} diff --git a/web/Caddyfile b/web/Caddyfile index b046487..a567f6b 100644 --- a/web/Caddyfile +++ b/web/Caddyfile @@ -4,11 +4,17 @@ localhost root dist ext .html + header / { Content-Security-Policy "default-src * 'unsafe-eval' 'unsafe-inline' data: ;" } -proxy /api localhost:8880 + +proxy /d localhost:8880 + +proxy /api localhost:8880 { + transparent +} #log /api stdout log / stdout diff --git a/web/package.json b/web/package.json index 3a8e0dd..f65ccd1 100644 --- a/web/package.json +++ b/web/package.json @@ -27,6 +27,7 @@ }, "scripts": { "start": "parcel index.html", - "watch": "parcel watch index.html" + "watch": "parcel watch index.html", + "build": "parcel index.html --public-url" } } diff --git a/web/src/App.vue b/web/src/App.vue index 34dba91..4c8a35c 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -2,13 +2,16 @@
-

upload id: {{ uploadId }}

+ +
@@ -88,4 +97,8 @@ export default { .last-upload { background: cyan; } + +.dl-link{ + color: #d33b00 +} diff --git a/web/src/DownloadLink.vue b/web/src/DownloadLink.vue new file mode 100644 index 0000000..c7a3ae6 --- /dev/null +++ b/web/src/DownloadLink.vue @@ -0,0 +1,25 @@ + + + diff --git a/web/src/Pay.vue b/web/src/Pay.vue index feda4e4..1e95bd2 100644 --- a/web/src/Pay.vue +++ b/web/src/Pay.vue @@ -1,12 +1,11 @@