show download link

master
Chakib Benziane 5 years ago
parent be1f3ae1b7
commit 0d4a3d00e6

@ -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
}

@ -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

@ -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)

@ -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"`

@ -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,
})
}

@ -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)

@ -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
}

@ -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

@ -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"
}
}

@ -2,13 +2,16 @@
<div id="app">
<upload></upload>
<pay :uploadId="uploadId" :status="status" :invoice="invoice"></pay>
<p v-if="uploadId">upload id: <span class="last-upload">{{ uploadId }}</span></p>
<download-link v-if="uploadId && paid" :id="uploadId"></download-link>
</div>
</template>
<script charset="utf-8">
import Upload from './Upload.vue';
import Pay from './Pay.vue';
import DownloadLink from './DownloadLink.vue';
import GetWorker from './workerInterface.js';
import { lastSession } from './api.js';
@ -65,9 +68,15 @@ export default {
})
},
computed: {
paid() {
return this.invoice.status == 'paid';
},
},
components: {
Upload,
Pay,
DownloadLink,
}
}
</script>
@ -88,4 +97,8 @@ export default {
.last-upload {
background: cyan;
}
.dl-link{
color: #d33b00
}
</style>

@ -0,0 +1,25 @@
<template>
<div class="download">
<p>upload id: <span class="last-upload">{{ id }}</span></p>
<p>download: <a :href="link" class="dl-link">{{link}}</a></p>
</div>
</template>
<script>
const baseDlPath = "/d/"
export default {
props: ['id'],
mounted(){
},
computed:{
link(){
let linkTarget = baseDlPath + this.id
return new URL(linkTarget, window.location.toString())
}
}
}
</script>

@ -1,12 +1,11 @@
<template>
<div id="payment">
<img :src="payreqURI" />
<img v-if="unpaid" :src="payreqURI" />
<timer v-if="showTimer" :expires="expires" :expired="expired"></timer>
<p v-if="invoice.status == 'paid'">Paid on {{paidAt}}</p>
<p class='paid' v-if="paid">PAID</p>
<p class='unpaid' v-if="unpaid">UNPAID</p>
<p class='expired' v-if="expired">UNPAID</p>
<p class="paid" v-if="paid">Paid on {{paidAt}}</p>
<p class="unpaid" v-if="unpaid">UNPAID</p>
<p class="expired" v-if="expired">EXPIRED</p>
<canvas id="canvas" hidden>
</div>
@ -51,7 +50,6 @@ export default {
computed: {
paidAt: function(){
return new Date(this.invoice.paid_at).toGMTString();
},
paid: function() {
@ -61,23 +59,26 @@ export default {
unpaid: function(){
return this.invoice.status == 'unpaid';
},
showTimer: function(){
return !this.paid;
return this.invoice.status != 'paid';
}
},
watch: {
invoice: function(val) {
if (val){
console.log("new invoice")
this.expires = 0;
this.expires = val.expires_at;
if (new Date(val.expires_at*1000) - new Date() <= 0){
if ((new Date(val.expires_at*1000) - new Date() <= 0 ) &&
!this.paid) {
this.expired = true;
} else {
this.expired = false;
}
this.makeLnQR(val.payreq)
}
}
},

@ -32,15 +32,19 @@ export default {
}
}
},
watch: {
expires: function(val){
let self = this;
this.timer = new Timer(new Date(val*1000));
expires:{
immediate: true,
handler(val, oldVal){
let self = this;
this.timer = new Timer(new Date(val*1000));
}
}
}
}
</script>
<style>

@ -7,7 +7,7 @@ const endPoints = {
upload: '/api/upload',
session: '/api/session',
checkstatus: '/api/upload/check'
checkstatus: '/api/upload/poll'
}
@ -30,7 +30,7 @@ export async function lastSession(){
return res.json()
}
export async function checkUploadStatus(uploadId){
export async function pollUploadStatus(uploadId){
console.log('checking upload status')
@ -95,7 +95,7 @@ class Upload {
}
async checkstatus(){
return checkUploadStatus(this.uploadId)
return pollUploadStatus(this.uploadId)
}
async send(){
@ -127,5 +127,5 @@ class Upload {
export default {
endPoints: endPoints,
Upload: Upload,
checkUploadStatus: checkUploadStatus,
pollUploadStatus: pollUploadStatus,
}

@ -113,7 +113,7 @@ self.onmessage = e => {
case 'watch-payment':
console.log('worker watching payment ' + e.data.uploadId);
Api.checkUploadStatus(e.data.uploadId)
Api.pollUploadStatus(e.data.uploadId)
.then((data)=>{
self.postMessage(Object.assign({msg: 'payment-received'}, data))

Loading…
Cancel
Save