diff --git a/api/download.go b/api/download.go
index b02f555..5af3e81 100644
--- a/api/download.go
+++ b/api/download.go
@@ -1,9 +1,11 @@
package api
import (
+ "encoding/gob"
"fmt"
"log"
"net/http"
+ "time"
"git.sp4ke.com/sp4ke/bit4sat/ln"
"git.sp4ke.com/sp4ke/bit4sat/storage"
@@ -23,7 +25,7 @@ func DownloadHandler(c *gin.Context) {
return
}
- // Get the upload session
+ // Get the download session
session, err := SessionStore.Get(c.Request, DlSessionKey)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
@@ -31,7 +33,6 @@ func DownloadHandler(c *gin.Context) {
}
dlSess, exists := session.Values["session-id"]
- log.Printf("%#v", dlSess)
// Test if we are alread in a download session
if !exists {
@@ -47,21 +48,28 @@ func DownloadHandler(c *gin.Context) {
log.Printf("going to generate invoice for %s", upId)
- session.AddFlash(sessId, "session-id")
+ //TODO: use fee asked by uploader
+ //
+ // Get upload data asked by uploader
+ up, err := storage.GetUploadById(upId)
+ if err != nil {
+ utils.JSONErrPriv(c, http.StatusInternalServerError, err)
+ return
+ }
- session.Save(c.Request, c.Writer)
+ // Get files metadata for upload
+ var upFilesMeta []storage.FileUpload
+ upFilesMeta, err = storage.GetUploadFilesMeta(upId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
- //TODO: use fee asked by uploader
- //
- // Get upload fee asked by uploader
- //
+ session.Values["files"] = upFilesMeta
+
invoiceOpts := ln.InvoiceOpts{
- Amount: 100,
- Curr: ln.CurSat,
+ Amount: up.AskAmount,
+ Curr: ln.CurrencyID[up.AskCurrency],
Memo: fmt.Sprintf("bit4sat download: %s", sessId),
}
@@ -71,23 +79,73 @@ func DownloadHandler(c *gin.Context) {
return
}
- // This is a returning download session to pay the invoice
- //
+ session.Values["session-id"] = sessId
+ session.Values["invoice"] = invoice
+
+ invoiceExpires := time.Time(invoice.ExpiresAt).Sub(time.Now()).Seconds()
+
+ //TODO: this session can be reused during this hour
+ session.Options.MaxAge = int(invoiceExpires)
+
+ //log.Printf("session: %#v", session.Values)
+
+ err = session.Save(c.Request, c.Writer)
+ if err != nil {
+ utils.JSONErrPriv(c, http.StatusInternalServerError, err)
+ return
+ }
+
c.JSON(http.StatusPaymentRequired, gin.H{
- "invoice_rhash": invoice.RHash,
+ "invoice": invoice,
+ "files": upFilesMeta,
})
+ // This is a returning download session to pay the invoice
} else {
log.Printf("continue download session id: %s", dlSess)
- session.Flashes("session-id")
- err = session.Save(c.Request, c.Writer)
- if err != nil {
- utils.JSONErrPriv(c, http.StatusInternalServerError, err)
+ var ok bool
+
+ invVal := session.Values["invoice"]
+ var invoice = &ln.Invoice{}
+ if invoice, ok = invVal.(*ln.Invoice); !ok {
+ log.Printf("Could not find invoice in session %s", dlSess)
+ session.Options.MaxAge = -1
+
+ err = session.Save(c.Request, c.Writer)
+ if err != nil {
+ utils.JSONErrPriv(c, http.StatusInternalServerError, err)
+ return
+ }
+
+ utils.JSONErr(c, http.StatusExpectationFailed,
+ "invoice not found, start a new download session session")
return
}
- c.Status(http.StatusOK)
+ //TODO: Check if invoice paid ??
+
+ filesVal := session.Values["files"]
+ var filesMeta = &[]storage.FileUpload{}
+ if filesMeta, ok = filesVal.(*[]storage.FileUpload); !ok {
+ // The cookie broke ???
+ session.Options.MaxAge = -1
+
+ err = session.Save(c.Request, c.Writer)
+ if err != nil {
+ utils.JSONErrPriv(c, http.StatusInternalServerError, err)
+ return
+ }
+
+ utils.JSONErr(c, http.StatusExpectationFailed,
+ "start a new download session session")
+ return
+ }
+
+ c.JSON(http.StatusPaymentRequired, gin.H{
+ "invoice": invoice,
+ "files": filesMeta,
+ })
}
return
@@ -110,3 +168,8 @@ func TestDownHandler(c *gin.Context) {
return
}
+
+func init() {
+ gob.Register(&ln.Invoice{})
+ gob.Register(&[]storage.FileUpload{})
+}
diff --git a/ln/invoice.go b/ln/invoice.go
index 2043aad..6412e57 100644
--- a/ln/invoice.go
+++ b/ln/invoice.go
@@ -112,6 +112,7 @@ type Invoice struct {
RHash string `json:"rhash"`
PreImage string `json:"-"` // used as secret admin token
Status string `json:"status"`
+ Settled bool `json:"settled"`
PaidAt timestamp `json:"paid_at"`
CreatedAt timestamp `json:"created_at"`
ExpiresAt timestamp `json:"expires_at"`
@@ -131,7 +132,6 @@ func (i *Invoice) UnmarshalBinary(b []byte) error {
// Create new Invoice from lnrpc.Invoice
func InvoiceFromLndIn(lndInvoice *lnrpc.Invoice) *Invoice {
- log.Println(hex.EncodeToString(lndInvoice.RPreimage))
invoice := Invoice{
AddIndex: lndInvoice.AddIndex,
Description: lndInvoice.GetMemo(),
@@ -147,6 +147,7 @@ func InvoiceFromLndIn(lndInvoice *lnrpc.Invoice) *Invoice {
// Calculate status
if lndInvoice.Settled {
+ invoice.Settled = true
invoice.Status = InvoiceStatus[Paid]
}
@@ -166,6 +167,7 @@ func UpdateInvoiceFromLnd(storedIn *Invoice, newIn *lnrpc.Invoice) *Invoice {
// Calculate status
if newIn.Settled {
storedIn.Status = InvoiceStatus[Paid]
+ storedIn.Settled = true
}
// Handle expired status
diff --git a/storage/upload_model.go b/storage/upload_model.go
index d96d33e..878d27e 100644
--- a/storage/upload_model.go
+++ b/storage/upload_model.go
@@ -8,6 +8,7 @@ import (
"fmt"
"log"
"math/bits"
+ "time"
"git.sp4ke.com/sp4ke/bit4sat/db"
"git.sp4ke.com/sp4ke/bit4sat/ln"
@@ -44,7 +45,7 @@ const (
CREATE TABLE IF NOT EXISTS uploads (
upload_id text PRIMARY KEY,
download_id text DEFAULT '',
- created timestamp,
+ created timestamp DEFAULT now(),
ask_fee boolean NOT NULL DEFAULT 'false',
ask_currency varchar(3) DEFAULT '',
ask_amount real DEFAULT 0,
@@ -223,15 +224,32 @@ type FileUpload struct {
}
type Upload struct {
- UploadId string `db:"upload_id"`
- DownloadID string `db:"download_id"`
- AskFee bool `db:"ask_fee"`
- AskCurrency string `db:"ask_currency"`
- AskAmount float64 `db:"ask_amount"`
- InvoiceRhash string `db:"invoice_rhash"` // used as id
- Settled bool `db:"settled"`
- UploadStatus uint32 `db:"status"` // upload flag status
- AdminToekn string `db:"admin_token"`
+ UploadId string `db:"upload_id"`
+ DownloadID string `db:"download_id"`
+ AskFee bool `db:"ask_fee"`
+ Created time.Time `db:"created"`
+ AskCurrency string `db:"ask_currency"`
+ AskAmount float64 `db:"ask_amount"`
+ InvoiceRhash string `db:"invoice_rhash"` // used as id
+ Settled bool `db:"settled"`
+ UploadStatus uint32 `db:"status"` // upload flag status
+ AdminToekn string `db:"admin_token"`
+}
+
+func (u Upload) MarshalBinary() ([]byte, error) {
+ return json.Marshal(u)
+}
+
+func (u *Upload) UnmarshalBinary(b []byte) error {
+ return json.Unmarshal(b, &u)
+}
+
+func (u FileUpload) MarshalBinary() ([]byte, error) {
+ return json.Marshal(u)
+}
+
+func (u *FileUpload) UnmarshalBinary(b []byte) error {
+ return json.Unmarshal(b, &u)
}
// TODO: sync from redis to db
@@ -262,7 +280,7 @@ func GetDownloadId(uploadId string) (string, error) {
return "", err
}
- if len(downloadId) == 0 {
+ if downloadId == "" {
return "", fmt.Errorf("download id missing in %s", uploadId)
}
@@ -324,6 +342,68 @@ func GetUploadInvoice(uploadId string) (*ln.Invoice, error) {
return &invoice, nil
}
+func GetUploadFilesMeta(uploadId string) ([]FileUpload, error) {
+ key := fmt.Sprintf("upload_files_%s", uploadId)
+ var files []FileUpload
+ var exists bool
+
+ // try redis
+ err := DB.Redis.Do(radix.FlatCmd(&exists, "EXISTS", key))
+ if err != nil {
+ return nil, err
+ }
+
+ // Sql
+ if !exists {
+ log.Printf("upload %s files not in cache", uploadId)
+ query := "SELECT * FROM file_uploads WHERE upload_id = $1"
+ err := DB.Sql.Select(&files, query, uploadId)
+ if err != nil {
+ return nil, err
+ }
+
+ // Store back on redis cache
+ err = DB.Redis.Do(radix.FlatCmd(nil, "SADD", key, files))
+ } else {
+ err = DB.Redis.Do(radix.FlatCmd(&files, "SMEMBERS", key))
+ }
+
+ return files, err
+
+}
+
+func GetUploadById(id string) (*Upload, error) {
+ key := fmt.Sprintf("upload_%s", id)
+ up := Upload{}
+ up.UploadId = id
+
+ // Try from redis first
+ var exists bool
+ err := DB.Redis.Do(radix.FlatCmd(&exists, "EXISTS", key))
+ if err != nil {
+ return nil, err
+ }
+
+ // if does not exists, get it from sql then set it on redis as cache
+ if !exists {
+ log.Printf("upload %s not found on redis, caching from sql", id)
+ query := `SELECT * FROM uploads WHERE upload_id = $1`
+ err := DB.Sql.Get(&up, query, id)
+ if err != nil {
+ return nil, err
+ }
+
+ // Sotre it back on redis
+ err = DB.Redis.Do(radix.FlatCmd(nil, "SET", key, up))
+
+ // Get it from redis
+ } else {
+ err = DB.Redis.Do(radix.FlatCmd(&up, "GET", key))
+ }
+
+ return &up, err
+}
+
func GetUploadIdInvoiceId(uploadId string) (string, error) {
invoice, err := GetUploadInvoice(uploadId)
diff --git a/web/Caddyfile b/web/Caddyfile
index 5526047..82c61cb 100644
--- a/web/Caddyfile
+++ b/web/Caddyfile
@@ -15,6 +15,11 @@ proxy /api localhost:8880 {
transparent
}
+## Download rewrite
+rewrite /d {
+ to /
+}
+
## test
proxy /t localhost:8880 {
transparent
diff --git a/web/src/App.vue b/web/src/App.vue
index 5632f42..535d0ea 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -12,6 +12,9 @@
diff --git a/web/src/DownloadLink.vue b/web/src/DownloadLink.vue
deleted file mode 100644
index c7a3ae6..0000000
--- a/web/src/DownloadLink.vue
+++ /dev/null
@@ -1,25 +0,0 @@
-
- upload id: {{ id }} download: {{link}}