refactor: Structure code into different packages
I have put too much code into the same files. Also, I put everything into the same package: main. This package is only meant for executables. Therefore, I have refactored my code to use multiple packages. These packages also guarantee separation of concerns since Golang doesn't allow cyclic imports.
This commit is contained in:
parent
b4a8adcb9a
commit
7558655458
10
Makefile
10
Makefile
@ -1,8 +1,12 @@
|
|||||||
|
.PHONY: build run
|
||||||
|
|
||||||
|
SOURCE := $(shell find db env lib lnd pages public server -type f)
|
||||||
|
|
||||||
build: delphi.market
|
build: delphi.market
|
||||||
|
|
||||||
delphi.market: src/*.go
|
delphi.market: $(SOURCE)
|
||||||
go build -o delphi.market ./src/
|
go build -o delphi.market .
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go run ./src/
|
go run .
|
||||||
|
|
||||||
|
45
db/db.go
Normal file
45
db/db.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/namsral/flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
db *DB
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
*sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
log.Fatalf("error loading env vars: %s", err)
|
||||||
|
}
|
||||||
|
var dbUrl string
|
||||||
|
flag.StringVar(&dbUrl, "DATABASE_URL", "", "Database URL")
|
||||||
|
flag.Parse()
|
||||||
|
if dbUrl == "" {
|
||||||
|
log.Fatal("DATABASE_URL not set")
|
||||||
|
}
|
||||||
|
db = initDB(dbUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB(url string) *DB {
|
||||||
|
db, err := sql.Open("postgres", url)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// test connection
|
||||||
|
_, err = db.Exec("SELECT 1")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// TODO: run migrations
|
||||||
|
return &DB{DB: db}
|
||||||
|
}
|
46
db/invoice.go
Normal file
46
db/invoice.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func CreateInvoice(invoice *Invoice) error {
|
||||||
|
if err := db.QueryRow(""+
|
||||||
|
"INSERT INTO invoices(pubkey, msats, preimage, hash, bolt11, created_at, expires_at) "+
|
||||||
|
"VALUES($1, $2, $3, $4, $5, $6, $7) "+
|
||||||
|
"RETURNING id",
|
||||||
|
invoice.Pubkey, invoice.Msats, invoice.Preimage, invoice.Hash, invoice.PaymentRequest, invoice.CreatedAt, invoice.ExpiresAt).Scan(&invoice.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchInvoiceWhere struct {
|
||||||
|
Id string
|
||||||
|
Hash string
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchInvoice(where *FetchInvoiceWhere, invoice *Invoice) error {
|
||||||
|
var (
|
||||||
|
query = "SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since FROM invoices "
|
||||||
|
args []any
|
||||||
|
)
|
||||||
|
if where.Id != "" {
|
||||||
|
query += "WHERE id = $1"
|
||||||
|
args = append(args, where.Id)
|
||||||
|
} else if where.Hash != "" {
|
||||||
|
query += "WHERE hash = $1"
|
||||||
|
args = append(args, where.Hash)
|
||||||
|
}
|
||||||
|
if err := db.QueryRow(query, args...).Scan(
|
||||||
|
&invoice.Id, &invoice.Pubkey, &invoice.Msats, &invoice.Preimage, &invoice.Hash,
|
||||||
|
&invoice.PaymentRequest, &invoice.CreatedAt, &invoice.ExpiresAt, &invoice.ConfirmedAt, &invoice.HeldSince); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfirmInvoice(hash string, confirmedAt time.Time, msatsReceived int) error {
|
||||||
|
if _, err := db.Exec("UPDATE invoices SET confirmed_at = $2, msats_received = $3 WHERE hash = $1", hash, confirmedAt, msatsReceived); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
18
db/lnauth.go
Normal file
18
db/lnauth.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
func CreateLNAuth(lnAuth *LNAuth) error {
|
||||||
|
err := db.QueryRow(
|
||||||
|
"INSERT INTO lnauth(k1, lnurl) VALUES($1, $2) RETURNING session_id",
|
||||||
|
lnAuth.K1, lnAuth.LNURL).Scan(&lnAuth.SessionId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchSessionId(k1 string, sessionId *string) error {
|
||||||
|
err := db.QueryRow("SELECT session_id FROM lnauth WHERE k1 = $1", k1).Scan(sessionId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteLNAuth(lnAuth *LNAuth) error {
|
||||||
|
_, err := db.Exec("DELETE FROM lnauth WHERE k1 = $1", lnAuth.K1)
|
||||||
|
return err
|
||||||
|
}
|
90
db/market.go
Normal file
90
db/market.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import "database/sql"
|
||||||
|
|
||||||
|
func FetchMarket(marketId int, market *Market) error {
|
||||||
|
if err := db.QueryRow("SELECT id, description FROM markets WHERE id = $1", marketId).Scan(&market.Id, &market.Description); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchActiveMarkets(markets *[]Market) error {
|
||||||
|
var (
|
||||||
|
rows *sql.Rows
|
||||||
|
market Market
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if rows, err = db.Query("SELECT id, description, active FROM markets WHERE active = true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
rows.Scan(&market.Id, &market.Description, &market.Active)
|
||||||
|
*markets = append(*markets, market)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchShares(marketId int, shares *[]Share) error {
|
||||||
|
rows, err := db.Query("SELECT id, market_id, description FROM shares WHERE market_id = $1 ORDER BY description DESC", marketId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var share Share
|
||||||
|
rows.Scan(&share.Id, &share.MarketId, &share.Description)
|
||||||
|
*shares = append(*shares, share)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchOrdersWhere struct {
|
||||||
|
MarketId int
|
||||||
|
Pubkey string
|
||||||
|
Confirmed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchOrders(where *FetchOrdersWhere, orders *[]Order) error {
|
||||||
|
query := "" +
|
||||||
|
"SELECT o.id, share_id, o.pubkey, o.side, o.quantity, o.price, o.invoice_id, o.created_at, s.description, s.market_id, i.confirmed_at " +
|
||||||
|
"FROM orders o " +
|
||||||
|
"JOIN invoices i ON o.invoice_id = i.id " +
|
||||||
|
"JOIN shares s ON o.share_id = s.id " +
|
||||||
|
"WHERE "
|
||||||
|
var args []any
|
||||||
|
if where.MarketId > 0 {
|
||||||
|
query += "share_id = ANY(SELECT id FROM shares WHERE market_id = $1) "
|
||||||
|
args = append(args, where.MarketId)
|
||||||
|
} else if where.Pubkey != "" {
|
||||||
|
query += "o.pubkey = $1 "
|
||||||
|
args = append(args, where.Pubkey)
|
||||||
|
}
|
||||||
|
if where.Confirmed {
|
||||||
|
query += "AND i.confirmed_at IS NOT NULL "
|
||||||
|
}
|
||||||
|
query += "AND (i.confirmed_at IS NOT NULL OR i.expires_at > CURRENT_TIMESTAMP) "
|
||||||
|
query += "ORDER BY price DESC"
|
||||||
|
rows, err := db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var order Order
|
||||||
|
rows.Scan(&order.Id, &order.ShareId, &order.Pubkey, &order.Side, &order.Quantity, &order.Price, &order.InvoiceId, &order.CreatedAt, &order.Share.Description, &order.Share.MarketId, &order.Invoice.ConfirmedAt)
|
||||||
|
*orders = append(*orders, order)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateOrder(order *Order) error {
|
||||||
|
if _, err := db.Exec(""+
|
||||||
|
"INSERT INTO orders(share_id, pubkey, side, quantity, price, invoice_id) "+
|
||||||
|
"VALUES ($1, $2, $3, $4, $5, $6)",
|
||||||
|
order.ShareId, order.Pubkey, order.Side, order.Quantity, order.Price, order.InvoiceId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
16
db/session.go
Normal file
16
db/session.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
func CreateSession(s *Session) error {
|
||||||
|
_, err := db.Exec("INSERT INTO sessions(pubkey, session_id) VALUES($1, $2)", s.Pubkey, s.SessionId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchSession(s *Session) error {
|
||||||
|
err := db.QueryRow("SELECT pubkey FROM sessions WHERE session_id = $1", s.SessionId).Scan(&s.Pubkey)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSession(s *Session) error {
|
||||||
|
_, err := db.Exec("DELETE FROM sessions where session_id = $1", s.SessionId)
|
||||||
|
return err
|
||||||
|
}
|
66
db/types.go
Normal file
66
db/types.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/guregu/null.v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Serial = int
|
||||||
|
type UUID = string
|
||||||
|
|
||||||
|
type LNAuth struct {
|
||||||
|
K1 string
|
||||||
|
LNURL string
|
||||||
|
CreatdAt time.Time
|
||||||
|
SessionId string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Pubkey string
|
||||||
|
LastSeen time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
Pubkey string
|
||||||
|
SessionId string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Market struct {
|
||||||
|
Id Serial
|
||||||
|
Description string
|
||||||
|
Active bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Share struct {
|
||||||
|
Id UUID
|
||||||
|
MarketId int
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Invoice struct {
|
||||||
|
Id UUID
|
||||||
|
Pubkey string
|
||||||
|
Msats int64
|
||||||
|
MsatsReceived int64
|
||||||
|
Preimage string
|
||||||
|
Hash string
|
||||||
|
PaymentRequest string
|
||||||
|
CreatedAt time.Time
|
||||||
|
ExpiresAt time.Time
|
||||||
|
ConfirmedAt null.Time
|
||||||
|
HeldSince null.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
Id UUID
|
||||||
|
CreatedAt time.Time
|
||||||
|
ShareId string `form:"share_id"`
|
||||||
|
Share
|
||||||
|
Pubkey string
|
||||||
|
Side string `form:"side"`
|
||||||
|
Quantity int64 `form:"quantity"`
|
||||||
|
Price int64 `form:"price"`
|
||||||
|
InvoiceId UUID
|
||||||
|
Invoice
|
||||||
|
}
|
13
db/user.go
Normal file
13
db/user.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
func CreateUser(u *User) error {
|
||||||
|
_, err := db.Exec(
|
||||||
|
"INSERT INTO users(pubkey) VALUES ($1) ON CONFLICT(pubkey) DO UPDATE SET last_seen = CURRENT_TIMESTAMP",
|
||||||
|
u.Pubkey)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateUser(u *User) error {
|
||||||
|
_, err := db.Exec("UPDATE users SET last_seen = $1 WHERE pubkey = $2", u.LastSeen, u.Pubkey)
|
||||||
|
return err
|
||||||
|
}
|
42
env/env.go
vendored
Normal file
42
env/env.go
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/namsral/flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Port int
|
||||||
|
PublicURL string
|
||||||
|
Env string
|
||||||
|
CommitLongSha string
|
||||||
|
CommitShortSha string
|
||||||
|
Version string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
log.Fatalf("error loading env vars: %s", err)
|
||||||
|
}
|
||||||
|
flag.StringVar(&PublicURL, "PUBLIC_URL", "delphi.market", "Public URL of website")
|
||||||
|
flag.IntVar(&Port, "PORT", 4321, "Server port")
|
||||||
|
flag.StringVar(&Env, "ENV", "development", "Specify for which environment files should be built")
|
||||||
|
flag.Parse()
|
||||||
|
CommitLongSha = execCmd("git", "rev-parse", "HEAD")
|
||||||
|
CommitShortSha = execCmd("git", "rev-parse", "--short", "HEAD")
|
||||||
|
Version = fmt.Sprintf("v0.0.0+%s", CommitShortSha)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCmd(name string, args ...string) string {
|
||||||
|
cmd := exec.Command(name, args...)
|
||||||
|
stdout, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(stdout))
|
||||||
|
}
|
14
go.mod
14
go.mod
@ -1,16 +1,21 @@
|
|||||||
module delphi.market
|
module git.ekzyis.com/ekzyis/delphi.market
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
|
replace git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e => gitlab.com/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.2
|
github.com/btcsuite/btcd/btcec/v2 v2.2.2
|
||||||
github.com/btcsuite/btcutil v1.0.2
|
github.com/btcsuite/btcutil v1.0.2
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/labstack/echo/v4 v4.11.1
|
github.com/labstack/echo/v4 v4.11.1
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/lightninglabs/lndclient v1.0.0
|
||||||
|
github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200615174244-103c59a4889f
|
||||||
github.com/namsral/flag v1.7.4-pre
|
github.com/namsral/flag v1.7.4-pre
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||||
|
gopkg.in/guregu/null.v4 v4.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -55,10 +60,8 @@ require (
|
|||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
|
||||||
github.com/labstack/gommon v0.4.0 // indirect
|
github.com/labstack/gommon v0.4.0 // indirect
|
||||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
||||||
github.com/lightninglabs/lndclient v1.0.0 // indirect
|
|
||||||
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 // indirect
|
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 // indirect
|
||||||
github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea // indirect
|
github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea // indirect
|
||||||
github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200615174244-103c59a4889f // indirect
|
|
||||||
github.com/lightningnetwork/lnd/clock v1.0.1 // indirect
|
github.com/lightningnetwork/lnd/clock v1.0.1 // indirect
|
||||||
github.com/lightningnetwork/lnd/queue v1.0.4 // indirect
|
github.com/lightningnetwork/lnd/queue v1.0.4 // indirect
|
||||||
github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect
|
github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect
|
||||||
@ -85,14 +88,13 @@ require (
|
|||||||
go.uber.org/zap v1.14.1 // indirect
|
go.uber.org/zap v1.14.1 // indirect
|
||||||
golang.org/x/crypto v0.11.0 // indirect
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
golang.org/x/net v0.12.0 // indirect
|
golang.org/x/net v0.12.0 // indirect
|
||||||
golang.org/x/sys v0.10.0 // indirect
|
golang.org/x/sys v0.12.0 // indirect
|
||||||
golang.org/x/term v0.10.0 // indirect
|
golang.org/x/term v0.10.0 // indirect
|
||||||
golang.org/x/text v0.11.0 // indirect
|
golang.org/x/text v0.11.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c // indirect
|
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c // indirect
|
||||||
google.golang.org/grpc v1.24.0 // indirect
|
google.golang.org/grpc v1.24.0 // indirect
|
||||||
gopkg.in/errgo.v1 v1.0.1 // indirect
|
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||||
gopkg.in/guregu/null.v4 v4.0.0 // indirect
|
|
||||||
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
|
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
|
||||||
gopkg.in/macaroon.v2 v2.1.0 // indirect
|
gopkg.in/macaroon.v2 v2.1.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.3 // indirect
|
gopkg.in/yaml.v2 v2.2.3 // indirect
|
||||||
|
35
go.sum
35
go.sum
@ -1,5 +1,5 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
|
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
|
||||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
|
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
|
||||||
@ -24,7 +24,6 @@ github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 h1:QyTpiR5nQ
|
|||||||
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
|
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.2 h1:5uxe5YjoCq+JeOpg0gZSNHuFgeogrocBYxvg6w9sAgc=
|
github.com/btcsuite/btcd/btcec/v2 v2.2.2 h1:5uxe5YjoCq+JeOpg0gZSNHuFgeogrocBYxvg6w9sAgc=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.2/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8=
|
github.com/btcsuite/btcd/btcec/v2 v2.2.2/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8=
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
@ -51,8 +50,10 @@ github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JG
|
|||||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||||
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
|
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
|
||||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||||
|
github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
|
||||||
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
|
github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE=
|
||||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
|
||||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||||
@ -81,6 +82,7 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
|
|||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
|
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
|
||||||
|
github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY=
|
||||||
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
@ -96,7 +98,9 @@ github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
|||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
@ -109,6 +113,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
|||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
@ -135,13 +140,19 @@ github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI
|
|||||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
|
||||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
||||||
|
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
|
||||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
|
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
|
||||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||||
|
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU=
|
||||||
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
|
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
|
||||||
|
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc=
|
||||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||||
|
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d h1:irPlN9z5VCe6BTsqVsxheCZH99OFSmqSVyTigW4mEoY=
|
||||||
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
|
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
|
||||||
|
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag=
|
||||||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
@ -151,8 +162,10 @@ github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6
|
|||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
|
github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
|
||||||
github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
|
github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
|
||||||
@ -210,6 +223,7 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@ -257,12 +271,15 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
|||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
gitlab.com/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:PIe5G60AUtC0u4HgbjMVEeRR1EUGMMBl6a0gB1tRdDk=
|
||||||
|
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
|
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
|
||||||
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||||
@ -277,14 +294,16 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -320,8 +339,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -339,6 +358,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
|||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@ -353,6 +373,7 @@ google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s=
|
|||||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
|
gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
|
||||||
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
|
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
|
||||||
@ -365,6 +386,7 @@ gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETf
|
|||||||
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
|
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
|
||||||
gopkg.in/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI=
|
gopkg.in/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI=
|
||||||
gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o=
|
gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@ -377,6 +399,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
|
@ -13,10 +13,9 @@ function restart_server() {
|
|||||||
PID=$(pidof delphi.market)
|
PID=$(pidof delphi.market)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sync() {
|
function restart() {
|
||||||
restart_server
|
restart_server
|
||||||
date +%s.%N > public/hotreload
|
date +%s.%N > public/hotreload
|
||||||
rsync -avh public/ dev1.delphi.market:/var/www/dev1.delphi --delete
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
@ -25,9 +24,9 @@ function cleanup() {
|
|||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
sync
|
restart
|
||||||
tail -f server.log &
|
tail -f server.log &
|
||||||
|
|
||||||
while inotifywait -r -e modify src/ pages/; do
|
while inotifywait -r -e modify db/ env/ lib/ lnd/ pages/ public/ server/; do
|
||||||
sync
|
restart
|
||||||
done
|
done
|
||||||
|
8
lib/max.go
Normal file
8
lib/max.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
func Max(x, y int) int {
|
||||||
|
if x < y {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
7
lib/merge.go
Normal file
7
lib/merge.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
func Merge[T comparable](target *map[T]any, src *map[T]any) {
|
||||||
|
for k, v := range *src {
|
||||||
|
(*target)[k] = v
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@ -6,13 +6,6 @@ import (
|
|||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Max(x, y int) int {
|
|
||||||
if x < y {
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToQR(s string) (string, error) {
|
func ToQR(s string) (string, error) {
|
||||||
png, err := qrcode.Encode(s, qrcode.Medium, 256)
|
png, err := qrcode.Encode(s, qrcode.Medium, 256)
|
||||||
if err != nil {
|
if err != nil {
|
112
lnd/invoice.go
Normal file
112
lnd/invoice.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package lnd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"github.com/lightninglabs/lndclient"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateInvoice(pubkey string, msats int64) (*db.Invoice, error) {
|
||||||
|
var (
|
||||||
|
expiry time.Duration = time.Hour
|
||||||
|
preimage lntypes.Preimage
|
||||||
|
hash lntypes.Hash
|
||||||
|
paymentRequest string
|
||||||
|
lnInvoice *lndclient.Invoice
|
||||||
|
dbInvoice *db.Invoice
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if preimage, err = generateNewPreimage(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hash = preimage.Hash()
|
||||||
|
if paymentRequest, err = lnd.Invoices.AddHoldInvoice(context.TODO(), &invoicesrpc.AddInvoiceData{
|
||||||
|
Hash: &hash,
|
||||||
|
Value: lnwire.MilliSatoshi(msats),
|
||||||
|
Expiry: int64(expiry / time.Millisecond),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if lnInvoice, err = lnd.Client.LookupInvoice(context.TODO(), hash); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dbInvoice = &db.Invoice{
|
||||||
|
Pubkey: pubkey,
|
||||||
|
Msats: msats,
|
||||||
|
Preimage: preimage.String(),
|
||||||
|
PaymentRequest: paymentRequest,
|
||||||
|
Hash: hash.String(),
|
||||||
|
CreatedAt: lnInvoice.CreationDate,
|
||||||
|
ExpiresAt: lnInvoice.CreationDate.Add(expiry),
|
||||||
|
}
|
||||||
|
if err := db.CreateInvoice(dbInvoice); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dbInvoice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckInvoice(hash lntypes.Hash) {
|
||||||
|
var (
|
||||||
|
pollInterval = 5 * time.Second
|
||||||
|
invoice db.Invoice
|
||||||
|
lnInvoice *lndclient.Invoice
|
||||||
|
preimage lntypes.Preimage
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if !Enabled {
|
||||||
|
log.Printf("LND disabled, skipping checking invoice: hash=%s", hash)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = db.FetchInvoice(&db.FetchInvoiceWhere{Hash: hash.String()}, &invoice); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLoopError := func(err error) {
|
||||||
|
log.Println(err)
|
||||||
|
time.Sleep(pollInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
log.Printf("lookup invoice: hash=%s", hash)
|
||||||
|
if lnInvoice, err = lnd.Client.LookupInvoice(context.TODO(), hash); err != nil {
|
||||||
|
handleLoopError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if time.Now().After(invoice.ExpiresAt) {
|
||||||
|
// cancel invoices after expiration if no matching order found yet
|
||||||
|
if err = lnd.Invoices.CancelInvoice(context.TODO(), hash); err != nil {
|
||||||
|
handleLoopError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("invoice expired: hash=%s", hash)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if lnInvoice.AmountPaid > 0 {
|
||||||
|
if preimage, err = lntypes.MakePreimageFromStr(invoice.Preimage); err != nil {
|
||||||
|
handleLoopError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO settle invoice after matching order was found
|
||||||
|
if err = lnd.Invoices.SettleInvoice(context.TODO(), preimage); err != nil {
|
||||||
|
handleLoopError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = db.ConfirmInvoice(hash.String(), time.Now(), int(lnInvoice.AmountPaid)); err != nil {
|
||||||
|
handleLoopError(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("invoice confirmed: hash=%s", hash)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(pollInterval)
|
||||||
|
}
|
||||||
|
}
|
21
lnd/lib.go
Normal file
21
lnd/lib.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package lnd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateNewPreimage() (lntypes.Preimage, error) {
|
||||||
|
randomBytes := make([]byte, 32)
|
||||||
|
_, err := io.ReadFull(rand.Reader, randomBytes)
|
||||||
|
if err != nil {
|
||||||
|
return lntypes.Preimage{}, err
|
||||||
|
}
|
||||||
|
preimage, err := lntypes.MakePreimage(randomBytes)
|
||||||
|
if err != nil {
|
||||||
|
return lntypes.Preimage{}, err
|
||||||
|
}
|
||||||
|
return preimage, nil
|
||||||
|
}
|
49
lnd/lnd.go
Normal file
49
lnd/lnd.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package lnd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/lightninglabs/lndclient"
|
||||||
|
"github.com/namsral/flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lnd *LNDClient
|
||||||
|
Enabled bool
|
||||||
|
)
|
||||||
|
|
||||||
|
type LNDClient struct {
|
||||||
|
lndclient.GrpcLndServices
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var (
|
||||||
|
lndCert string
|
||||||
|
lndMacaroonDir string
|
||||||
|
lndHost string
|
||||||
|
rpcLndServices *lndclient.GrpcLndServices
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if err = godotenv.Load(); err != nil {
|
||||||
|
log.Fatalf("error loading env vars: %s", err)
|
||||||
|
}
|
||||||
|
flag.StringVar(&lndCert, "LND_CERT", "", "Path to LND TLS certificate")
|
||||||
|
flag.StringVar(&lndMacaroonDir, "LND_MACAROON_DIR", "", "LND macaroon directory")
|
||||||
|
flag.StringVar(&lndHost, "LND_HOST", "localhost:10001", "LND gRPC server address")
|
||||||
|
flag.Parse()
|
||||||
|
if rpcLndServices, err = lndclient.NewLndServices(&lndclient.LndServicesConfig{
|
||||||
|
LndAddress: lndHost,
|
||||||
|
MacaroonDir: lndMacaroonDir,
|
||||||
|
TLSPath: lndCert,
|
||||||
|
// TODO: make network configurable
|
||||||
|
Network: lndclient.NetworkRegtest,
|
||||||
|
}); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
Enabled = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lnd = &LNDClient{GrpcLndServices: *rpcLndServices}
|
||||||
|
log.Printf("Connected to %s running LND v%s", lndHost, lnd.Version.Version)
|
||||||
|
Enabled = true
|
||||||
|
}
|
1
lnd/types.go
Normal file
1
lnd/types.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package lnd
|
28
main.go
Normal file
28
main.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/env"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
s *server.Server
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Printf("Commit: %s", env.CommitShortSha)
|
||||||
|
log.Printf("Public URL: %s", env.PublicURL)
|
||||||
|
log.Printf("Environment: %s", env.Env)
|
||||||
|
|
||||||
|
s = server.NewServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := s.Start(fmt.Sprintf("%s:%d", "127.0.0.1", env.Port)); err != http.ErrServerClosed {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -51,11 +51,11 @@
|
|||||||
<div class="font-mono word-wrap mb-1">{{.lnurl}}</div>
|
<div class="font-mono word-wrap mb-1">{{.lnurl}}</div>
|
||||||
<details class="font-mono mb-1 align-left">
|
<details class="font-mono mb-1 align-left">
|
||||||
<summary>details</summary>
|
<summary>details</summary>
|
||||||
<div>id: {{.Invoice.Id}}</div>
|
<div>id: {{.invoice.Id}}</div>
|
||||||
<div>amount: {{div .Invoice.Msats 1000}} sats</div>
|
<div>amount: {{div .invoice.Msats 1000}} sats</div>
|
||||||
<div>created: {{.Invoice.CreatedAt}}</div>
|
<div>created: {{.invoice.CreatedAt}}</div>
|
||||||
<div>expiry : {{.Invoice.ExpiresAt}}</div>
|
<div>expiry : {{.invoice.ExpiresAt}}</div>
|
||||||
<div class="word-wrap">hash: {{.Invoice.PaymentHash}}</div>
|
<div class="word-wrap">hash: {{.invoice.Hash}}</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -63,10 +63,10 @@
|
|||||||
<script>
|
<script>
|
||||||
const statusElement = document.querySelector("#status")
|
const statusElement = document.querySelector("#status")
|
||||||
const label = document.querySelector("#status-label")
|
const label = document.querySelector("#status-label")
|
||||||
const status = "{{.Status}}"
|
const status = "{{.status}}"
|
||||||
const redirectUrl = "{{.RedirectURL}}"
|
const redirectUrl = "{{.redirectURL}}"
|
||||||
function poll() {
|
function poll() {
|
||||||
const invoiceId = "{{.Invoice.Id}}"
|
const invoiceId = "{{.invoice.Id}}"
|
||||||
const countdown = document.querySelector("#countdown")
|
const countdown = document.querySelector("#countdown")
|
||||||
const redirect = () => {
|
const redirect = () => {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
|
65
server/auth/lnauth.go
Normal file
65
server/auth/lnauth.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcutil/bech32"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LNAuth struct {
|
||||||
|
K1 string
|
||||||
|
LNURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LNAuthResponse struct {
|
||||||
|
K1 string `query:"k1"`
|
||||||
|
Sig string `query:"sig"`
|
||||||
|
Key string `query:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLNAuth() (*LNAuth, error) {
|
||||||
|
k1 := make([]byte, 32)
|
||||||
|
_, err := rand.Read(k1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("rand.Read error: %w", err)
|
||||||
|
}
|
||||||
|
k1hex := hex.EncodeToString(k1)
|
||||||
|
url := []byte(fmt.Sprintf("https://%s/api/login?tag=login&k1=%s&action=login", env.PublicURL, k1hex))
|
||||||
|
conv, err := bech32.ConvertBits(url, 8, 5, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bech32.ConvertBits error: %w", err)
|
||||||
|
}
|
||||||
|
lnurl, err := bech32.Encode("lnurl", conv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bech32.Encode error: %w", err)
|
||||||
|
}
|
||||||
|
return &LNAuth{k1hex, lnurl}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyLNAuth(r *LNAuthResponse) (bool, error) {
|
||||||
|
var k1Bytes, sigBytes, keyBytes []byte
|
||||||
|
k1Bytes, err := hex.DecodeString(r.K1)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("k1 decode error: %w", err)
|
||||||
|
}
|
||||||
|
sigBytes, err = hex.DecodeString(r.Sig)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("sig decode error: %w", err)
|
||||||
|
}
|
||||||
|
keyBytes, err = hex.DecodeString(r.Key)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("key decode error: %w", err)
|
||||||
|
}
|
||||||
|
key, err := btcec.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("key parse error: %w", err)
|
||||||
|
}
|
||||||
|
ecdsaKey := ecdsa.PublicKey{Curve: btcec.S256(), X: key.X(), Y: key.Y()}
|
||||||
|
return ecdsa.VerifyASN1(&ecdsaKey, k1Bytes, sigBytes), nil
|
||||||
|
}
|
45
server/error.go
Normal file
45
server/error.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func httpErrorHandler(err error, c echo.Context) {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
code := http.StatusInternalServerError
|
||||||
|
if httpError, ok := err.(*echo.HTTPError); ok {
|
||||||
|
code = httpError.Code
|
||||||
|
}
|
||||||
|
filePath := fmt.Sprintf("public/%d.html", code)
|
||||||
|
var f *os.File
|
||||||
|
if f, err = os.Open(filePath); err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
serveError(c, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = c.Stream(code, "text/html", f); err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
serveError(c, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveError(c echo.Context, code int) error {
|
||||||
|
var (
|
||||||
|
f *os.File
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if f, err = os.Open(fmt.Sprintf("public/%d.html", code)); err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = c.Stream(code, "text/html", f); err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
28
server/router/handler/index.go
Normal file
28
server/router/handler/index.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lib"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleIndex(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
markets []db.Market
|
||||||
|
err error
|
||||||
|
data map[string]any
|
||||||
|
)
|
||||||
|
if err = db.FetchActiveMarkets(&markets); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data = map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"markets": markets,
|
||||||
|
}
|
||||||
|
lib.Merge(&data, &envVars)
|
||||||
|
return c.Render(http.StatusOK, "index.html", data)
|
||||||
|
}
|
||||||
|
}
|
78
server/router/handler/invoice.go
Normal file
78
server/router/handler/invoice.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lib"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lnd"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleInvoiceAPI(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
invoiceId string
|
||||||
|
invoice db.Invoice
|
||||||
|
u db.User
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
invoiceId = c.Param("id")
|
||||||
|
if err = db.FetchInvoice(&db.FetchInvoiceWhere{Id: invoiceId}, &invoice); err == sql.ErrNoRows {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if u = c.Get("session").(db.User); invoice.Pubkey != u.Pubkey {
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
invoice.Preimage = ""
|
||||||
|
return c.JSON(http.StatusOK, invoice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleInvoice(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
invoiceId string
|
||||||
|
invoice db.Invoice
|
||||||
|
u db.User
|
||||||
|
hash lntypes.Hash
|
||||||
|
qr string
|
||||||
|
status string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
invoiceId = c.Param("id")
|
||||||
|
if err = db.FetchInvoice(&db.FetchInvoiceWhere{Id: invoiceId}, &invoice); err == sql.ErrNoRows {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if u = c.Get("session").(db.User); invoice.Pubkey != u.Pubkey {
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
if hash, err = lntypes.MakeHashFromStr(invoice.Hash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go lnd.CheckInvoice(hash)
|
||||||
|
if qr, err = lib.ToQR(invoice.PaymentRequest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if invoice.ConfirmedAt.Valid {
|
||||||
|
status = "Paid"
|
||||||
|
} else if time.Now().After(invoice.ExpiresAt) {
|
||||||
|
status = "Expired"
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"invoice": invoice,
|
||||||
|
"status": status,
|
||||||
|
"lnurl": invoice.PaymentRequest,
|
||||||
|
"qr": qr,
|
||||||
|
}
|
||||||
|
return c.Render(http.StatusOK, "invoice.html", data)
|
||||||
|
}
|
||||||
|
}
|
76
server/router/handler/login.go
Normal file
76
server/router/handler/login.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lib"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/server/auth"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleLogin(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
lnAuth *auth.LNAuth
|
||||||
|
dbLnAuth db.LNAuth
|
||||||
|
err error
|
||||||
|
expires time.Time = time.Now().Add(60 * 60 * 24 * 365 * time.Second)
|
||||||
|
qr string
|
||||||
|
data map[string]any
|
||||||
|
)
|
||||||
|
if lnAuth, err = auth.NewLNAuth(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbLnAuth = db.LNAuth{K1: lnAuth.K1, LNURL: lnAuth.LNURL}
|
||||||
|
if err = db.CreateLNAuth(&dbLnAuth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.SetCookie(&http.Cookie{Name: "session", HttpOnly: true, Path: "/", Value: dbLnAuth.SessionId, Secure: true, Expires: expires})
|
||||||
|
if qr, err = lib.ToQR(lnAuth.LNURL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data = map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"lnurl": lnAuth.LNURL,
|
||||||
|
"qr": qr,
|
||||||
|
}
|
||||||
|
lib.Merge(&data, &envVars)
|
||||||
|
return c.Render(http.StatusOK, "login.html", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleLoginCallback(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
query auth.LNAuthResponse
|
||||||
|
sessionId string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if err := c.Bind(&query); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
if err = db.FetchSessionId(query.K1, &sessionId); err == sql.ErrNoRows {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, map[string]string{"reason": "session not found"})
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ok, err := auth.VerifyLNAuth(&query); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, map[string]string{"reason": "bad signature"})
|
||||||
|
}
|
||||||
|
if err = db.CreateUser(&db.User{Pubkey: query.Key}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = db.CreateSession(&db.Session{Pubkey: query.Key, SessionId: sessionId}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = db.DeleteLNAuth(&db.LNAuth{K1: query.K1}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, map[string]string{"status": "OK"})
|
||||||
|
}
|
||||||
|
}
|
30
server/router/handler/logout.go
Normal file
30
server/router/handler/logout.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleLogout(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
cookie *http.Cookie
|
||||||
|
sessionId string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if cookie, err = c.Cookie("session"); err != nil {
|
||||||
|
// cookie not found
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/")
|
||||||
|
}
|
||||||
|
sessionId = cookie.Value
|
||||||
|
if err = db.DeleteSession(&db.Session{SessionId: sessionId}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// tell browser that cookie is expired and thus can be deleted
|
||||||
|
c.SetCookie(&http.Cookie{Name: "session", HttpOnly: true, Path: "/", Value: sessionId, Secure: true, Expires: time.Now()})
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/")
|
||||||
|
}
|
||||||
|
}
|
121
server/router/handler/market.go
Normal file
121
server/router/handler/market.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/env"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lib"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lnd"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleMarket(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
marketId int64
|
||||||
|
market db.Market
|
||||||
|
shares []db.Share
|
||||||
|
orders []db.Order
|
||||||
|
err error
|
||||||
|
data map[string]any
|
||||||
|
)
|
||||||
|
if marketId, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
||||||
|
}
|
||||||
|
if err = db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = db.FetchShares(market.Id, &shares); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = db.FetchOrders(&db.FetchOrdersWhere{MarketId: market.Id, Confirmed: true}, &orders); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data = map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"Id": market.Id,
|
||||||
|
"Description": market.Description,
|
||||||
|
// shares are sorted by description in descending order
|
||||||
|
// that's how we know that YES must be the first share
|
||||||
|
"YesShare": shares[0],
|
||||||
|
"NoShare": shares[1],
|
||||||
|
"Orders": orders,
|
||||||
|
}
|
||||||
|
lib.Merge(&data, &envVars)
|
||||||
|
return c.Render(http.StatusOK, "market.html", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlePostOrder(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
marketId string
|
||||||
|
u db.User
|
||||||
|
o db.Order
|
||||||
|
invoice *db.Invoice
|
||||||
|
msats int64
|
||||||
|
data map[string]any
|
||||||
|
qr string
|
||||||
|
hash lntypes.Hash
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
marketId = c.Param("id")
|
||||||
|
// TODO:
|
||||||
|
// [ ] Step 0: If SELL order, check share balance of user
|
||||||
|
// [x] Create HODL invoice
|
||||||
|
// [x] Create (unconfirmed) order
|
||||||
|
// [ ] Find matching orders
|
||||||
|
// [ ] Settle invoice when matching order was found,
|
||||||
|
// else cancel invoice if expired
|
||||||
|
|
||||||
|
// parse body
|
||||||
|
if err := c.Bind(&o); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
u = c.Get("session").(db.User)
|
||||||
|
o.Pubkey = u.Pubkey
|
||||||
|
msats = o.Quantity * o.Price * 1000
|
||||||
|
|
||||||
|
// TODO: if SELL order, check share balance of user
|
||||||
|
|
||||||
|
// Create HODL invoice
|
||||||
|
if invoice, err = lnd.CreateInvoice(o.Pubkey, msats); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Create QR code to pay HODL invoice
|
||||||
|
if qr, err = lib.ToQR(invoice.PaymentRequest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hash, err = lntypes.MakeHashFromStr(invoice.Hash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start goroutine to poll status and update invoice in background
|
||||||
|
go lnd.CheckInvoice(hash)
|
||||||
|
|
||||||
|
// Create (unconfirmed) order
|
||||||
|
o.InvoiceId = invoice.Id
|
||||||
|
if err := db.CreateOrder(&o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: find matching orders
|
||||||
|
|
||||||
|
data = map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"lnurl": invoice.PaymentRequest,
|
||||||
|
"qr": qr,
|
||||||
|
"invoice": *invoice,
|
||||||
|
"redirectURL": fmt.Sprintf("https://%s/market/%s", env.PublicURL, marketId),
|
||||||
|
}
|
||||||
|
lib.Merge(&data, &envVars)
|
||||||
|
return c.Render(http.StatusPaymentRequired, "invoice.html", data)
|
||||||
|
}
|
||||||
|
}
|
29
server/router/handler/session.go
Normal file
29
server/router/handler/session.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleCheckSession(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
cookie *http.Cookie
|
||||||
|
s db.Session
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if cookie, err = c.Cookie("session"); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, map[string]string{"reason": "cookie required"})
|
||||||
|
}
|
||||||
|
s = db.Session{SessionId: cookie.Value}
|
||||||
|
if err = db.FetchSession(&s); err == sql.ErrNoRows {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, map[string]string{"reason": "session not found"})
|
||||||
|
} else if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, map[string]string{"pubkey": s.Pubkey})
|
||||||
|
}
|
||||||
|
}
|
31
server/router/handler/user.go
Normal file
31
server/router/handler/user.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lib"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleUser(envVars map[string]any) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
u db.User
|
||||||
|
orders []db.Order
|
||||||
|
err error
|
||||||
|
data map[string]any
|
||||||
|
)
|
||||||
|
u = c.Get("session").(db.User)
|
||||||
|
if err = db.FetchOrders(&db.FetchOrdersWhere{Pubkey: u.Pubkey}, &orders); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data = map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"user": u,
|
||||||
|
"Orders": orders,
|
||||||
|
}
|
||||||
|
lib.Merge(&data, &envVars)
|
||||||
|
return c.Render(http.StatusOK, "user.html", data)
|
||||||
|
}
|
||||||
|
}
|
19
server/router/middleware/lnd.go
Normal file
19
server/router/middleware/lnd.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/lnd"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LNDGuard(envVars map[string]any) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if lnd.Enabled {
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
return echo.NewHTTPError(http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
server/router/middleware/session.go
Normal file
51
server/router/middleware/session.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/db"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Session(envVars map[string]any) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
cookie *http.Cookie
|
||||||
|
err error
|
||||||
|
s *db.Session
|
||||||
|
u *db.User
|
||||||
|
)
|
||||||
|
if cookie, err = c.Cookie("session"); err != nil {
|
||||||
|
// cookie not found
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
s = &db.Session{SessionId: cookie.Value}
|
||||||
|
if err = db.FetchSession(s); err == nil {
|
||||||
|
// session found
|
||||||
|
u = &db.User{Pubkey: s.Pubkey, LastSeen: time.Now()}
|
||||||
|
if err = db.UpdateUser(u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Set("session", *u)
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SessionGuard(envVars map[string]any) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
session := c.Get("session")
|
||||||
|
if session == nil {
|
||||||
|
return c.Redirect(http.StatusTemporaryRedirect, "/login")
|
||||||
|
}
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
server/router/router.go
Normal file
44
server/router/router.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/env"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/server/router/handler"
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/server/router/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddRoutes(e *echo.Echo) {
|
||||||
|
envVars := map[string]any{
|
||||||
|
"PUBLIC_URL": env.PublicURL,
|
||||||
|
"COMMIT_SHORT_SHA": env.CommitShortSha,
|
||||||
|
"COMMIT_LONG_SHA": env.CommitLongSha,
|
||||||
|
"VERSION": env.Version,
|
||||||
|
}
|
||||||
|
e.Use(middleware.Session(envVars))
|
||||||
|
e.GET("/", handler.HandleIndex(envVars))
|
||||||
|
e.GET("/login", handler.HandleLogin(envVars))
|
||||||
|
e.GET("/api/login", handler.HandleLoginCallback(envVars))
|
||||||
|
e.GET("/api/session", handler.HandleCheckSession(envVars))
|
||||||
|
e.POST("/logout", handler.HandleLogout(envVars))
|
||||||
|
e.GET("/user",
|
||||||
|
handler.HandleUser(envVars),
|
||||||
|
middleware.SessionGuard(envVars))
|
||||||
|
e.GET("/market/:id",
|
||||||
|
handler.HandleMarket(envVars),
|
||||||
|
middleware.SessionGuard(envVars))
|
||||||
|
e.POST("/market/:id/order",
|
||||||
|
handler.HandlePostOrder(envVars),
|
||||||
|
middleware.SessionGuard(envVars),
|
||||||
|
middleware.LNDGuard(envVars))
|
||||||
|
e.GET("/invoice/:id",
|
||||||
|
handler.HandleInvoice(envVars),
|
||||||
|
middleware.SessionGuard(envVars),
|
||||||
|
middleware.LNDGuard(envVars),
|
||||||
|
)
|
||||||
|
e.GET("/api/invoice/:id",
|
||||||
|
handler.HandleInvoiceAPI(envVars),
|
||||||
|
middleware.SessionGuard(envVars),
|
||||||
|
middleware.LNDGuard(envVars),
|
||||||
|
)
|
||||||
|
}
|
59
server/router/template.go
Normal file
59
server/router/template.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template struct {
|
||||||
|
templates *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
type Number interface {
|
||||||
|
constraints.Integer | constraints.Float
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||||
|
return t.templates.ExecuteTemplate(w, name, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
T *Template
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
T = &Template{
|
||||||
|
templates: template.Must(template.New("").Funcs(template.FuncMap{
|
||||||
|
"add": add[int64],
|
||||||
|
"sub": sub[int64],
|
||||||
|
"div": div[int64],
|
||||||
|
"substr": substr,
|
||||||
|
}).ParseGlob("pages/**.html")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func add[T Number](arg1 T, arg2 T) T {
|
||||||
|
return arg1 + arg2
|
||||||
|
}
|
||||||
|
|
||||||
|
func sub[T Number](arg1 T, arg2 T) T {
|
||||||
|
return arg1 - arg2
|
||||||
|
}
|
||||||
|
|
||||||
|
func div[T Number](arg1 T, arg2 T) T {
|
||||||
|
return arg1 / arg2
|
||||||
|
}
|
||||||
|
|
||||||
|
func substr(s string, start, length int) string {
|
||||||
|
if start < 0 || start >= len(s) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
end := start + length
|
||||||
|
if end > len(s) {
|
||||||
|
end = len(s)
|
||||||
|
}
|
||||||
|
return s[start:end]
|
||||||
|
}
|
31
server/server.go
Normal file
31
server/server.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
|
||||||
|
"git.ekzyis.com/ekzyis/delphi.market/server/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*echo.Echo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer() *Server {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
e.Static("/", "public")
|
||||||
|
|
||||||
|
e.Renderer = router.T
|
||||||
|
|
||||||
|
router.AddRoutes(e)
|
||||||
|
|
||||||
|
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||||
|
Format: "${time_custom} ${method} ${uri} ${status}\n",
|
||||||
|
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
||||||
|
}))
|
||||||
|
|
||||||
|
e.HTTPErrorHandler = httpErrorHandler
|
||||||
|
|
||||||
|
return &Server{e}
|
||||||
|
}
|
202
src/auth.go
202
src/auth.go
@ -1,202 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
|
||||||
"github.com/btcsuite/btcutil/bech32"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LnAuth struct {
|
|
||||||
k1 string
|
|
||||||
lnurl string
|
|
||||||
}
|
|
||||||
|
|
||||||
type LnAuthResponse struct {
|
|
||||||
K1 string `query:"k1"`
|
|
||||||
Sig string `query:"sig"`
|
|
||||||
Key string `query:"key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
Pubkey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func lnAuth() (*LnAuth, error) {
|
|
||||||
k1 := make([]byte, 32)
|
|
||||||
_, err := rand.Read(k1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("rand.Read error: %w", err)
|
|
||||||
}
|
|
||||||
k1hex := hex.EncodeToString(k1)
|
|
||||||
url := []byte(fmt.Sprintf("https://%s/api/login?tag=login&k1=%s&action=login", PUBLIC_URL, k1hex))
|
|
||||||
conv, err := bech32.ConvertBits(url, 8, 5, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("bech32.ConvertBits error: %w", err)
|
|
||||||
}
|
|
||||||
lnurl, err := bech32.Encode("lnurl", conv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("bech32.Encode error: %w", err)
|
|
||||||
}
|
|
||||||
return &LnAuth{k1hex, lnurl}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lnAuthVerify(r *LnAuthResponse) (bool, error) {
|
|
||||||
var k1Bytes, sigBytes, keyBytes []byte
|
|
||||||
k1Bytes, err := hex.DecodeString(r.K1)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("k1 decode error: %w", err)
|
|
||||||
}
|
|
||||||
sigBytes, err = hex.DecodeString(r.Sig)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("sig decode error: %w", err)
|
|
||||||
}
|
|
||||||
keyBytes, err = hex.DecodeString(r.Key)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("key decode error: %w", err)
|
|
||||||
}
|
|
||||||
key, err := btcec.ParsePubKey(keyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("key parse error: %w", err)
|
|
||||||
}
|
|
||||||
ecdsaKey := ecdsa.PublicKey{Curve: btcec.S256(), X: key.X(), Y: key.Y()}
|
|
||||||
return ecdsa.VerifyASN1(&ecdsaKey, k1Bytes, sigBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func login(c echo.Context) error {
|
|
||||||
lnauth, err := lnAuth()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var sessionId string
|
|
||||||
err = db.QueryRow("INSERT INTO lnauth(k1, lnurl) VALUES($1, $2) RETURNING session_id", lnauth.k1, lnauth.lnurl).Scan(&sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
expires := time.Now().Add(60 * 60 * 24 * 365 * time.Second)
|
|
||||||
c.SetCookie(&http.Cookie{Name: "session", HttpOnly: true, Path: "/", Value: sessionId, Secure: true, Expires: expires})
|
|
||||||
qr, err := ToQR(lnauth.lnurl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.Render(http.StatusOK, "login.html", map[string]any{"session": c.Get("session"), "PUBLIC_URL": PUBLIC_URL, "lnurl": lnauth.lnurl, "qr": qr})
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyLogin(c echo.Context) error {
|
|
||||||
var query LnAuthResponse
|
|
||||||
if err := c.Bind(&query); err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{"status": "ERROR", "reason": "bad request"})
|
|
||||||
}
|
|
||||||
var sessionId string
|
|
||||||
err := db.QueryRow("SELECT session_id FROM lnauth WHERE k1 = $1", query.K1).Scan(&sessionId)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{"status": "ERROR", "reason": "unknown k1"})
|
|
||||||
} else if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
ok, err := lnAuthVerify(&query)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
c.Logger().Error("bad signature")
|
|
||||||
return c.JSON(http.StatusUnauthorized, map[string]string{"status": "ERROR", "reason": "bad signature"})
|
|
||||||
}
|
|
||||||
_, err = db.Exec("INSERT INTO users(pubkey) VALUES ($1) ON CONFLICT(pubkey) DO UPDATE SET last_seen = CURRENT_TIMESTAMP", query.Key)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
_, err = db.Exec("INSERT INTO sessions(pubkey, session_id) VALUES($1, $2)", query.Key, sessionId)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
_, err = db.Exec("DELETE FROM lnauth WHERE k1 = $1", query.K1)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, map[string]string{"status": "OK"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSession(c echo.Context) error {
|
|
||||||
cookie, err := c.Cookie("session")
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
sessionId := cookie.Value
|
|
||||||
var pubkey string
|
|
||||||
err = db.QueryRow("SELECT pubkey FROM sessions WHERE session_id = $1", sessionId).Scan(&pubkey)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return c.JSON(http.StatusNotFound, map[string]string{"status": "Not Found", "message": "session not found"})
|
|
||||||
} else if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, map[string]string{"pubkey": pubkey})
|
|
||||||
}
|
|
||||||
|
|
||||||
func sessionHandler(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
cookie, err := c.Cookie("session")
|
|
||||||
if err != nil {
|
|
||||||
// cookie not found
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
sessionId := cookie.Value
|
|
||||||
var pubkey string
|
|
||||||
err = db.QueryRow("SELECT pubkey FROM sessions WHERE session_id = $1", sessionId).Scan(&pubkey)
|
|
||||||
if err == nil {
|
|
||||||
// session found
|
|
||||||
_, err = db.Exec("UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE pubkey = $1", pubkey)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
c.Set("session", Session{pubkey})
|
|
||||||
} else if err != sql.ErrNoRows {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"status": "ERROR", "reason": "internal server error"})
|
|
||||||
}
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sessionGuard(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
session := c.Get("session")
|
|
||||||
if session == nil {
|
|
||||||
return c.Redirect(http.StatusTemporaryRedirect, "/login")
|
|
||||||
}
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logout(c echo.Context) error {
|
|
||||||
cookie, err := c.Cookie("session")
|
|
||||||
if err != nil {
|
|
||||||
// cookie not found
|
|
||||||
return c.Redirect(http.StatusSeeOther, "/")
|
|
||||||
}
|
|
||||||
sessionId := cookie.Value
|
|
||||||
_, err = db.Exec("DELETE FROM sessions where session_id = $1", sessionId)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// tell browser that cookie is expired and thus can be deleted
|
|
||||||
c.SetCookie(&http.Cookie{Name: "session", HttpOnly: true, Path: "/", Value: sessionId, Secure: true, Expires: time.Now()})
|
|
||||||
return c.Redirect(http.StatusSeeOther, "/")
|
|
||||||
}
|
|
188
src/db.go
188
src/db.go
@ -1,188 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
"github.com/namsral/flag"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DbUrl string
|
|
||||||
db *DB
|
|
||||||
)
|
|
||||||
|
|
||||||
type DB struct {
|
|
||||||
*sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error loading .env file")
|
|
||||||
}
|
|
||||||
flag.StringVar(&DbUrl, "DATABASE_URL", "", "Database URL")
|
|
||||||
flag.Parse()
|
|
||||||
validateFlags()
|
|
||||||
db = initDb()
|
|
||||||
_, err = db.Exec("SELECT 1")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func initDb() *DB {
|
|
||||||
db, err := sql.Open("postgres", DbUrl)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return &DB{DB: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateFlags() {
|
|
||||||
if DbUrl == "" {
|
|
||||||
log.Fatal("DATABASE_URL not set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) FetchMarket(marketId int, market *Market) error {
|
|
||||||
if err := db.QueryRow("SELECT id, description FROM markets WHERE id = $1", marketId).Scan(&market.Id, &market.Description); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) FetchShares(marketId int, shares *[]Share) error {
|
|
||||||
rows, err := db.Query("SELECT id, market_id, description FROM shares WHERE market_id = $1 ORDER BY description DESC", marketId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var share Share
|
|
||||||
rows.Scan(&share.Id, &share.MarketId, &share.Description)
|
|
||||||
*shares = append(*shares, share)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetchOrdersWhere struct {
|
|
||||||
MarketId int
|
|
||||||
Pubkey string
|
|
||||||
Confirmed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) FetchOrders(where *FetchOrdersWhere, orders *[]Order) error {
|
|
||||||
query := "" +
|
|
||||||
"SELECT o.id, share_id, o.pubkey, o.side, o.quantity, o.price, o.invoice_id, o.created_at, s.description, s.market_id, i.confirmed_at " +
|
|
||||||
"FROM orders o " +
|
|
||||||
"JOIN invoices i ON o.invoice_id = i.id " +
|
|
||||||
"JOIN shares s ON o.share_id = s.id " +
|
|
||||||
"WHERE "
|
|
||||||
var args []any
|
|
||||||
if where.MarketId > 0 {
|
|
||||||
query += "share_id = ANY(SELECT id FROM shares WHERE market_id = $1) "
|
|
||||||
args = append(args, where.MarketId)
|
|
||||||
} else if where.Pubkey != "" {
|
|
||||||
query += "o.pubkey = $1 "
|
|
||||||
args = append(args, where.Pubkey)
|
|
||||||
}
|
|
||||||
if where.Confirmed {
|
|
||||||
query += "AND i.confirmed_at IS NOT NULL "
|
|
||||||
}
|
|
||||||
query += "AND (i.confirmed_at IS NOT NULL OR i.expires_at > CURRENT_TIMESTAMP) "
|
|
||||||
query += "ORDER BY price DESC"
|
|
||||||
rows, err := db.Query(query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var order Order
|
|
||||||
rows.Scan(&order.Id, &order.ShareId, &order.Pubkey, &order.Side, &order.Quantity, &order.Price, &order.InvoiceId, &order.CreatedAt, &order.Share.Description, &order.Share.MarketId, &order.Invoice.ConfirmedAt)
|
|
||||||
*orders = append(*orders, order)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) CreateOrder(order *Order) error {
|
|
||||||
if _, err := db.Exec(""+
|
|
||||||
"INSERT INTO orders(share_id, pubkey, side, quantity, price, invoice_id) "+
|
|
||||||
"VALUES ($1, $2, $3, $4, $5, $6)",
|
|
||||||
order.ShareId, order.Pubkey, order.Side, order.Quantity, order.Price, order.InvoiceId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) CreateInvoice(invoice *Invoice) error {
|
|
||||||
if err := db.QueryRow(""+
|
|
||||||
"INSERT INTO invoices(pubkey, msats, preimage, hash, bolt11, created_at, expires_at) "+
|
|
||||||
"VALUES($1, $2, $3, $4, $5, $6, $7) "+
|
|
||||||
"RETURNING id",
|
|
||||||
invoice.Pubkey, invoice.Msats, invoice.Preimage, invoice.PaymentHash, invoice.PaymentRequest, invoice.CreatedAt, invoice.ExpiresAt).Scan(&invoice.Id); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetchInvoiceWhere struct {
|
|
||||||
Id string
|
|
||||||
Hash string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) FetchInvoice(where *FetchInvoiceWhere, invoice *Invoice) error {
|
|
||||||
query := "SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since FROM invoices "
|
|
||||||
var args []any
|
|
||||||
if where.Id != "" {
|
|
||||||
query += "WHERE id = $1"
|
|
||||||
args = append(args, where.Id)
|
|
||||||
} else if where.Hash != "" {
|
|
||||||
query += "WHERE hash = $1"
|
|
||||||
args = append(args, where.Hash)
|
|
||||||
}
|
|
||||||
if err := db.QueryRow(query, args...).Scan(&invoice.Id, &invoice.Pubkey, &invoice.Msats, &invoice.Preimage, &invoice.PaymentHash, &invoice.PaymentRequest, &invoice.CreatedAt, &invoice.ExpiresAt, &invoice.ConfirmedAt, &invoice.HeldSince); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetchInvoicesWhere struct {
|
|
||||||
Expired bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) FetchInvoices(where *FetchInvoicesWhere, invoices *[]Invoice) error {
|
|
||||||
query := "" +
|
|
||||||
"SELECT id, msats, msats_received, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since " +
|
|
||||||
"FROM invoices i "
|
|
||||||
if where.Expired {
|
|
||||||
query += "WHERE i.expires_at <= CURRENT_TIMESTAMP"
|
|
||||||
} else {
|
|
||||||
query += "WHERE i.expires_at > CURRENT_TIMESTAMP"
|
|
||||||
}
|
|
||||||
rows, err := db.Query(query)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var inv Invoice
|
|
||||||
rows.Scan(&inv.Id, &inv.Msats, &inv.ReceivedMsats, &inv.Preimage, &inv.PaymentHash, &inv.PaymentRequest, &inv.CreatedAt, &inv.ExpiresAt, &inv.ConfirmedAt, &inv.HeldSince)
|
|
||||||
*invoices = append(*invoices, inv)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) ConfirmInvoice(hash string, confirmedAt time.Time, msatsReceived int) error {
|
|
||||||
if _, err := db.Exec("UPDATE invoices SET confirmed_at = $2, msats_received = $3 WHERE hash = $1", hash, confirmedAt, msatsReceived); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) FetchUser(pubkey string, user *User) error {
|
|
||||||
return db.QueryRow("SELECT pubkey FROM users WHERE pubkey = $1", pubkey).Scan(&user.Pubkey)
|
|
||||||
}
|
|
222
src/lnd.go
222
src/lnd.go
@ -1,222 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"database/sql"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"github.com/lightninglabs/lndclient"
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
|
||||||
"github.com/namsral/flag"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
LndCert string
|
|
||||||
LndMacaroonDir string
|
|
||||||
LndHost string
|
|
||||||
lnd *LndClient
|
|
||||||
lndEnabled bool
|
|
||||||
)
|
|
||||||
|
|
||||||
type LndClient struct {
|
|
||||||
lndclient.GrpcLndServices
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error loading .env file")
|
|
||||||
}
|
|
||||||
flag.StringVar(&LndCert, "LND_CERT", "", "Path to LND TLS certificate")
|
|
||||||
flag.StringVar(&LndMacaroonDir, "LND_MACAROON_DIR", "", "LND macaroon directory")
|
|
||||||
flag.StringVar(&LndHost, "LND_HOST", "localhost:10001", "LND gRPC server address")
|
|
||||||
flag.Parse()
|
|
||||||
lndEnabled = false
|
|
||||||
rpcLndServices, err := lndclient.NewLndServices(&lndclient.LndServicesConfig{
|
|
||||||
LndAddress: LndHost,
|
|
||||||
MacaroonDir: LndMacaroonDir,
|
|
||||||
TLSPath: LndCert,
|
|
||||||
Network: lndclient.NetworkRegtest,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lnd = &LndClient{GrpcLndServices: *rpcLndServices}
|
|
||||||
ver := lnd.Version
|
|
||||||
log.Printf("Connected to %s running LND v%s", LndHost, ver.Version)
|
|
||||||
lndEnabled = true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func lndGuard(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
if lndEnabled {
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
return serveError(c, 405)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lnd *LndClient) GenerateNewPreimage() (lntypes.Preimage, error) {
|
|
||||||
randomBytes := make([]byte, 32)
|
|
||||||
_, err := io.ReadFull(rand.Reader, randomBytes)
|
|
||||||
if err != nil {
|
|
||||||
return lntypes.Preimage{}, err
|
|
||||||
}
|
|
||||||
preimage, err := lntypes.MakePreimage(randomBytes)
|
|
||||||
if err != nil {
|
|
||||||
return lntypes.Preimage{}, err
|
|
||||||
}
|
|
||||||
return preimage, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lnd *LndClient) CreateInvoice(pubkey string, msats int) (*Invoice, error) {
|
|
||||||
expiry := time.Hour
|
|
||||||
preimage, err := lnd.GenerateNewPreimage()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hash := preimage.Hash()
|
|
||||||
paymentRequest, err := lnd.Invoices.AddHoldInvoice(context.TODO(), &invoicesrpc.AddInvoiceData{
|
|
||||||
Hash: &hash,
|
|
||||||
Value: lnwire.MilliSatoshi(msats),
|
|
||||||
Expiry: int64(expiry / time.Millisecond),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lnInvoice, err := lnd.Client.LookupInvoice(context.TODO(), hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dbInvoice := Invoice{
|
|
||||||
Session: Session{pubkey},
|
|
||||||
Msats: msats,
|
|
||||||
Preimage: preimage.String(),
|
|
||||||
PaymentRequest: paymentRequest,
|
|
||||||
PaymentHash: hash.String(),
|
|
||||||
CreatedAt: lnInvoice.CreationDate,
|
|
||||||
ExpiresAt: lnInvoice.CreationDate.Add(expiry),
|
|
||||||
}
|
|
||||||
if err := db.CreateInvoice(&dbInvoice); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &dbInvoice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lnd *LndClient) CheckInvoice(hash lntypes.Hash) {
|
|
||||||
if !lndEnabled {
|
|
||||||
log.Printf("LND disabled, skipping checking invoice: hash=%s", hash)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var invoice Invoice
|
|
||||||
if err := db.FetchInvoice(&FetchInvoiceWhere{Hash: hash.String()}, &invoice); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loopPause := 5 * time.Second
|
|
||||||
handleLoopError := func(err error) {
|
|
||||||
log.Println(err)
|
|
||||||
time.Sleep(loopPause)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
log.Printf("lookup invoice: hash=%s", hash)
|
|
||||||
lnInvoice, err := lnd.Client.LookupInvoice(context.TODO(), hash)
|
|
||||||
if err != nil {
|
|
||||||
handleLoopError(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if time.Now().After(invoice.ExpiresAt) {
|
|
||||||
if err := lnd.Invoices.CancelInvoice(context.TODO(), hash); err != nil {
|
|
||||||
handleLoopError(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Printf("invoice expired: hash=%s", hash)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if lnInvoice.AmountPaid > 0 {
|
|
||||||
preimage, err := lntypes.MakePreimageFromStr(invoice.Preimage)
|
|
||||||
if err != nil {
|
|
||||||
handleLoopError(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO settle invoice after matching order was found
|
|
||||||
if err := lnd.Invoices.SettleInvoice(context.TODO(), preimage); err != nil {
|
|
||||||
handleLoopError(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := db.ConfirmInvoice(hash.String(), time.Now(), int(lnInvoice.AmountPaid)); err != nil {
|
|
||||||
handleLoopError(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Printf("invoice confirmed: hash=%s", hash)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(loopPause)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func invoice(c echo.Context) error {
|
|
||||||
invoiceId := c.Param("id")
|
|
||||||
var invoice Invoice
|
|
||||||
if err := db.FetchInvoice(&FetchInvoiceWhere{Id: invoiceId}, &invoice); err == sql.ErrNoRows {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
session := c.Get("session").(Session)
|
|
||||||
if invoice.Pubkey != session.Pubkey {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
|
||||||
}
|
|
||||||
hash, err := lntypes.MakeHashFromStr(invoice.PaymentHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go lnd.CheckInvoice(hash)
|
|
||||||
qr, err := ToQR(invoice.PaymentRequest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
status := ""
|
|
||||||
if invoice.ConfirmedAt.Valid {
|
|
||||||
status = "Paid"
|
|
||||||
} else if time.Now().After(invoice.ExpiresAt) {
|
|
||||||
status = "Expired"
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"session": c.Get("session"),
|
|
||||||
"Invoice": invoice,
|
|
||||||
"Status": status,
|
|
||||||
"lnurl": invoice.PaymentRequest,
|
|
||||||
"qr": qr,
|
|
||||||
}
|
|
||||||
return c.Render(http.StatusOK, "invoice.html", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func invoiceStatus(c echo.Context) error {
|
|
||||||
invoiceId := c.Param("id")
|
|
||||||
var invoice Invoice
|
|
||||||
if err := db.FetchInvoice(&FetchInvoiceWhere{Id: invoiceId}, &invoice); err == sql.ErrNoRows {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
session := c.Get("session").(Session)
|
|
||||||
if invoice.Pubkey != session.Pubkey {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
|
||||||
}
|
|
||||||
invoice.Preimage = ""
|
|
||||||
return c.JSON(http.StatusOK, invoice)
|
|
||||||
}
|
|
155
src/market.go
155
src/market.go
@ -1,155 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
|
||||||
"gopkg.in/guregu/null.v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Market struct {
|
|
||||||
Id int
|
|
||||||
Description string
|
|
||||||
Active bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Share struct {
|
|
||||||
Id string
|
|
||||||
MarketId int
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Order struct {
|
|
||||||
Session
|
|
||||||
Share
|
|
||||||
Market
|
|
||||||
Invoice
|
|
||||||
Id string
|
|
||||||
ShareId string `form:"share_id"`
|
|
||||||
Side string `form:"side"`
|
|
||||||
Price int `form:"price"`
|
|
||||||
Quantity int `form:"quantity"`
|
|
||||||
InvoiceId string
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Invoice struct {
|
|
||||||
Session
|
|
||||||
Id string
|
|
||||||
Msats int
|
|
||||||
ReceivedMsats int
|
|
||||||
Preimage string
|
|
||||||
PaymentRequest string
|
|
||||||
PaymentHash string
|
|
||||||
CreatedAt time.Time
|
|
||||||
ExpiresAt time.Time
|
|
||||||
ConfirmedAt null.Time
|
|
||||||
HeldSince null.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func costFunction(b float64, q1 float64, q2 float64) float64 {
|
|
||||||
// reference: http://blog.oddhead.com/2006/10/30/implementing-hansons-market-maker/
|
|
||||||
return b * math.Log(math.Pow(math.E, q1/b)+math.Pow(math.E, q2/b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// logarithmic market scoring rule (LMSR) market maker from Robin Hanson:
|
|
||||||
// https://mason.gmu.edu/~rhanson/mktscore.pdf1
|
|
||||||
func BinaryLMSR(invariant int, funding int, q1 int, q2 int, dq1 int) float64 {
|
|
||||||
b := float64(funding)
|
|
||||||
fq1 := float64(q1)
|
|
||||||
fq2 := float64(q2)
|
|
||||||
fdq1 := float64(dq1)
|
|
||||||
return costFunction(b, fq1+fdq1, fq2) - costFunction(b, fq1, fq2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func order(c echo.Context) error {
|
|
||||||
marketId := c.Param("id")
|
|
||||||
// (TBD) Step 0: If SELL order, check share balance of user
|
|
||||||
// (TBD) Step 1: respond with HODL invoice
|
|
||||||
o := new(Order)
|
|
||||||
if err := c.Bind(o); err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "bad request")
|
|
||||||
}
|
|
||||||
session := c.Get("session").(Session)
|
|
||||||
o.Pubkey = session.Pubkey
|
|
||||||
amount := o.Quantity * o.Price
|
|
||||||
invoice, err := lnd.CreateInvoice(session.Pubkey, amount*1000)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.InvoiceId = invoice.Id
|
|
||||||
if err := db.CreateOrder(o); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
qr, err := ToQR(invoice.PaymentRequest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hash, err := lntypes.MakeHashFromStr(invoice.PaymentHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go lnd.CheckInvoice(hash)
|
|
||||||
data := map[string]any{
|
|
||||||
"session": c.Get("session"),
|
|
||||||
"ENV": ENV,
|
|
||||||
"lnurl": invoice.PaymentRequest,
|
|
||||||
"qr": qr,
|
|
||||||
"Invoice": invoice,
|
|
||||||
"RedirectURL": fmt.Sprintf("https://%s/market/%s", PUBLIC_URL, marketId),
|
|
||||||
}
|
|
||||||
return c.Render(http.StatusPaymentRequired, "invoice.html", data)
|
|
||||||
// Step 2: After payment, confirm order if no matching order was found
|
|
||||||
// if err := db.CreateOrder(o); err != nil {
|
|
||||||
// if strings.Contains(err.Error(), "violates check constraint") {
|
|
||||||
// return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
|
||||||
// }
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// (TBD) Step 3:
|
|
||||||
// Settle hodl invoice when matching order was found
|
|
||||||
// else cancel hodl invoice if expired
|
|
||||||
// ...
|
|
||||||
// return market(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func market(c echo.Context) error {
|
|
||||||
marketId, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
|
||||||
}
|
|
||||||
|
|
||||||
var market Market
|
|
||||||
if err = db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var shares []Share
|
|
||||||
if err = db.FetchShares(market.Id, &shares); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var orders []Order
|
|
||||||
if err = db.FetchOrders(&FetchOrdersWhere{MarketId: market.Id, Confirmed: true}, &orders); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"session": c.Get("session"),
|
|
||||||
"ENV": ENV,
|
|
||||||
"Id": market.Id,
|
|
||||||
"Description": market.Description,
|
|
||||||
// shares are sorted by description in descending order
|
|
||||||
// that's how we know that YES must be the first share
|
|
||||||
"YesShare": shares[0],
|
|
||||||
"NoShare": shares[1],
|
|
||||||
"Orders": orders,
|
|
||||||
}
|
|
||||||
return c.Render(http.StatusOK, "market.html", data)
|
|
||||||
}
|
|
108
src/router.go
108
src/router.go
@ -1,108 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Template struct {
|
|
||||||
templates *template.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
func add(arg1 int, arg2 int) int {
|
|
||||||
return arg1 + arg2
|
|
||||||
}
|
|
||||||
|
|
||||||
func sub(arg1 int, arg2 int) int {
|
|
||||||
return arg1 - arg2
|
|
||||||
}
|
|
||||||
|
|
||||||
func div(arg1 int, arg2 int) int {
|
|
||||||
return arg1 / arg2
|
|
||||||
}
|
|
||||||
|
|
||||||
func substr(s string, start, length int) string {
|
|
||||||
if start < 0 || start >= len(s) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
end := start + length
|
|
||||||
if end > len(s) {
|
|
||||||
end = len(s)
|
|
||||||
}
|
|
||||||
return s[start:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
FuncMap template.FuncMap = template.FuncMap{
|
|
||||||
"add": add,
|
|
||||||
"sub": sub,
|
|
||||||
"div": div,
|
|
||||||
"substr": substr,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
|
||||||
return t.templates.ExecuteTemplate(w, name, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func index(c echo.Context) error {
|
|
||||||
rows, err := db.Query("SELECT id, description, active FROM markets WHERE active = true")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var markets []Market
|
|
||||||
for rows.Next() {
|
|
||||||
var market Market
|
|
||||||
rows.Scan(&market.Id, &market.Description, &market.Active)
|
|
||||||
markets = append(markets, market)
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"session": c.Get("session"),
|
|
||||||
"ENV": ENV,
|
|
||||||
"markets": markets,
|
|
||||||
"VERSION": VERSION,
|
|
||||||
"COMMIT_LONG_SHA": COMMIT_LONG_SHA}
|
|
||||||
return c.Render(http.StatusOK, "index.html", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveError(c echo.Context, code int) error {
|
|
||||||
f, err := os.Open(fmt.Sprintf("public/%d.html", code))
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.Stream(code, "text/html", f)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpErrorHandler(err error, c echo.Context) {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
code := http.StatusInternalServerError
|
|
||||||
httpError, ok := err.(*echo.HTTPError)
|
|
||||||
if ok {
|
|
||||||
code = httpError.Code
|
|
||||||
}
|
|
||||||
filePath := fmt.Sprintf("public/%d.html", code)
|
|
||||||
f, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
serveError(c, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = c.Stream(code, "text/html", f)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
serveError(c, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"github.com/labstack/echo/v4/middleware"
|
|
||||||
"github.com/namsral/flag"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
e *echo.Echo
|
|
||||||
t *Template
|
|
||||||
COMMIT_LONG_SHA string
|
|
||||||
COMMIT_SHORT_SHA string
|
|
||||||
VERSION string
|
|
||||||
PORT int
|
|
||||||
PUBLIC_URL string
|
|
||||||
ENV string
|
|
||||||
)
|
|
||||||
|
|
||||||
func execCmd(name string, args ...string) string {
|
|
||||||
cmd := exec.Command(name, args...)
|
|
||||||
stdout, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(stdout))
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error loading .env file")
|
|
||||||
}
|
|
||||||
flag.StringVar(&PUBLIC_URL, "PUBLIC_URL", "delphi.market", "Public URL of website")
|
|
||||||
flag.IntVar(&PORT, "PORT", 4321, "Server port")
|
|
||||||
flag.StringVar(&ENV, "ENV", "development", "Specify for which environment files should be built")
|
|
||||||
flag.Parse()
|
|
||||||
e = echo.New()
|
|
||||||
t = &Template{
|
|
||||||
templates: template.Must(template.New("").Funcs(FuncMap).ParseGlob("pages/**.html")),
|
|
||||||
}
|
|
||||||
COMMIT_LONG_SHA = execCmd("git", "rev-parse", "HEAD")
|
|
||||||
COMMIT_SHORT_SHA = execCmd("git", "rev-parse", "--short", "HEAD")
|
|
||||||
VERSION = fmt.Sprintf("v0.0.0+%s", COMMIT_SHORT_SHA)
|
|
||||||
log.Printf("Running commit %s", COMMIT_SHORT_SHA)
|
|
||||||
log.Printf("Public URL: %s", PUBLIC_URL)
|
|
||||||
log.Printf("Environment: %s", ENV)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
e.Static("/", "public")
|
|
||||||
e.Renderer = t
|
|
||||||
e.GET("/", index)
|
|
||||||
e.GET("/login", login)
|
|
||||||
e.GET("/api/login", verifyLogin)
|
|
||||||
e.GET("/api/session", checkSession)
|
|
||||||
e.POST("/logout", logout)
|
|
||||||
e.GET("/user", sessionGuard(user))
|
|
||||||
e.GET("/market/:id", sessionGuard(market))
|
|
||||||
e.POST("/market/:id/order", sessionGuard(lndGuard(order)))
|
|
||||||
e.GET("/invoice/:id", sessionGuard(lndGuard(invoice)))
|
|
||||||
e.GET("/api/invoice/:id", sessionGuard(lndGuard(invoiceStatus)))
|
|
||||||
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
|
||||||
Format: "${time_custom} ${method} ${uri} ${status}\n",
|
|
||||||
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
|
||||||
}))
|
|
||||||
e.Use(sessionHandler)
|
|
||||||
e.HTTPErrorHandler = httpErrorHandler
|
|
||||||
if err := RunJobs(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err := e.Start(fmt.Sprintf("%s:%d", "127.0.0.1", PORT))
|
|
||||||
if err != http.ErrServerClosed {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
29
src/user.go
29
src/user.go
@ -1,29 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func user(c echo.Context) error {
|
|
||||||
session := c.Get("session").(Session)
|
|
||||||
u := User{}
|
|
||||||
if err := db.FetchUser(session.Pubkey, &u); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var orders []Order
|
|
||||||
if err := db.FetchOrders(&FetchOrdersWhere{Pubkey: session.Pubkey}, &orders); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"session": c.Get("session"),
|
|
||||||
"user": u,
|
|
||||||
"Orders": orders,
|
|
||||||
}
|
|
||||||
return c.Render(http.StatusOK, "user.html", data)
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RunJobs() error {
|
|
||||||
var invoices []Invoice
|
|
||||||
if err := db.FetchInvoices(&FetchInvoicesWhere{Expired: false}, &invoices); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, inv := range invoices {
|
|
||||||
hash, err := lntypes.MakeHashFromStr(inv.PaymentHash)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go lnd.CheckInvoice(hash)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user