From 0f5075acab48f095d015bc5065ff6ebd52ffd3ee Mon Sep 17 00:00:00 2001 From: Chakib Benziane Date: Fri, 5 Apr 2019 18:18:44 +0200 Subject: [PATCH] db refactoring + wip dl id --- .dockerignore | 4 +- Makefile | 5 + config/vars.go | 5 + docker-compose.yml | 5 + go.mod | 2 +- go.sum | 4 +- main.go | 1 - storage/shortid.go | 52 +++++++--- storage/upload_ctrl.go | 88 ++++++++++++----- storage/upload_form.go | 9 +- storage/upload_model.go | 205 ++++++++++++++++++++++++++++++++++------ web/Caddyfile-prod | 28 ++++++ web/package.json | 2 +- web/src/Pay.vue | 5 +- web/src/Upload.vue | 26 +++-- web/src/UploadView.vue | 9 +- web/src/api.js | 4 +- web/src/store.js | 5 + web/src/worker.js | 13 +-- 19 files changed, 375 insertions(+), 97 deletions(-) create mode 100644 web/Caddyfile-prod diff --git a/.dockerignore b/.dockerignore index 6107594..55f1db4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ -web +node_modules +**/node_modules +dist bit4sat file-storage diff --git a/Makefile b/Makefile index 1dbdc7e..bfeb53f 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ TARGET := bit4sat GOINSTALL := GO111MODULE=on go install -v GOBUILD := GO111MODULE=on go build -v +WEBDIST := bit4sat/webdist +WEBDIST_DOCKER := docker/Dockerfile-dist-prod .PHONY: all build install @@ -12,3 +14,6 @@ build: install: $(GOINSTALL) + +webdist: + docker build -t $(WEBDIST) -f $(WEBDIST_DOCKER) . diff --git a/config/vars.go b/config/vars.go index 5ed3402..a95fa49 100644 --- a/config/vars.go +++ b/config/vars.go @@ -8,6 +8,8 @@ import ( const ( ChargeCallbackEndpoint = "chargecallback" + ShortIdMinLength = 8 + LongIdMinLength = 32 ) var ( @@ -24,6 +26,7 @@ var ( LndCertPath, LndAdminMacaroonPath, LockMacaroonIp, + ShortIdSalt, HttpProxy, @@ -62,6 +65,8 @@ func init() { flag.StringVar(&HttpProxy, "http-proxy", "", "http proxy for clients") + flag.StringVar(&ShortIdSalt, "short-id-salt", "bit4sat-23591", "hashid salt") + //log.Printf("locking macaroon to ip %s", LockMacaroonIp) flag.Parse() diff --git a/docker-compose.yml b/docker-compose.yml index 5af3a94..6aca2a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,7 @@ services: - LN_CHARGE_TOKEN=3emU3Fy8VasHCzMaMXHSVJYpQSqH3yXQj8N5cQFBbq3botrudJuR7zQkBBmFSbAmgXs9GD4j4U3J4R2sMfgqPo8q - SESSION_SECRET=Ai7fCy36UE5cb9wcmdAxxRXwYyQDsDMr6rYocA6Eava7pdiB29EusLbb9sTYWS1e - GRPC_SSL_CIPHER_SUITES="HIGH+ECDSA" + - SHORT_ID_SALT=Czp6NtlGpt0ebzG1DuUND1nMftLUR77c # Used in case of ssl problems - HTTP_PROXY=http://tinyproxy:8888 @@ -40,6 +41,10 @@ services: ports: - "8880:8880" + depends_on: + - redis + - postgres + volumes: - $PWD:/src - gocache:/go diff --git a/go.mod b/go.mod index 07e4237..fea71e5 100644 --- a/go.mod +++ b/go.mod @@ -17,8 +17,8 @@ require ( github.com/mediocregopher/radix/v3 v3.2.3 github.com/namsral/flag v1.7.4-pre github.com/segmentio/ksuid v1.0.2 + github.com/speps/go-hashids v2.0.0+incompatible github.com/stretchr/testify v1.3.0 // indirect - github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780 // indirect golang.org/x/net v0.0.0-20190322120337-addf6b3196f6 golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect diff --git a/go.sum b/go.sum index adcd59a..dec2c12 100644 --- a/go.sum +++ b/go.sum @@ -173,12 +173,12 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sK github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/segmentio/ksuid v1.0.2 h1:9yBfKyw4ECGTdALaF09Snw3sLJmYIX6AbPJrAy6MrDc= github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU= +github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw= +github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc= 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/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= -github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs= diff --git a/main.go b/main.go index c9b8fb5..bcaa26e 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,6 @@ import ( func main() { go watchers.WatchInvoice() - //<-make(chan bool) defer db.DB.Sql.Close() diff --git a/storage/shortid.go b/storage/shortid.go index 84618e8..377ca16 100644 --- a/storage/shortid.go +++ b/storage/shortid.go @@ -1,26 +1,54 @@ package storage import ( - "log" + "time" - "github.com/teris-io/shortid" + "git.sp4ke.com/sp4ke/bit4sat/config" + "github.com/speps/go-hashids" ) -const ( - Seed = 8562913 -) +var ShortHD *hashids.HashIDData +var LongHD *hashids.HashIDData -var SID *shortid.Shortid +func GetShortId() (string, error) { + hd, err := hashids.NewWithData(ShortHD) + if err != nil { + return "", err + } + + now := int64(time.Now().UnixNano()) + short, err := hd.EncodeInt64([]int64{now}) + if err != nil { + return "", err + } + + return short, nil -func GetSIDGenerator() *shortid.Shortid { - return SID } -func init() { - var err error - SID, err = shortid.New(1, shortid.DefaultABC, Seed) +func GetLongId() (string, error) { + + hd, err := hashids.NewWithData(LongHD) if err != nil { - log.Fatal(err) + return "", err } + now := int64(time.Now().UnixNano()) + long, err := hd.EncodeInt64([]int64{now}) + if err != nil { + return "", err + } + + return long, nil + +} + +func init() { + ShortHD = hashids.NewData() + ShortHD.Salt = config.ShortIdSalt + ShortHD.MinLength = 8 + + LongHD = hashids.NewData() + LongHD.Salt = config.ShortIdSalt + LongHD.MinLength = 32 } diff --git a/storage/upload_ctrl.go b/storage/upload_ctrl.go index 4cbd3bb..84808ad 100644 --- a/storage/upload_ctrl.go +++ b/storage/upload_ctrl.go @@ -27,13 +27,14 @@ func (ctrl UploadCtrl) New(c *gin.Context) { uploadForm := UploadForm{} if err := c.ShouldBindJSON(&uploadForm); err != nil { + log.Printf("error parsing form: %s", err) utils.JSONErr(c, http.StatusNotAcceptable, - "could not parse form") + "invalid form") return } // Create unique id - uploadId, err := GetSIDGenerator().Generate() + uploadId, err := GetShortId() if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return @@ -45,17 +46,50 @@ func (ctrl UploadCtrl) New(c *gin.Context) { return } + if uploadForm.RequestPayment { + if _, ok := ln.CurrencyID[uploadForm.PaymentCurrency]; !ok { + utils.JSONErr(c, http.StatusInternalServerError, + "currency not handled or recognized") + return + + } + } + + // Create new upload + up := &Upload{} + up.UploadId = uploadId + up.AskFee = uploadForm.RequestPayment + up.AskCurrency = uploadForm.PaymentCurrency + up.AskAmount = uploadForm.RequestPaymentAmount + + // Generate unique download id + // + dlId, err := GetShortId() + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + up.DownloadID = dlId + + err = up.TxWrite(tx) + + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + db.RollbackTx(tx, nil) + return + } + for _, file := range uploadForm.Files { - up := &Upload{} - up.ID = uploadId + fup := &FileUpload{} + fup.UploadId = uploadId - up.FileName, up.FileExt = utils.CleanFileName(file.Name) + fup.FileName, fup.FileExt = utils.CleanFileName(file.Name) - up.FileSize = file.Size - up.FileType = file.Type - up.SHA256 = file.SHA256 + fup.FileSize = file.Size + fup.FileType = file.Type + fup.SHA256 = file.SHA256 - err := up.TxWrite(tx) + err := fup.TxWrite(tx) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) db.RollbackTx(tx, nil) @@ -80,23 +114,17 @@ func (ctrl UploadCtrl) New(c *gin.Context) { session := sessions.Default(c) // Set the uploadId as last upload for session - session.Set(SessLastUploadName, uploadId) + session.AddFlash(SessLastUploadName, uploadId) //session.AddFlash(SessLastUploadName, uploadId) session.Save() // If not free upload generate a an invoice // TODO: check if upload fee is free - if true { - - // New invoice for 10$ - invoice, err := ln.NewInvoiceForUpload(100, ln.CurSat, uploadId) - if err != nil { - utils.JSONErrPriv(c, http.StatusInternalServerError, err) - return - } + if uploadForm.RequestPayment { - // Save the secret admin token - err = SetUploadAdminToken(uploadId, invoice.PreImage) + invoice, err := ln.NewInvoiceForUpload(uploadForm.RequestPaymentAmount, + ln.CurrencyID[uploadForm.PaymentCurrency], + uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return @@ -285,8 +313,21 @@ func (ctrl UploadCtrl) PollStatus(c *gin.Context) { return } - // get the secret admin token - token, err := GetUploadAdminToken(uploadId) + // Generate admin token and save it + adminToken, err := GetLongId() + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + err = SetUploadAdminToken(uploadId, invoice.PreImage) + if err != nil { + utils.JSONErrPriv(c, http.StatusInternalServerError, err) + return + } + + // Get the download id + dlId, err := GetDownloadId(uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return @@ -301,7 +342,8 @@ func (ctrl UploadCtrl) PollStatus(c *gin.Context) { c.JSON(http.StatusAccepted, gin.H{ "status": uploadStatus, "invoice": invoice, - "admin_token": token, + "admin_token": adminToken, + "download_id": dlId, }) return diff --git a/storage/upload_form.go b/storage/upload_form.go index 31ca6a5..e626ff2 100644 --- a/storage/upload_form.go +++ b/storage/upload_form.go @@ -9,16 +9,17 @@ type File struct { Type string `form:"type"` // MIME type } +//TODO: field validation type UploadForm struct { Files []File `form:"files" binding:"required"` // Ask payment - RequestPayment bool `form:"request_payment"` + RequestPayment bool `json:"request_payment"` - RequestPaymentAmount int `form:"request_payment_amount"` + RequestPaymentAmount float64 `json:"request_payment_amount" binding:"min=1"` - PaymentCurrency string `form:"payment_currency"` + PaymentCurrency string `json:"payment_currency"` // Default Date().toJSON() from js - TimeStamp time.Time `form:"timestamp"` + TimeStamp time.Time `json:"timestamp"` } diff --git a/storage/upload_model.go b/storage/upload_model.go index f05ded5..986599d 100644 --- a/storage/upload_model.go +++ b/storage/upload_model.go @@ -21,27 +21,50 @@ var DB = db.DB const ( //TODO: sync upload status from redis // TODO: status is currently handled in cache not here - DBUploadSchema = ` - CREATE TABLE IF NOT EXISTS upload ( - id serial PRIMARY KEY, - upload_id varchar(9) NOT NULL, + // + // stored: if file was stored + DBFileUploadsSchema = ` + CREATE TABLE IF NOT EXISTS file_uploads ( + file_upload_id serial PRIMARY KEY, + upload_id text NOT NULL references uploads(upload_id), sha256 varchar(64) NOT NULL, file_name varchar(255) NOT NULL, file_type varchar(255) DEFAULT '', file_size integer NOT NULL, file_ext varchar(255) DEFAULT '', - ask_fee integer NOT NULL DEFAULT 0, - stored boolean DEFAULT '0', + stored boolean DEFAULT 'false', UNIQUE (upload_id, sha256) ); ` - QNewUpload = `INSERT INTO upload - (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, :stored, :ask_fee)` + // Unique entry per upload + // + // store: all files for upload where stored + DBUploadsSchema = ` + CREATE TABLE IF NOT EXISTS uploads ( + upload_id text PRIMARY KEY, + download_id text DEFAULT '', + created timestamp, + ask_fee boolean NOT NULL DEFAULT 'false', + ask_currency varchar(3) DEFAULT '', + ask_amount real DEFAULT 0, + status integer NOT NULL DEFAULT 0, + settled boolean DEFAULT 'false', + admin_token text DEFAULT '', + invoice_rhash varchar(64) references invoices(rhash) + ); + ` + + DBInvoicesSchema = ` + CREATE TABLE IF NOT EXISTS invoices ( + rhash varchar(64) PRIMARY KEY, + payload text DEFAULT '' + ); + ` - QSetStored = `UPDATE upload SET stored = :stored WHERE upload_id = :upload_id ` + QSetFileStored = `UPDATE file_uploads + SET stored = :stored + WHERE upload_id = :upload_id AND sha256 = :sha256` QGetByHashID = `SELECT upload_id, sha256, @@ -49,9 +72,8 @@ const ( file_type, file_size, file_ext, - ask_fee, stored -FROM upload WHERE sha256 = $1 AND upload_id = $2` + FROM file_uploads WHERE sha256 = $1 AND upload_id = $2` ) type UpStatus uint32 @@ -189,16 +211,27 @@ var ( ErrAlreadyExists = errors.New("already exists") ) -type Upload struct { - ID string `db:"upload_id"` - Free bool `db:"-"` // is this a free upload +type FileUpload struct { + ID string `db:"file_upload_id"` + UploadId string `db:"upload_id"` SHA256 string `db:"sha256"` FileName string `db:"file_name"` FileType string `db:"file_type"` FileSize int64 `db:"file_size"` FileExt string `db:"file_ext"` Stored bool `db:"stored"` - AskFee int `db:"ask_fee"` // fee asked for download +} + +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"` } // TODO: sync from redis to db @@ -206,6 +239,41 @@ type Upload struct { //} +func GetDownloadId(uploadId string) (string, error) { + key := fmt.Sprintf("download_for_upload_%s", uploadId) + + // Try redis + var downloadId string + + var exists bool + + // Check if exists + err := DB.Redis.Do(radix.FlatCmd(&exists, "EXISTS", key)) + if err != nil { + return "", err + } + + //if !exists get from sql and store in redis + if !exists { + log.Printf("download id not found for %s, trying form db") + query := `SELECT download_id from uploads WHERE upload_id = $1` + err = DB.Sql.Get(&downloadId, query) + if err != nil { + return "", err + } + + if len(downloadId) == 0 { + return "", fmt.Errorf("download id missing in %s", uploadId) + } + + // Store it back on redis cache + err = DB.Redis.Do(radix.FlatCmd(nil, "SET", key, downloadId)) + } + + return downloadId, err + +} + func GetUploadInvoice(uploadId string) (*ln.Invoice, error) { var invoice ln.Invoice @@ -247,7 +315,12 @@ func SetUploadStatus(id string, status UpStatus) error { } } - log.Println("done set bit") + // Store on sql db + query := `UPDATE uploads SET status = $1 WHERE upload_id = $2` + _, err := DB.Sql.Exec(query, uint32(status), id) + if err != nil { + return err + } return nil } @@ -264,7 +337,14 @@ func GetUploadStatus(id string) (status UpStatus, err error) { func SetUploadAdminToken(uploadId, token string) error { key := fmt.Sprintf("upload_admin_%s", uploadId) - return DB.Redis.Do(radix.FlatCmd(nil, "SET", key, token)) + err := DB.Redis.Do(radix.FlatCmd(nil, "SET", key, token)) + if err != nil { + return err + } + + query := `UPDATE uploads SET admin_token = $1 WHERE upload_id = $2` + _, err = DB.Sql.Exec(query, token, uploadId) + return err } func GetUploadAdminToken(uploadId string) (string, error) { @@ -272,7 +352,6 @@ func GetUploadAdminToken(uploadId string) (string, error) { key := fmt.Sprintf("upload_admin_%s", uploadId) err := DB.Redis.Do(radix.FlatCmd(&token, "GET", key)) return token, err - } func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error { @@ -290,7 +369,35 @@ func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error { // Set inverse relation invoiceUploadKey := fmt.Sprintf("invoice_%s_upload", invoice.RHash) - return DB.Redis.Do(radix.FlatCmd(nil, "SET", invoiceUploadKey, uploadId)) + err = DB.Redis.Do(radix.FlatCmd(nil, "SET", invoiceUploadKey, uploadId)) + if err != nil { + return err + } + + // SQL insert + tx, err := DB.Sql.Beginx() + if err != nil { + return err + } + + // Add invoice to invoices table + query := `INSERT INTO invoices(rhash, payload) + VALUES($1,$2) + ON CONFLICT DO NOTHING + ` + _, err = tx.Exec(query, invoice.RHash, invoiceJson) + if err != nil { + return err + } + + // Set invoice to upload + query = `UPDATE uploads SET invoice_rhash = $1 WHERE upload_id = $2` + _, err = tx.Exec(query, invoice.RHash, uploadId) + if err != nil { + return err + } + + return tx.Commit() } func GetUploadIdForInvoice(invoiceId string) (string, error) { @@ -309,8 +416,8 @@ func IdExists(id string) (exists bool, err error) { } // Get a file by upload id and hash -func GetByHashID(sha256 string, id string) (*Upload, error) { - var up Upload +func GetByHashID(sha256 string, id string) (*FileUpload, error) { + var up FileUpload err := DB.Sql.Get(&up, QGetByHashID, sha256, id) @@ -325,9 +432,32 @@ func GetByHashID(sha256 string, id string) (*Upload, error) { return &up, nil } -func (u *Upload) TxSetFileStored(tx *sqlx.Tx) error { +func (u *FileUpload) TxSetFileStored(tx *sqlx.Tx) error { u.Stored = true - _, err := tx.NamedExec(QSetStored, u) + _, err := tx.NamedExec(QSetFileStored, u) + + if err != nil { + return err + } + + return nil +} + +func (u *FileUpload) TxWrite(tx *sqlx.Tx) error { + query := `INSERT INTO file_uploads + (upload_id, sha256, file_name, file_type, file_size, file_ext) + VALUES + (:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext) + ` + + _, err := tx.NamedExec(query, u) + + if pqError, ok := err.(*pq.Error); ok { + // unique constraint + if pqError.Code == "23505" { + return ErrAlreadyExists + } + } if err != nil { return err @@ -337,8 +467,12 @@ func (u *Upload) TxSetFileStored(tx *sqlx.Tx) error { } func (u *Upload) TxWrite(tx *sqlx.Tx) error { + query := `INSERT INTO uploads + (upload_id, download_id, ask_fee, ask_currency, settled) + VALUES + (:upload_id, :download_id, :ask_fee, :ask_currency, :settled)` - _, err := tx.NamedExec(QNewUpload, u) + _, err := tx.NamedExec(query, u) if pqError, ok := err.(*pq.Error); ok { // unique constraint @@ -355,7 +489,11 @@ func (u *Upload) TxWrite(tx *sqlx.Tx) error { } func (u *Upload) Write() error { - _, err := DB.Sql.NamedExec(QNewUpload, u) + query := `INSERT INTO uploads + (upload_id, download_id, ask_fee, ask_currency, settled) + VALUES + (:upload_id, :download_id, :ask_fee, :ask_currency, :settled)` + _, err := DB.Sql.NamedExec(query, u) if pqError, ok := err.(*pq.Error); ok { // unique constraint @@ -372,8 +510,19 @@ func (u *Upload) Write() error { } func init() { - _, err := DB.Sql.Exec(DBUploadSchema) + _, err := DB.Sql.Exec(DBInvoicesSchema) if err != nil { log.Fatal(err) } + + _, err = DB.Sql.Exec(DBUploadsSchema) + if err != nil { + log.Fatal(err) + } + + _, err = DB.Sql.Exec(DBFileUploadsSchema) + if err != nil { + log.Fatal(err) + } + } diff --git a/web/Caddyfile-prod b/web/Caddyfile-prod new file mode 100644 index 0000000..3c2b6c4 --- /dev/null +++ b/web/Caddyfile-prod @@ -0,0 +1,28 @@ +#172.29.195.31 +0.0.0.0 + +root /srv +ext .html + + +header / { + Content-Security-Policy "default-src * 'unsafe-eval' 'unsafe-inline' data: ;" +} + + +proxy /d bit4sat-api:8880 + +proxy /api bit4sat-api:8880 { + transparent +} + +#log /api stdout +log / stdout + +#proxy /ws localhost:8880 { +# websocket +# transparent +#} + + +#tls self_signed diff --git a/web/package.json b/web/package.json index ebd7062..3cef4bd 100644 --- a/web/package.json +++ b/web/package.json @@ -31,6 +31,6 @@ "scripts": { "start": "parcel index.html", "watch": "parcel watch index.html", - "build": "parcel index.html --public-url" + "build": "parcel build index.html" } } diff --git a/web/src/Pay.vue b/web/src/Pay.vue index 4c44080..8613bdd 100644 --- a/web/src/Pay.vue +++ b/web/src/Pay.vue @@ -4,7 +4,9 @@
-
upload #{{uploadId}}
+
+ ID: {{uploadId}} +
@@ -31,6 +33,7 @@ + {{invoice.msatoshi / 1000}} Satoshi