From ee3e5e82e49467c6e9ccda9782a6041a4c646c34 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 12 Jul 2024 04:59:32 +0200 Subject: [PATCH] Remove old code * removed code that will not be used * removed code that will most likely be rewritten --- Makefile | 2 +- db/invoice.go | 106 ------ db/lnauth.go | 18 - db/market.go | 353 -------------------- db/session.go | 17 - db/types.go | 80 ----- db/user.go | 17 - db/withdrawal.go | 13 - hotreload.sh | 2 +- lnd/invoice.go | 163 --------- lnd/types.go | 1 - lnd/withdrawal.go | 14 +- main.go | 2 +- pages/index.html | 54 --- pages/invoice.html | 113 ------- pages/login.html | 77 ----- pages/market.html | 82 ----- pages/user.html | 106 ------ public/js/order.js | 16 - public/market.css | 47 --- server/router/handler/index.go | 16 - server/router/handler/invoice.go | 110 ------- server/router/handler/lnauth_test.go | 57 ++++ server/router/handler/login.go | 67 ---- server/router/handler/login_test.go | 123 ------- server/router/handler/logout.go | 31 -- server/router/handler/logout_test.go | 59 +--- server/router/handler/market.go | 475 --------------------------- server/router/handler/session.go | 30 -- server/router/handler/user.go | 30 -- server/router/handler/withdrawal.go | 79 ----- server/router/middleware/session.go | 42 +-- server/router/router.go | 3 +- 33 files changed, 92 insertions(+), 2313 deletions(-) delete mode 100644 db/invoice.go delete mode 100644 db/lnauth.go delete mode 100644 db/market.go delete mode 100644 db/session.go delete mode 100644 db/types.go delete mode 100644 db/user.go delete mode 100644 db/withdrawal.go delete mode 100644 lnd/invoice.go delete mode 100644 lnd/types.go delete mode 100644 pages/index.html delete mode 100644 pages/invoice.html delete mode 100644 pages/login.html delete mode 100644 pages/market.html delete mode 100644 pages/user.html delete mode 100644 public/js/order.js delete mode 100644 public/market.css delete mode 100644 server/router/handler/invoice.go create mode 100644 server/router/handler/lnauth_test.go delete mode 100644 server/router/handler/login_test.go delete mode 100644 server/router/handler/logout.go delete mode 100644 server/router/handler/market.go delete mode 100644 server/router/handler/session.go delete mode 100644 server/router/handler/user.go delete mode 100644 server/router/handler/withdrawal.go diff --git a/Makefile b/Makefile index b400f44..76c5d7f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: build run test -SOURCE := $(shell find db env lib lnd pages public server -type f) main.go +SOURCE := $(shell find db env lib lnd public server -type f) main.go build: delphi.market diff --git a/db/invoice.go b/db/invoice.go deleted file mode 100644 index fda5f38..0000000 --- a/db/invoice.go +++ /dev/null @@ -1,106 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "time" -) - -func (db *DB) CreateInvoice(tx *sql.Tx, ctx context.Context, invoice *Invoice) error { - if err := tx.QueryRowContext(ctx, ""+ - "INSERT INTO invoices(pubkey, msats, preimage, hash, bolt11, created_at, expires_at, description) "+ - "VALUES($1, $2, $3, $4, $5, $6, $7, $8) "+ - "RETURNING id", - invoice.Pubkey, invoice.Msats, invoice.Preimage, invoice.Hash, invoice.PaymentRequest, invoice.CreatedAt, invoice.ExpiresAt, invoice.Description).Scan(&invoice.Id); err != nil { - return err - } - return nil -} - -type FetchInvoiceWhere struct { - Id string - Hash string -} - -func (db *DB) FetchInvoice(where *FetchInvoiceWhere, invoice *Invoice) error { - var ( - query = "SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since, COALESCE(description, '') 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, &invoice.Description); err != nil { - return err - } - return nil -} - -type FetchInvoicesWhere struct { - Unconfirmed bool -} - -func (db *DB) FetchInvoices(where *FetchInvoicesWhere, invoices *[]Invoice) error { - var ( - rows *sql.Rows - invoice Invoice - err error - ) - var ( - query = "SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since, COALESCE(description, '') FROM invoices " - ) - if where.Unconfirmed { - query += "WHERE confirmed_at IS NULL" - } - if rows, err = db.Query(query); err != nil { - return err - } - defer rows.Close() - for rows.Next() { - rows.Scan( - &invoice.Id, &invoice.Pubkey, &invoice.Msats, &invoice.Preimage, &invoice.Hash, - &invoice.PaymentRequest, &invoice.CreatedAt, &invoice.ExpiresAt, &invoice.ConfirmedAt, &invoice.HeldSince, &invoice.Description) - *invoices = append(*invoices, invoice) - } - return nil -} - -func (db *DB) FetchUserInvoices(pubkey string, invoices *[]Invoice) error { - var ( - rows *sql.Rows - invoice Invoice - err error - ) - var ( - query = "" + - "SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since, COALESCE(description, ''), " + - "CASE WHEN confirmed_at IS NOT NULL THEN 'PAID' WHEN expires_at < CURRENT_TIMESTAMP THEN 'EXPIRED' ELSE 'PENDING' END AS status " + - "FROM invoices " + - "WHERE pubkey = $1 " + - "ORDER BY created_at DESC" - ) - if rows, err = db.Query(query, pubkey); err != nil { - return err - } - defer rows.Close() - for rows.Next() { - rows.Scan( - &invoice.Id, &invoice.Pubkey, &invoice.Msats, &invoice.Preimage, &invoice.Hash, - &invoice.PaymentRequest, &invoice.CreatedAt, &invoice.ExpiresAt, &invoice.ConfirmedAt, &invoice.HeldSince, &invoice.Description, &invoice.Status) - *invoices = append(*invoices, invoice) - } - return nil -} - -func (db *DB) ConfirmInvoice(tx *sql.Tx, c context.Context, hash string, confirmedAt time.Time, msatsReceived int) error { - if _, err := tx.ExecContext(c, "UPDATE invoices SET confirmed_at = $2, msats_received = $3 WHERE hash = $1", hash, confirmedAt, msatsReceived); err != nil { - return err - } - return nil -} diff --git a/db/lnauth.go b/db/lnauth.go deleted file mode 100644 index a3ff863..0000000 --- a/db/lnauth.go +++ /dev/null @@ -1,18 +0,0 @@ -package db - -func (db *DB) 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 (db *DB) FetchSessionId(k1 string, sessionId *string) error { - err := db.QueryRow("SELECT session_id FROM lnauth WHERE k1 = $1", k1).Scan(sessionId) - return err -} - -func (db *DB) DeleteLNAuth(lnAuth *LNAuth) error { - _, err := db.Exec("DELETE FROM lnauth WHERE k1 = $1", lnAuth.K1) - return err -} diff --git a/db/market.go b/db/market.go deleted file mode 100644 index f1494e1..0000000 --- a/db/market.go +++ /dev/null @@ -1,353 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "log" - "time" -) - -type FetchOrdersWhere struct { - MarketId int - Pubkey string - Confirmed bool -} - -func (db *DB) CreateMarket(tx *sql.Tx, ctx context.Context, market *Market) error { - if err := tx.QueryRowContext(ctx, ""+ - "INSERT INTO markets(description, end_date, pubkey, invoice_id) "+ - "VALUES($1, $2, $3, $4) "+ - "RETURNING id", market.Description, market.EndDate, market.Pubkey, market.InvoiceId).Scan(&market.Id); err != nil { - return err - } - // For now, we only support binary markets. - if _, err := tx.Exec("INSERT INTO shares(market_id, description) VALUES ($1, 'YES'), ($1, 'NO')", market.Id); err != nil { - return err - } - return nil -} - -func (db *DB) FetchMarket(marketId int, market *Market) error { - if err := db.QueryRow("SELECT id, description, end_date, pubkey, settled_at FROM markets WHERE id = $1", marketId).Scan(&market.Id, &market.Description, &market.EndDate, &market.Pubkey, &market.SettledAt); err != nil { - return err - } - return nil -} - -func (db *DB) FetchActiveMarkets(markets *[]Market) error { - var ( - rows *sql.Rows - market Market - err error - ) - if rows, err = db.Query("" + - "SELECT m.id, m.description, m.end_date FROM markets m " + - "JOIN invoices i ON i.id = m.invoice_id WHERE i.confirmed_at IS NOT NULL"); err != nil { - return err - } - defer rows.Close() - for rows.Next() { - rows.Scan(&market.Id, &market.Description, &market.EndDate) - *markets = append(*markets, market) - } - return nil -} - -func (db *DB) FetchShares(marketId int, shares *[]Share) error { - rows, err := db.Query("SELECT id, market_id, description, win 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, &share.Win) - *shares = append(*shares, share) - } - return nil -} - -func (db *DB) FetchShare(tx *sql.Tx, ctx context.Context, shareId string, share *Share) error { - return tx.QueryRowContext(ctx, "SELECT id, market_id, description FROM shares WHERE id = $1", shareId).Scan(&share.Id, &share.MarketId, &share.Description) -} - -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, o.deleted_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 o.deleted_at IS NULL " - var args []any - if where.MarketId > 0 { - query += "AND share_id = ANY(SELECT id FROM shares WHERE market_id = $1) " - args = append(args, where.MarketId) - } else if where.Pubkey != "" { - query += "AND o.pubkey = $1 " - args = append(args, where.Pubkey) - } - if where.Confirmed { - query += "AND o.order_id 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.DeletedAt, &order.Share.Description, &order.Share.MarketId, &order.Invoice.ConfirmedAt) - *orders = append(*orders, order) - } - return nil -} - -func (db *DB) CreateOrder(tx *sql.Tx, ctx context.Context, order *Order) error { - if _, err := tx.ExecContext(ctx, ""+ - "INSERT INTO orders(share_id, pubkey, side, quantity, price, invoice_id) "+ - "VALUES ($1, $2, $3, $4, $5, CASE WHEN $6 = '' THEN NULL ELSE $6::UUID END)", - order.ShareId, order.Pubkey, order.Side, order.Quantity, order.Price, order.InvoiceId.String); err != nil { - return err - } - return nil -} - -func (db *DB) FetchOrder(tx *sql.Tx, ctx context.Context, orderId string, order *Order) error { - query := "" + - "SELECT o.id, o.share_id, o.pubkey, o.side, o.quantity, o.price, o.created_at, o.deleted_at, o.order_id, s.description, s.market_id, i.confirmed_at, o.invoice_id, COALESCE(i.msats_received, 0) " + - "FROM orders o " + - "LEFT JOIN invoices i ON o.invoice_id = i.id " + - "JOIN shares s ON o.share_id = s.id " + - "WHERE o.id = $1" - return tx.QueryRowContext(ctx, query, orderId).Scan(&order.Id, &order.ShareId, &order.Pubkey, &order.Side, &order.Quantity, &order.Price, &order.CreatedAt, &order.DeletedAt, &order.OrderId, &order.Share.Description, &order.MarketId, &order.Invoice.ConfirmedAt, &order.InvoiceId, &order.Invoice.MsatsReceived) -} - -func (db *DB) FetchUserOrders(pubkey string, orders *[]Order) error { - query := "" + - "SELECT o.id, share_id, o.pubkey, o.side, o.quantity, o.price, o.created_at, o.deleted_at, s.description, s.market_id, i.confirmed_at, " + - "CASE WHEN o.order_id IS NOT NULL THEN 'EXECUTED' WHEN o.deleted_at IS NOT NULL THEN 'CANCELED' ELSE 'PENDING' END AS status, o.order_id, o.invoice_id " + - "FROM orders o " + - "LEFT JOIN invoices i ON o.invoice_id = i.id " + - "JOIN shares s ON o.share_id = s.id " + - "WHERE o.pubkey = $1 AND ( (o.side = 'BUY' AND i.confirmed_at IS NOT NULL) OR o.side = 'SELL' ) " + - "ORDER BY o.created_at DESC" - rows, err := db.Query(query, pubkey) - 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.CreatedAt, &order.DeletedAt, &order.ShareDescription, &order.Share.MarketId, &order.Invoice.ConfirmedAt, &order.Status, &order.OrderId, &order.InvoiceId) - *orders = append(*orders, order) - } - return nil -} - -func (db *DB) FetchMarketOrders(marketId int64, orders *[]Order) error { - query := "" + - "SELECT o.id, share_id, o.pubkey, o.side, o.quantity, o.price, o.created_at, s.description, s.market_id, " + - "CASE WHEN o.order_id IS NOT NULL THEN 'EXECUTED' ELSE 'PENDING' END AS status, o.order_id, o.invoice_id " + - "FROM orders o " + - "JOIN shares s ON o.share_id = s.id " + - "LEFT JOIN invoices i ON o.invoice_id = i.id " + - "WHERE s.market_id = $1 AND o.deleted_at IS NULL AND ( (o.side = 'BUY' AND i.confirmed_at IS NOT NULL) OR o.side = 'SELL' ) " + - "ORDER BY o.created_at DESC" - rows, err := db.Query(query, marketId) - 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.CreatedAt, &order.ShareDescription, &order.Share.MarketId, &order.Status, &order.OrderId, &order.InvoiceId) - *orders = append(*orders, order) - } - return nil -} - -func (db *DB) RunMatchmaking(orderId string) { - var ( - ctx context.Context - cancel context.CancelFunc - tx *sql.Tx - o1 Order - o2 Order - err error - ) - ctx, cancel = context.WithTimeout(context.TODO(), 10*time.Second) - defer cancel() - if tx, err = db.BeginTx(ctx, nil); err != nil { - log.Println(err) - return - } - // TODO: assert that order was confirmed - if err = db.FetchOrder(tx, ctx, orderId, &o1); err != nil { - log.Println(err) - tx.Rollback() - return - } - if err = db.FindOrderMatches(tx, ctx, &o1, &o2); err != nil { - log.Println(err) - tx.Rollback() - return - } - if o2.OrderId.Valid { - log.Printf("assertion failed: order %s matched order %s but order_id already set to %s\n", o1.Id, o2.Id, o2.OrderId.String) - tx.Rollback() - return - } - if o1.Id == o2.Id { - log.Printf("assertion failed: order %s matched itself", o1.Id) - tx.Rollback() - return - } - if err = db.MatchOrders(tx, ctx, &o1, &o2); err != nil { - log.Println(err) - tx.Rollback() - return - } - - tx.Commit() -} - -func (db *DB) FindOrderMatches(tx *sql.Tx, ctx context.Context, o1 *Order, o2 *Order) error { - query := "" + - "SELECT o.id, o.order_id, o.side, o.quantity, o.price, o.pubkey FROM orders o " + - "JOIN shares s ON s.id = o.share_id " + - "LEFT JOIN invoices i ON i.id = o.invoice_id " + - // only match orders which are not soft deleted - "WHERE o.deleted_at IS NULL " + - // only match orders which are not already settled - "AND o.order_id IS NULL " + - // only match orders from other users - "AND o.pubkey <> $1 " + - // orders must always be for same market and have same quantity - "AND o.quantity = $2 AND s.market_id = $3 " + - // BUY orders must have been confirmed by paying the invoice - "AND CASE WHEN o.side = 'BUY' THEN i.confirmed_at IS NOT NULL ELSE 1=1 END " + - "AND (" + - // -- BUY orders match if they are for different shares and the sum of their prices equal 100 - // -- example: BUY 5 YES @ 60 <> BUY 5 NO @ 40 - " ( $5 = 'BUY' AND o.side = 'BUY' AND o.price = (100-$6) AND o.share_id <> $4 ) " + - // -- BUY orders match SELL orders if they are for the same share and have same price - // -- example: BUY 5 YES @ 60 <> SELL 5 YES @ 60 - " OR ( $5 = 'BUY' AND o.side = 'SELL' AND o.price = $6 AND o.share_id = $4 ) " + - // -- SELL orders match BUY orders if they are for the same share and have same price - // -- example: SELL 5 YES @ 60 <> BUY 5 YES @ 60 - " OR ( $5 = 'SELL' AND o.side = 'BUY' AND o.price = $6 AND o.share_id = $4 ) " + - ") " + - // match oldest order first - "ORDER BY o.created_at ASC LIMIT 1" - return tx.QueryRowContext(ctx, query, o1.Pubkey, o1.Quantity, o1.Share.MarketId, o1.ShareId, o1.Side, o1.Price).Scan(&o2.Id, &o2.OrderId, &o2.Side, &o2.Quantity, &o2.Price, &o2.Pubkey) -} - -func (db *DB) MatchOrders(tx *sql.Tx, ctx context.Context, o1 *Order, o2 *Order) error { - var err error - if _, err = tx.ExecContext(ctx, "UPDATE orders SET order_id = $1 WHERE id = $2", o1.Id, o2.Id); err != nil { - tx.Rollback() - return err - } - if _, err = tx.ExecContext(ctx, "UPDATE orders SET order_id = $1 WHERE id = $2", o2.Id, o1.Id); err != nil { - tx.Rollback() - return err - } - if o1.Side == "SELL" { - if _, err = tx.ExecContext(ctx, "UPDATE users SET msats = msats + $1 WHERE pubkey = $2", (o1.Price*o1.Quantity)*1000, o1.Pubkey); err != nil { - tx.Rollback() - return err - } - } - if o2.Side == "SELL" { - if _, err = tx.ExecContext(ctx, "UPDATE users SET msats = msats + $1 WHERE pubkey = $2", (o2.Price*o2.Quantity)*1000, o2.Pubkey); err != nil { - tx.Rollback() - return err - } - } - log.Printf("Matched orders: %s <> %s\n", o1.Id, o2.Id) - return nil -} - -// [ -// -// { "x": , "y": { : , ... } }, -// -// ] -type MarketStat struct { - X time.Time `json:"x"` - Y map[string]int `json:"y"` -} -type MarketStats = []MarketStat - -func (db *DB) FetchMarketStats(marketId int64, stats *MarketStats) error { - query := "" + - "SELECT " + - "s.description, " + - "GREATEST(i.confirmed_at, i2.confirmed_at) AS confirmed_at, " + - "SUM(o.price * o.quantity) OVER (PARTITION BY o.share_id ORDER BY o.created_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS score " + - "FROM orders o " + - "JOIN orders o2 ON o2.id = o.order_id " + - "JOIN shares s ON s.id = o.share_id " + - "JOIN invoices i ON i.id = o.invoice_id " + - "JOIN invoices i2 ON i2.id = o2.invoice_id " + - "WHERE s.market_id = $1 AND i.confirmed_at IS NOT NULL AND o.order_id IS NOT NULL ORDER BY i.confirmed_at ASC" - rows, err := db.Query(query, marketId) - if err != nil { - return err - } - - defer rows.Close() - for rows.Next() { - var stat MarketStat - var ( - timestamp time.Time - description string - score int - ) - rows.Scan(&description, ×tamp, &score) - stat.X = timestamp - stat.Y = map[string]int{ - description: score, - } - *stats = append(*stats, stat) - } - return nil -} - -func (db *DB) FetchUserBalance(tx *sql.Tx, ctx context.Context, marketId int, pubkey string, balance *map[string]any) error { - query := "" + - "SELECT s.description, " + - "SUM(CASE WHEN o.side = 'BUY' THEN o.quantity ELSE -o.quantity END) " + - "FROM orders o " + - "LEFT JOIN invoices i ON i.id = o.invoice_id " + - "JOIN shares s ON s.id = o.share_id " + - "WHERE o.pubkey = $1 AND s.market_id = $2 " + - // ignore canceled orders - "AND o.deleted_at IS NULL " + - "AND ( " + - // shares from BUY orders are received if they were paid (necessary precondition for matchmaking) and found a matching order - " (o.side = 'BUY' AND i.confirmed_at IS NOT NULL AND o.order_id IS NOT NULL) " + - // shares from SELL orders are deducted immediately to prevent double-spends - " OR o.side = 'SELL' " + - ") " + - "GROUP BY o.pubkey, s.description" - rows, err := tx.QueryContext(ctx, query, pubkey, marketId) - if err != nil { - return err - } - - defer rows.Close() - for rows.Next() { - var ( - sdesc string - val int - ) - if err = rows.Scan(&sdesc, &val); err != nil { - return err - } - (*balance)[sdesc] = val - } - return nil -} diff --git a/db/session.go b/db/session.go deleted file mode 100644 index d5530ee..0000000 --- a/db/session.go +++ /dev/null @@ -1,17 +0,0 @@ -package db - -func (db *DB) CreateSession(s *Session) error { - _, err := db.Exec("INSERT INTO sessions(pubkey, session_id) VALUES($1, $2)", s.Pubkey, s.SessionId) - return err -} - -func (db *DB) FetchSession(s *Session) error { - query := "SELECT u.pubkey, u.msats FROM sessions s LEFT JOIN users u ON u.pubkey = s.pubkey WHERE session_id = $1" - err := db.QueryRow(query, s.SessionId).Scan(&s.Pubkey, &s.Msats) - return err -} - -func (db *DB) DeleteSession(s *Session) error { - _, err := db.Exec("DELETE FROM sessions where session_id = $1", s.SessionId) - return err -} diff --git a/db/types.go b/db/types.go deleted file mode 100644 index 261b5be..0000000 --- a/db/types.go +++ /dev/null @@ -1,80 +0,0 @@ -package db - -import ( - "time" - - "gopkg.in/guregu/null.v4" -) - -type ( - Serial = int - UUID = string - LNAuth struct { - K1 string - LNURL string - CreatedAt time.Time - SessionId string - } - User struct { - Pubkey string - Msats int64 - LastSeen time.Time - } - Session struct { - Pubkey string - Msats int64 - SessionId string - } - Market struct { - Id Serial `json:"id"` - Description string `json:"description"` - EndDate time.Time `json:"endDate"` - SettledAt null.Time `json:"settledAt"` - Pubkey string `json:"pubkey"` - InvoiceId UUID - } - Share struct { - Id UUID `json:"sid"` - MarketId int - Description string - Win bool - } - 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 - Description string - Status string - } - Order struct { - Id UUID - CreatedAt time.Time - DeletedAt null.Time - ShareId string `json:"sid"` - ShareDescription string - Share - Pubkey string - Side string `json:"side"` - Quantity int64 `json:"quantity"` - Price int64 `json:"price"` - InvoiceId null.String - Invoice - OrderId null.String - } - Withdrawal struct { - Id UUID - CreatedAt time.Time - DeletedAt null.Time - Pubkey string - Bolt11 string `json:"bolt11"` - PaidAt null.Time - } -) diff --git a/db/user.go b/db/user.go deleted file mode 100644 index 290c651..0000000 --- a/db/user.go +++ /dev/null @@ -1,17 +0,0 @@ -package db - -func (db *DB) 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 (db *DB) FetchUser(pubkey string, u *User) error { - return db.QueryRow("SELECT pubkey, last_seen FROM users WHERE pubkey = $1", pubkey).Scan(&u.Pubkey, &u.LastSeen) -} - -func (db *DB) UpdateUser(u *User) error { - _, err := db.Exec("UPDATE users SET last_seen = $1 WHERE pubkey = $2", u.LastSeen, u.Pubkey) - return err -} diff --git a/db/withdrawal.go b/db/withdrawal.go deleted file mode 100644 index 66aa183..0000000 --- a/db/withdrawal.go +++ /dev/null @@ -1,13 +0,0 @@ -package db - -import ( - "context" - "database/sql" -) - -func (db *DB) CreateWithdrawal(tx *sql.Tx, c context.Context, w *Withdrawal) error { - if _, err := tx.ExecContext(c, "INSERT INTO withdrawals(pubkey, bolt11) VALUES ($1, $2)", w.Pubkey, w.Bolt11); err != nil { - return err - } - return nil -} diff --git a/hotreload.sh b/hotreload.sh index b3c7d41..46f53ab 100755 --- a/hotreload.sh +++ b/hotreload.sh @@ -31,6 +31,6 @@ trap cleanup EXIT restart tail -f server.log & -while inotifywait -r -e modify db/ env/ lib/ lnd/ pages/ public/ server/; do +while inotifywait -r -e modify db/ env/ lib/ lnd/ public/ server/; do restart done diff --git a/lnd/invoice.go b/lnd/invoice.go deleted file mode 100644 index 4a1c4ae..0000000 --- a/lnd/invoice.go +++ /dev/null @@ -1,163 +0,0 @@ -package lnd - -import ( - "context" - "database/sql" - "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 (lnd *LNDClient) CreateInvoice(tx *sql.Tx, ctx context.Context, d *db.DB, pubkey string, msats int64, description string) (*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(ctx, &invoicesrpc.AddInvoiceData{ - Hash: &hash, - Value: lnwire.MilliSatoshi(msats), - Expiry: int64(expiry / time.Millisecond), - }); err != nil { - return nil, err - } - if lnInvoice, err = lnd.Client.LookupInvoice(ctx, 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), - Description: description, - } - if err := d.CreateInvoice(tx, ctx, dbInvoice); err != nil { - return nil, err - } - return dbInvoice, nil -} - -func (lnd *LNDClient) CheckInvoice(d *db.DB, hash lntypes.Hash) { - var ( - pollInterval = 5 * time.Second - invoice db.Invoice - lnInvoice *lndclient.Invoice - preimage lntypes.Preimage - err error - ) - - if err = d.FetchInvoice(&db.FetchInvoiceWhere{Hash: hash.String()}, &invoice); err != nil { - log.Println(err) - return - } - - for { - ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) - var tx *sql.Tx - if tx, err = d.BeginTx(ctx, nil); err != nil { - cancel() - continue - } - - handleLoopError := func(err error) { - log.Println(err) - tx.Rollback() - cancel() - time.Sleep(pollInterval) - } - - log.Printf("lookup invoice: hash=%s", hash) - if lnInvoice, err = lnd.Client.LookupInvoice(ctx, 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(ctx, hash); err != nil { - handleLoopError(err) - continue - } - log.Printf("invoice expired: hash=%s", hash) - tx.Commit() - break - } - if lnInvoice.AmountPaid == lnInvoice.Amount { - 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(ctx, preimage); err != nil { - handleLoopError(err) - continue - } - if err = d.ConfirmInvoice(tx, ctx, hash.String(), time.Now(), int(lnInvoice.AmountPaid)); err != nil { - handleLoopError(err) - continue - } - log.Printf("invoice confirmed: hash=%s", hash) - - // Run matchmaking if an order was paid - var orderId string - var deleted bool - if err = d.QueryRowContext(ctx, - "SELECT o.id, o.deleted_at IS NOT NULL FROM orders o WHERE invoice_id = (SELECT i.id FROM invoices i WHERE hash = $1)", - hash.String(), - ).Scan(&orderId, &deleted); err != nil && err != sql.ErrNoRows { - handleLoopError(err) - continue - } - if deleted { - // order was canceled before it was paid. refund sats immediately. - // this can happen if the market was settled between creating the order and paying the corresponding invoice. - if _, err := tx.ExecContext(ctx, "UPDATE users SET msats = msats + $1", int64(lnInvoice.AmountPaid)); err != nil { - tx.Rollback() - break - } - log.Printf("order %s canceled. refunded sats to user.", orderId) - break - } - if orderId != "" { - go d.RunMatchmaking(orderId) - } - tx.Commit() - - break - } - time.Sleep(pollInterval) - } -} - -func (lnd *LNDClient) CheckInvoices(d *db.DB) error { - var ( - invoices []db.Invoice - err error - hash lntypes.Hash - ) - if err = d.FetchInvoices(&db.FetchInvoicesWhere{Unconfirmed: true}, &invoices); err != nil { - return err - } - for _, invoice := range invoices { - if hash, err = lntypes.MakeHashFromStr(invoice.Hash); err != nil { - return err - } - go lnd.CheckInvoice(d, hash) - } - return nil -} diff --git a/lnd/types.go b/lnd/types.go deleted file mode 100644 index 4e2eb51..0000000 --- a/lnd/types.go +++ /dev/null @@ -1 +0,0 @@ -package lnd diff --git a/lnd/withdrawal.go b/lnd/withdrawal.go index 0b9ae35..8c3fef1 100644 --- a/lnd/withdrawal.go +++ b/lnd/withdrawal.go @@ -10,21 +10,31 @@ import ( ) func (lnd *LNDClient) PayInvoice(tx *sql.Tx, bolt11 string) error { - maxFeeSats := btcutil.Amount(10) ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) defer cancel() + log.Printf("attempting to pay bolt11 %s ...\n", bolt11) + + maxFeeSats := btcutil.Amount(10) payChan := lnd.Client.PayInvoice(ctx, bolt11, maxFeeSats, nil) + res := <-payChan + if res.Err != nil { log.Printf("error paying bolt11: %s -- %s\n", bolt11, res.Err) tx.Rollback() return res.Err } + log.Printf("successfully paid bolt11: %s\n", bolt11) - if _, err := tx.ExecContext(ctx, "UPDATE withdrawals SET paid_at = CURRENT_TIMESTAMP WHERE bolt11 = $1", bolt11); err != nil { + + if _, err := tx.ExecContext(ctx, + "UPDATE withdrawals SET paid_at = CURRENT_TIMESTAMP WHERE bolt11 = $1", + bolt11, + ); err != nil { tx.Rollback() return err } + return res.Err } diff --git a/main.go b/main.go index 0f63da7..4cee309 100644 --- a/main.go +++ b/main.go @@ -61,7 +61,7 @@ func init() { log.Printf("[warn] error connecting to LND: %v\n", err) lnd_ = nil } else { - lnd_.CheckInvoices(db_) + // lnd_.CheckInvoices(db_) } ctx = server.Context{ diff --git a/pages/index.html b/pages/index.html deleted file mode 100644 index 5d9cb70..0000000 --- a/pages/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - delphi.market - - - - - - - - - {{ if eq .ENV "development" }} - - {{ end }} - - - -
- -
-
- - -
-     _      _       _     _ 
-  __| | ___| |_ __ | |__ (_)
- / _` |/ _ \ | '_ \| '_ \| |
-| (_| |  __/ | |_) | | | | |
- \__,_|\___|_| .__/|_| |_|_|
-             |_|            
-
-
-
A prediction market using the lightning network [WIP]
-
ACTIVE MARKETS
- {{ range .markets }} - {{.Description}} - {{ end }} -
- - - - \ No newline at end of file diff --git a/pages/invoice.html b/pages/invoice.html deleted file mode 100644 index 4d704d9..0000000 --- a/pages/invoice.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - delphi.market - - - - - - - - - - {{ if eq .ENV "development" }} - - {{ end }} - - - -
- -
-
- - -
- _  _    ___ ____  
-| || |  / _ \___ \ 
-| || |_| | | |__) |
-|__   _| |_| / __/ 
-   |_|  \___/_____|
-
-
-
-
Payment Required
- -
-
- - - -
{{.lnurl}}
-
- details -
id: {{.invoice.Id}}
-
amount: {{div .invoice.Msats 1000}} sats
-
created: {{.invoice.CreatedAt}}
-
expiry : {{.invoice.ExpiresAt}}
-
hash: {{.invoice.Hash}}
-
-
-
- - - - \ No newline at end of file diff --git a/pages/login.html b/pages/login.html deleted file mode 100644 index f640b96..0000000 --- a/pages/login.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - delphi.market - - - - - - - - - {{ if eq .ENV "development" }} - - {{ end }} - - - -
- -
-
- - -
- _             _       
-| | ___   __ _(_)_ __  
-| |/ _ \ / _` | | '_ \ 
-| | (_) | (_| | | | | |
-|_|\___/ \__, |_|_| |_|
-         |___/         
-
-
-
-
Login with Lightning
- -
{{.lnurl}}
-
- -
- - - - \ No newline at end of file diff --git a/pages/market.html b/pages/market.html deleted file mode 100644 index bc541b1..0000000 --- a/pages/market.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - delphi.market - - - - - - - - - - {{ if eq .ENV "development" }} - - {{ end }} - - - -
- -
-
- - -
-                      _        _   
- _ __ ___   __ _ _ __| | _____| |_ 
-| '_ ` _ \ / _` | '__| |/ / _ \ __|
-| | | | | | (_| | |  |   <  __/ |_ 
-|_| |_| |_|\__,_|_|  |_|\_\___|\__|
-
-
-
{{.Description}}
-
- Order Book - - - - - - {{ range .Orders }} - - - - - - {{ end }} -
{{ .Side }}{{ .Share.Description }}{{.Quantity}} @ {{.Price}} ⚡
-
-
-
- Order Form -
- - - - - - - - - - - - -
-
-
- - - - \ No newline at end of file diff --git a/pages/user.html b/pages/user.html deleted file mode 100644 index aaeb7b0..0000000 --- a/pages/user.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - delphi.market - - - - - - - - - - {{ if eq .ENV "development" }} - - {{ end }} - - - -
- -
-
- - -
- _   _ ___  ___ _ __ 
-| | | / __|/ _ \ '__|
-| |_| \__ \  __/ |   
- \__,_|___/\___|_|   
-
-
-
-
- You are: {{substr .session.Pubkey 0 8}} -
-
-
- -
-
-
-
- Open Orders - - - - - - - - {{ range .Orders }} - {{ if .Invoice.ConfirmedAt.Valid }} - - - - - - - {{ end }} - {{ end }} -
Market
- {{.Share.MarketId}} - {{.Side}}{{.Share.Description}}{{.Quantity}} @ {{.Price}} ⚡
-
-
- Unpaid Orders - - - - - - - - - {{ range .Orders }} - {{ if not .Invoice.ConfirmedAt.Valid }} - - - - - - - - {{ end }} - {{ end }} -
MarketInvoice
- {{.Share.MarketId}} - {{.Side}}{{.Share.Description}}{{.Quantity}} @ {{.Price}} ⚡invoice
-
-
- - - - \ No newline at end of file diff --git a/public/js/order.js b/public/js/order.js deleted file mode 100644 index 80e77f9..0000000 --- a/public/js/order.js +++ /dev/null @@ -1,16 +0,0 @@ -const marketId = document.querySelector("#market-id").value - -const buyBtn = document.querySelector("#buy") -const sellBtn = document.querySelector("#sell") -const sideInput = document.querySelector("#side") - -buyBtn.onclick = function (e) { - buyBtn.classList.add("selected") - sellBtn.classList.remove("selected") - sideInput.setAttribute("value", "BUY") -} -sellBtn.onclick = function(e) { - buyBtn.classList.remove("selected") - sellBtn.classList.add("selected") - sideInput.setAttribute("value", 'SELL') -} diff --git a/public/market.css b/public/market.css deleted file mode 100644 index c1e1045..0000000 --- a/public/market.css +++ /dev/null @@ -1,47 +0,0 @@ -.order-button { - border: none; - margin: auto; - border-radius: 4px; - font-size: 20px; -} - -.order-form { - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr; -} - -.sx-1 { - margin: 0 1rem; - padding: 0 1rem; -} - -.yes { - background-color: rgba(20,158,97,.24); - color: #35df8d; -} -button.yes:hover { - background-color: #35df8d; - color: white; -} -button.yes.selected { - background-color: #35df8d; - color: white; -} - -.no { - background-color: rgba(245,57,94,.24); - color: #ff7386; -} -button.no:hover { - background-color: #ff7386; - color: white; -} -button.no.selected { - background-color: #ff7386; - color: white; -} - -th { - font-size: small; -} \ No newline at end of file diff --git a/server/router/handler/index.go b/server/router/handler/index.go index 52bc77f..e0ae7ed 100644 --- a/server/router/handler/index.go +++ b/server/router/handler/index.go @@ -1,9 +1,6 @@ package handler import ( - "net/http" - - "git.ekzyis.com/ekzyis/delphi.market/db" "git.ekzyis.com/ekzyis/delphi.market/server/router/context" "git.ekzyis.com/ekzyis/delphi.market/server/router/pages" "github.com/labstack/echo/v4" @@ -14,16 +11,3 @@ func HandleIndex(sc context.Context) echo.HandlerFunc { return pages.Index().Render(context.RenderContext(sc, c), c.Response().Writer) } } - -func HandleMarkets(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - markets []db.Market - err error - ) - if err = sc.Db.FetchActiveMarkets(&markets); err != nil { - return err - } - return c.JSON(http.StatusOK, markets) - } -} diff --git a/server/router/handler/invoice.go b/server/router/handler/invoice.go deleted file mode 100644 index 6038b2c..0000000 --- a/server/router/handler/invoice.go +++ /dev/null @@ -1,110 +0,0 @@ -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/router/context" - "github.com/labstack/echo/v4" - "github.com/lightningnetwork/lnd/lntypes" -) - -func HandleInvoiceStatus(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - invoiceId string - invoice db.Invoice - u db.User - qr string - err error - ) - invoiceId = c.Param("id") - if err = sc.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 qr, err = lib.ToQR(invoice.PaymentRequest); err != nil { - return err - } - invoice.Preimage = "" - data := map[string]any{ - "Id": invoice.Id, - "Msats": invoice.Msats, - "MsatsReceived": invoice.MsatsReceived, - "Hash": invoice.Hash, - "PaymentRequest": invoice.PaymentRequest, - "CreatedAt": invoice.CreatedAt, - "ExpiresAt": invoice.ExpiresAt, - "ConfirmedAt": invoice.ConfirmedAt, - "HeldSince": invoice.HeldSince, - "Description": invoice.Description, - "Qr": qr, - } - return c.JSON(http.StatusOK, data) - } -} - -func HandleInvoice(sc context.Context) 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 = sc.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 sc.Lnd.CheckInvoice(sc.Db, 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) - } -} - -func HandleInvoices(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - u db.User - invoices []db.Invoice - err error - ) - u = c.Get("session").(db.User) - if err = sc.Db.FetchUserInvoices(u.Pubkey, &invoices); err != nil { - return err - } - return c.JSON(http.StatusOK, invoices) - } -} diff --git a/server/router/handler/lnauth_test.go b/server/router/handler/lnauth_test.go new file mode 100644 index 0000000..2832bfb --- /dev/null +++ b/server/router/handler/lnauth_test.go @@ -0,0 +1,57 @@ +package handler_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "git.ekzyis.com/ekzyis/delphi.market/server/router/context" + "git.ekzyis.com/ekzyis/delphi.market/server/router/handler" + "git.ekzyis.com/ekzyis/delphi.market/test" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func init() { + test.Init(&db) +} + +func TestLnAuth(t *testing.T) { + var ( + assert = assert.New(t) + e *echo.Echo + c echo.Context + sc context.Context + req *http.Request + rec *httptest.ResponseRecorder + cookies []*http.Cookie + sessionId string + dbSessionId string + err error + ) + sc = context.Context{Db: db} + e, req, rec = test.HTTPMocks("GET", "/login", nil) + c = e.NewContext(req, rec) + + err = handler.HandleLogin(sc)(c) + assert.NoErrorf(err, "handler returned error") + + // Set-Cookie header present + cookies = rec.Result().Cookies() + assert.Equalf(len(cookies), 1, "wrong number of Set-Cookie headers") + assert.Equalf(cookies[0].Name, "session", "wrong cookie name") + + // new challenge inserted + sessionId = cookies[0].Value + err = db.QueryRow("SELECT session_id FROM lnauth WHERE session_id = $1", sessionId).Scan(&dbSessionId) + if !assert.NoError(err) { + return + } + + // inserted challenge matches cookie value + assert.Equalf(sessionId, dbSessionId, "wrong session id") +} + +func TestLnAuthCallback(t *testing.T) { + t.Skip() +} diff --git a/server/router/handler/login.go b/server/router/handler/login.go index 5518c27..d715af0 100644 --- a/server/router/handler/login.go +++ b/server/router/handler/login.go @@ -1,11 +1,6 @@ package handler import ( - "database/sql" - "net/http" - - "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/server/auth" "git.ekzyis.com/ekzyis/delphi.market/server/router/context" "git.ekzyis.com/ekzyis/delphi.market/server/router/pages" "github.com/labstack/echo/v4" @@ -16,65 +11,3 @@ func HandleLogin(sc context.Context) echo.HandlerFunc { return pages.Login().Render(context.RenderContext(sc, c), c.Response().Writer) } } - -// func HandleLogin(sc context.Context) 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 = sc.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{ -// "lnurl": lnAuth.LNURL, -// "qr": qr, -// } -// return c.JSON(http.StatusOK, data) -// } -// } - -func HandleLoginCallback(sc context.Context) 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 = sc.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 = sc.Db.CreateUser(&db.User{Pubkey: query.Key}); err != nil { - return err - } - if err = sc.Db.CreateSession(&db.Session{Pubkey: query.Key, SessionId: sessionId}); err != nil { - return err - } - if err = sc.Db.DeleteLNAuth(&db.LNAuth{K1: query.K1}); err != nil { - return err - } - return c.JSON(http.StatusOK, map[string]string{"status": "OK"}) - } -} diff --git a/server/router/handler/login_test.go b/server/router/handler/login_test.go deleted file mode 100644 index 6fbcc0a..0000000 --- a/server/router/handler/login_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package handler_test - -import ( - "database/sql" - "encoding/hex" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - db_ "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/server/auth" - "git.ekzyis.com/ekzyis/delphi.market/server/router/context" - "git.ekzyis.com/ekzyis/delphi.market/server/router/handler" - "git.ekzyis.com/ekzyis/delphi.market/test" - "github.com/decred/dcrd/dcrec/secp256k1/v4" - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" -) - -func init() { - test.Init(&db) -} - -func TestLogin(t *testing.T) { - var ( - assert = assert.New(t) - e *echo.Echo - c echo.Context - sc context.Context - req *http.Request - rec *httptest.ResponseRecorder - cookies []*http.Cookie - sessionId string - dbSessionId string - err error - ) - sc = context.Context{Db: db} - e, req, rec = test.HTTPMocks("GET", "/login", nil) - c = e.NewContext(req, rec) - - err = handler.HandleLogin(sc)(c) - assert.NoErrorf(err, "handler returned error") - - // Set-Cookie header present - cookies = rec.Result().Cookies() - assert.Equalf(len(cookies), 1, "wrong number of Set-Cookie headers") - assert.Equalf(cookies[0].Name, "session", "wrong cookie name") - - // new challenge inserted - sessionId = cookies[0].Value - err = db.QueryRow("SELECT session_id FROM lnauth WHERE session_id = $1", sessionId).Scan(&dbSessionId) - if !assert.NoError(err) { - return - } - - // inserted challenge matches cookie value - assert.Equalf(sessionId, dbSessionId, "wrong session id") -} - -func TestLoginCallback(t *testing.T) { - var ( - assert = assert.New(t) - e *echo.Echo - c echo.Context - sc context.Context - req *http.Request - rec *httptest.ResponseRecorder - sk *secp256k1.PrivateKey - pk *secp256k1.PublicKey - lnAuth *auth.LNAuth - dbLnAuth *db_.LNAuth - u *db_.User - s *db_.Session - key string - sig string - err error - ) - lnAuth, err = auth.NewLNAuth() - if !assert.NoErrorf(err, "error creating challenge") { - return - } - dbLnAuth = &db_.LNAuth{K1: lnAuth.K1, LNURL: lnAuth.LNURL} - err = db.CreateLNAuth(dbLnAuth) - if !assert.NoErrorf(err, "error inserting challenge") { - return - } - - sk, pk, err = test.GenerateKeyPair() - if !assert.NoErrorf(err, "error generating keypair") { - return - } - sig, err = test.Sign(sk, lnAuth.K1) - if !assert.NoErrorf(err, "error signing k1") { - return - } - key = hex.EncodeToString(pk.SerializeCompressed()) - - sc = context.Context{Db: db} - e, req, rec = test.HTTPMocks("GET", fmt.Sprintf("/api/login?k1=%s&key=%s&sig=%s", lnAuth.K1, key, sig), nil) - c = e.NewContext(req, rec) - - err = handler.HandleLoginCallback(sc)(c) - assert.NoErrorf(err, "handler returned error") - - // user created - u = new(db_.User) - err = db.FetchUser(key, u) - if assert.NoErrorf(err, "error fetching user") { - assert.Equalf(u.Pubkey, key, "pubkeys do not match") - } - - // session created - s = &db_.Session{SessionId: dbLnAuth.SessionId} - err = db.FetchSession(s) - if assert.NoErrorf(err, "error fetching session") { - assert.Equalf(s.Pubkey, u.Pubkey, "session pubkey does not match user pubkey") - } - - // challenge deleted - err = db.FetchSessionId(u.Pubkey, &dbLnAuth.SessionId) - assert.ErrorIs(err, sql.ErrNoRows, "challenge not deleted") -} diff --git a/server/router/handler/logout.go b/server/router/handler/logout.go deleted file mode 100644 index e210ab5..0000000 --- a/server/router/handler/logout.go +++ /dev/null @@ -1,31 +0,0 @@ -package handler - -import ( - "net/http" - "time" - - "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/server/router/context" - "github.com/labstack/echo/v4" -) - -func HandleLogout(sc context.Context) 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.JSON(http.StatusNotFound, map[string]string{"reason": "session not found"}) - } - sessionId = cookie.Value - if err = sc.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.JSON(http.StatusSeeOther, map[string]string{"status": "OK"}) - } -} diff --git a/server/router/handler/logout_test.go b/server/router/handler/logout_test.go index f4da236..e12b502 100644 --- a/server/router/handler/logout_test.go +++ b/server/router/handler/logout_test.go @@ -1,20 +1,9 @@ package handler_test import ( - "database/sql" - "encoding/hex" - "fmt" - "net/http" - "net/http/httptest" "testing" - db_ "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/server/router/context" - "git.ekzyis.com/ekzyis/delphi.market/server/router/handler" "git.ekzyis.com/ekzyis/delphi.market/test" - "github.com/decred/dcrd/dcrec/secp256k1/v4" - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" ) func init() { @@ -22,51 +11,5 @@ func init() { } func TestLogout(t *testing.T) { - var ( - assert = assert.New(t) - e *echo.Echo - c echo.Context - sc context.Context - req *http.Request - rec *httptest.ResponseRecorder - pk *secp256k1.PublicKey - s *db_.Session - key string - err error - ) - sc = context.Context{Db: db} - e, req, rec = test.HTTPMocks("POST", "/logout", nil) - - _, pk, err = test.GenerateKeyPair() - if !assert.NoErrorf(err, "error generating keypair") { - return - } - key = hex.EncodeToString(pk.SerializeCompressed()) - err = sc.Db.CreateUser(&db_.User{Pubkey: key}) - if !assert.NoErrorf(err, "error creating user") { - return - } - s = &db_.Session{Pubkey: key} - err = sc.Db.QueryRow("SELECT encode(gen_random_uuid()::text::bytea, 'base64')").Scan(&s.SessionId) - if !assert.NoErrorf(err, "error creating session id") { - return - } - - // create session - err = sc.Db.CreateSession(s) - if !assert.NoErrorf(err, "error creating session") { - return - } - - // session authentication via cookie - req.Header.Add("cookie", fmt.Sprintf("session=%s", s.SessionId)) - - c = e.NewContext(req, rec) - err = handler.HandleLogout(sc)(c) - assert.NoErrorf(err, "handler returned error") - - // session must have been deleted - err = sc.Db.FetchSession(s) - assert.ErrorIsf(err, sql.ErrNoRows, "session not deleted") - + t.Skip() } diff --git a/server/router/handler/market.go b/server/router/handler/market.go deleted file mode 100644 index c3483f6..0000000 --- a/server/router/handler/market.go +++ /dev/null @@ -1,475 +0,0 @@ -package handler - -import ( - context_ "context" - "database/sql" - "fmt" - "net/http" - "strconv" - "strings" - "time" - - "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/lib" - "git.ekzyis.com/ekzyis/delphi.market/server/router/context" - "github.com/labstack/echo/v4" - "github.com/lightningnetwork/lnd/lntypes" -) - -func HandleMarket(sc context.Context) 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 - u db.User - tx *sql.Tx - ) - if marketId, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Bad Request") - } - if err = sc.Db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows { - return echo.NewHTTPError(http.StatusNotFound, "Not Found") - } else if err != nil { - return err - } - if err = sc.Db.FetchShares(market.Id, &shares); err != nil { - return err - } - if err = sc.Db.FetchOrders(&db.FetchOrdersWhere{MarketId: market.Id, Confirmed: true}, &orders); err != nil { - return err - } - data = map[string]any{ - "Id": market.Id, - "Pubkey": market.Pubkey, - "Description": market.Description, - "SettledAt": market.SettledAt, - "Shares": shares, - } - if session := c.Get("session"); session != nil { - u = session.(db.User) - ctx, cancel := context_.WithTimeout(context_.TODO(), 10*time.Second) - defer cancel() - if tx, err = sc.Db.BeginTx(ctx, nil); err != nil { - return err - } - defer tx.Commit() - uBalance := make(map[string]any) - if err = sc.Db.FetchUserBalance(tx, ctx, int(marketId), u.Pubkey, &uBalance); err != nil { - tx.Rollback() - return err - } - lib.Merge(&data, &map[string]any{"user": uBalance}) - } - return c.JSON(http.StatusOK, data) - } -} - -func HandleCreateMarket(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - tx *sql.Tx - u db.User - m db.Market - invoice *db.Invoice - msats int64 - invDescription string - data map[string]any - qr string - hash lntypes.Hash - err error - ) - if err := c.Bind(&m); err != nil { - return echo.NewHTTPError(http.StatusBadRequest) - } - - // transaction start - ctx, cancel := context_.WithTimeout(c.Request().Context(), 5*time.Second) - defer cancel() - if tx, err = sc.Db.BeginTx(ctx, nil); err != nil { - return err - } - defer tx.Commit() - - u = c.Get("session").(db.User) - m.Pubkey = u.Pubkey - msats = 1000 - // TODO: add [market:] for redirect after payment - invDescription = fmt.Sprintf("create market \"%s\"", m.Description) - if invoice, err = sc.Lnd.CreateInvoice(tx, ctx, sc.Db, u.Pubkey, msats, invDescription); err != nil { - tx.Rollback() - return err - } - if qr, err = lib.ToQR(invoice.PaymentRequest); err != nil { - tx.Rollback() - return err - } - if hash, err = lntypes.MakeHashFromStr(invoice.Hash); err != nil { - tx.Rollback() - return err - } - m.InvoiceId = invoice.Id - if err := sc.Db.CreateMarket(tx, ctx, &m); err != nil { - tx.Rollback() - return err - } - - // need to commit before starting to poll invoice status - tx.Commit() - go sc.Lnd.CheckInvoice(sc.Db, hash) - - data = map[string]any{ - "id": invoice.Id, - "bolt11": invoice.PaymentRequest, - "amount": msats, - "qr": qr, - } - return c.JSON(http.StatusPaymentRequired, data) - } -} - -func HandleMarketOrders(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - marketId int64 - orders []db.Order - err error - ) - if marketId, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Bad Request") - } - if err = sc.Db.FetchMarketOrders(marketId, &orders); err != nil { - return err - } - return c.JSON(http.StatusOK, orders) - } -} - -func HandleOrder(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - tx *sql.Tx - u db.User - o db.Order - s db.Share - m db.Market - invoice *db.Invoice - msats int64 - description string - data map[string]any - qr string - hash lntypes.Hash - err error - ) - 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 - - // transaction start - ctx, cancel := context_.WithTimeout(c.Request().Context(), 5*time.Second) - defer cancel() - if tx, err = sc.Db.BeginTx(ctx, nil); err != nil { - return err - } - defer tx.Commit() - - if err = sc.Db.FetchShare(tx, ctx, o.ShareId, &s); err != nil { - tx.Rollback() - return err - } - - if err = sc.Db.FetchMarket(s.MarketId, &m); err == sql.ErrNoRows { - return c.JSON(http.StatusNotFound, nil) - } else if err != nil { - return err - } - - if m.SettledAt.Valid { - return c.JSON(http.StatusBadRequest, map[string]string{"reason": "market already settled"}) - } - - description = fmt.Sprintf("%s %d %s shares @ %d sats [market:%d]", strings.ToUpper(o.Side), o.Quantity, s.Description, o.Price, s.MarketId) - - if o.Side == "BUY" { - // BUY orders require payment - if invoice, err = sc.Lnd.CreateInvoice(tx, ctx, sc.Db, o.Pubkey, msats, description); err != nil { - tx.Rollback() - return err - } - // Create QR code to pay HODL invoice - if qr, err = lib.ToQR(invoice.PaymentRequest); err != nil { - tx.Rollback() - return err - } - if hash, err = lntypes.MakeHashFromStr(invoice.Hash); err != nil { - tx.Rollback() - return err - } - - // Create (unconfirmed) order - o.InvoiceId.String = invoice.Id - if err := sc.Db.CreateOrder(tx, ctx, &o); err != nil { - tx.Rollback() - return err - } - // need to commit before starting to poll invoice status - tx.Commit() - go sc.Lnd.CheckInvoice(sc.Db, hash) - - data = map[string]any{ - "id": invoice.Id, - "bolt11": invoice.PaymentRequest, - "amount": msats, - "qr": qr, - } - return c.JSON(http.StatusPaymentRequired, data) - } - - // sell order: check user balance - balance := make(map[string]any) - if err = sc.Db.FetchUserBalance(tx, ctx, s.MarketId, o.Pubkey, &balance); err != nil { - return err - } - if balance[s.Description].(int) < int(o.Quantity) { - tx.Rollback() - return c.JSON(http.StatusBadRequest, nil) - } - // SELL orders don't require payment by user - if err := sc.Db.CreateOrder(tx, ctx, &o); err != nil { - tx.Rollback() - return err - } - tx.Commit() - return c.JSON(http.StatusCreated, nil) - } -} - -func HandleDeleteOrder(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - orderId string - tx *sql.Tx - u db.User - o db.Order - msats int64 - err error - ) - - if orderId = c.Param("id"); orderId == "" { - return echo.NewHTTPError(http.StatusBadRequest) - } - u = c.Get("session").(db.User) - - // transaction start - ctx, cancel := context_.WithTimeout(c.Request().Context(), 5*time.Second) - defer cancel() - if tx, err = sc.Db.BeginTx(ctx, nil); err != nil { - return err - } - defer tx.Commit() - - if err = sc.Db.FetchOrder(tx, ctx, orderId, &o); err != nil { - tx.Rollback() - return err - } - - if u.Pubkey != o.Pubkey { - // order does not belong to user - tx.Rollback() - return echo.NewHTTPError(http.StatusForbidden) - } - - if o.OrderId.Valid { - // order already settled - tx.Rollback() - return echo.NewHTTPError(http.StatusBadRequest) - } - - if o.DeletedAt.Valid { - // order already deleted - tx.Rollback() - return echo.NewHTTPError(http.StatusBadRequest) - } - - if o.Invoice.ConfirmedAt.Valid { - // order already paid: we need to move paid sats to user balance before deleting the order - // TODO update order and session on client - msats = o.Invoice.MsatsReceived - if res, err := tx.ExecContext(ctx, "UPDATE users SET msats = msats + $1 WHERE pubkey = $2", msats, u.Pubkey); err != nil { - tx.Rollback() - return err - } else { - // make sure exactly one row was affected - if rowsAffected, err := res.RowsAffected(); err != nil { - tx.Rollback() - return err - } else if rowsAffected != 1 { - tx.Rollback() - return echo.NewHTTPError(http.StatusInternalServerError) - } - } - } - - if _, err = tx.ExecContext(ctx, "UPDATE orders SET deleted_at = CURRENT_TIMESTAMP WHERE id = $1", o.Id); err != nil { - tx.Rollback() - return err - } - - tx.Commit() - - return c.JSON(http.StatusOK, nil) - } -} - -func HandleOrders(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - u db.User - orders []db.Order - err error - ) - u = c.Get("session").(db.User) - if err = sc.Db.FetchUserOrders(u.Pubkey, &orders); err != nil { - return err - } - return c.JSON(http.StatusOK, orders) - } -} - -func HandleMarketStats(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - marketId int64 - stats db.MarketStats - err error - ) - if marketId, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Bad Request") - } - if err = sc.Db.FetchMarketStats(marketId, &stats); err != nil { - return err - } - return c.JSON(http.StatusOK, stats) - } -} - -func HandleMarketSettlement(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - marketId int64 - market db.Market - s db.Share - tx *sql.Tx - u db.User - query string - err error - ) - if marketId, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil { - return c.JSON(http.StatusBadRequest, nil) - } - - if err = c.Bind(&s); err != nil || s.Id == "" { - return c.JSON(http.StatusBadRequest, nil) - } - - if err = sc.Db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows { - return c.JSON(http.StatusNotFound, map[string]string{"reason": "market not found"}) - } else if err != nil { - return err - } - - u = c.Get("session").(db.User) - - // only market owner can settle market - if market.Pubkey != u.Pubkey { - return c.JSON(http.StatusForbidden, map[string]string{"reason": "not your market"}) - } - - // market already settled? - if market.SettledAt.Valid { - return c.JSON(http.StatusBadRequest, map[string]string{"reason": "market already settled"}) - } - - // transaction start - ctx, cancel := context_.WithTimeout(c.Request().Context(), 10*time.Second) - defer cancel() - if tx, err = sc.Db.BeginTx(ctx, nil); err != nil { - return err - } - defer tx.Commit() - - // refund users for pending BUY orders - query = "" + - "UPDATE users u SET msats = msats + pending_orders.msats_received FROM ( " + - " SELECT o.pubkey, i.msats_received " + - " FROM orders o " + - " LEFT JOIN invoices i ON i.id = o.invoice_id " + - " JOIN shares s ON s.id = o.share_id " + - " WHERE s.market_id = $1 " + - // an order is pending if it wasn't canceled and wasn't matched yet - // (the o.side = 'BUY' shouldn't be necessary since i.msats_received will be NULL for SELL orders anyway - // but added here for clarification anyway) - " AND o.side = 'BUY' AND o.deleted_at IS NULL AND o.order_id IS NULL " + - ") AS pending_orders WHERE pending_orders.pubkey = u.pubkey" - if _, err = tx.ExecContext(ctx, query, marketId); err != nil { - tx.Rollback() - return err - } - - // now cancel pending orders - query = "" + - "UPDATE orders o SET deleted_at = CURRENT_TIMESTAMP WHERE id IN ( " + - // basically same subquery as above - " SELECT o.id FROM orders o " + - " JOIN shares s ON s.id = o.share_id " + - // again, orders are pending if they weren't canceled and weren't matched yet - " WHERE s.market_id = $1 AND o.deleted_at IS NULL and o.order_id IS NULL " + - ")" - if _, err = tx.ExecContext(ctx, query, marketId); err != nil { - tx.Rollback() - return err - } - - // payout - query = "" + - // * 100 since winning shares expire at 100 sats per share - // * 1000 to convert sats to msats - "UPDATE users u SET msats = msats + (user_shares.quantity * 100 * 1000) " + - "FROM ( " + - " SELECT o.pubkey, o.share_id, " + - " SUM(CASE WHEN o.side = 'BUY' THEN o.quantity ELSE -o.quantity END) AS quantity " + - " FROM orders o " + - " LEFT JOIN invoices i ON i.id = o.invoice_id " + - " JOIN shares s ON s.id = o.share_id " + - // only consider uncanceled orders for winning shares - " WHERE s.market_id = $1 AND o.deleted_at IS NULL AND s.id = $2 " + - // BUY orders must be paid and be matched. SELL orders must simply not be canceled to be considered. - " AND ( (o.side = 'BUY' AND i.confirmed_at IS NOT NULL AND o.order_id IS NOT NULL) OR o.side = 'SELL' ) " + - " GROUP BY o.pubkey, o.share_id " + - ") AS user_shares WHERE user_shares.pubkey = u.pubkey" - if _, err = tx.ExecContext(ctx, query, marketId, s.Id); err != nil { - tx.Rollback() - return err - } - - if _, err = tx.ExecContext(ctx, "UPDATE markets SET settled_at = CURRENT_TIMESTAMP WHERE id = $1", marketId); err != nil { - tx.Rollback() - return err - } - - if _, err = tx.ExecContext(ctx, "UPDATE shares SET win = (id = $1) WHERE market_id = $2", s.Id, marketId); err != nil { - tx.Rollback() - return err - } - - tx.Commit() - - return c.JSON(http.StatusOK, nil) - } -} diff --git a/server/router/handler/session.go b/server/router/handler/session.go deleted file mode 100644 index 82f4129..0000000 --- a/server/router/handler/session.go +++ /dev/null @@ -1,30 +0,0 @@ -package handler - -import ( - "database/sql" - "net/http" - - "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/server/router/context" - "github.com/labstack/echo/v4" -) - -func HandleCheckSession(sc context.Context) 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 c.JSON(http.StatusBadRequest, map[string]string{"reason": "cookie required"}) - } - s = db.Session{SessionId: cookie.Value} - if err = sc.Db.FetchSession(&s); err == sql.ErrNoRows { - return c.JSON(http.StatusBadRequest, map[string]string{"reason": "session not found"}) - } else if err != nil { - return c.JSON(http.StatusInternalServerError, nil) - } - return c.JSON(http.StatusOK, map[string]any{"pubkey": s.Pubkey, "msats": s.Msats}) - } -} diff --git a/server/router/handler/user.go b/server/router/handler/user.go deleted file mode 100644 index f130ca5..0000000 --- a/server/router/handler/user.go +++ /dev/null @@ -1,30 +0,0 @@ -package handler - -import ( - "net/http" - - "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/server/router/context" - "github.com/labstack/echo/v4" -) - -func HandleUser(sc context.Context) 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 = sc.Db.FetchOrders(&db.FetchOrdersWhere{Pubkey: u.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) - } -} diff --git a/server/router/handler/withdrawal.go b/server/router/handler/withdrawal.go deleted file mode 100644 index a4161af..0000000 --- a/server/router/handler/withdrawal.go +++ /dev/null @@ -1,79 +0,0 @@ -package handler - -import ( - context_ "context" - "database/sql" - "net/http" - "strings" - "time" - - "git.ekzyis.com/ekzyis/delphi.market/db" - "git.ekzyis.com/ekzyis/delphi.market/server/router/context" - "github.com/labstack/echo/v4" - "github.com/lightningnetwork/lnd/zpay32" -) - -func HandleWithdrawal(sc context.Context) echo.HandlerFunc { - return func(c echo.Context) error { - var ( - w db.Withdrawal - u db.User - inv *zpay32.Invoice - tx *sql.Tx - err error - ) - if err := c.Bind(&w); err != nil { - code := http.StatusBadRequest - return c.JSON(code, map[string]any{"status": code, "reason": "bolt11 required"}) - } - - if inv, err = zpay32.Decode(w.Bolt11, sc.Lnd.ChainParams); err != nil { - code := http.StatusBadRequest - return c.JSON(code, map[string]any{"status": code, "reason": "zpay32 decode error"}) - } - - // transaction start - ctx, cancel := context_.WithTimeout(c.Request().Context(), 5*time.Second) - defer cancel() - if tx, err = sc.Db.BeginTx(ctx, nil); err != nil { - return err - } - defer tx.Commit() - - u = c.Get("session").(db.User) - w.Pubkey = u.Pubkey - - // TODO deduct network fee from user balance - if u.Msats < int64(*inv.MilliSat) { - tx.Rollback() - code := http.StatusBadRequest - return c.JSON(code, map[string]any{"status": code, "reason": "insufficient balance"}) - } - - // create withdrawal - if err = sc.Db.CreateWithdrawal(tx, ctx, &w); err != nil { - tx.Rollback() - if strings.Contains(err.Error(), "violates unique constraint") { - code := http.StatusBadRequest - return c.JSON(code, map[string]any{"status": code, "reason": "bolt11 already submitted"}) - } - return err - } - - // pay invoice via LND - if err = sc.Lnd.PayInvoice(tx, w.Bolt11); err != nil { - tx.Rollback() - return err - } - - // deduct balance from user - if _, err = tx.ExecContext(ctx, "UPDATE users SET msats = msats - $1 WHERE pubkey = $2", int64(*inv.MilliSat), u.Pubkey); err != nil { - tx.Rollback() - return err - } - - tx.Commit() - - return c.JSON(http.StatusOK, nil) - } -} diff --git a/server/router/middleware/session.go b/server/router/middleware/session.go index 9e5b1b9..5b8c39c 100644 --- a/server/router/middleware/session.go +++ b/server/router/middleware/session.go @@ -1,11 +1,8 @@ package middleware import ( - "database/sql" "net/http" - "time" - "git.ekzyis.com/ekzyis/delphi.market/db" "git.ekzyis.com/ekzyis/delphi.market/server/router/context" "github.com/labstack/echo/v4" ) @@ -13,27 +10,24 @@ import ( func Session(sc context.Context) 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 = sc.Db.FetchSession(s); err == nil { - // session found - u = &db.User{Pubkey: s.Pubkey, Msats: s.Msats, LastSeen: time.Now()} - if err = sc.Db.UpdateUser(u); err != nil { - return err - } - c.Set("session", *u) - } else if err != sql.ErrNoRows { - return err - } + // TODO: implement session middleware + // 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 = sc.Db.FetchSession(s); err == nil { + // // session found + // c.Set("session", *u) + // } else if err != sql.ErrNoRows { + // return err + // } return next(c) } } diff --git a/server/router/router.go b/server/router/router.go index 84fa948..f80e061 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -5,13 +5,12 @@ import ( "git.ekzyis.com/ekzyis/delphi.market/server/router/context" "git.ekzyis.com/ekzyis/delphi.market/server/router/handler" - "git.ekzyis.com/ekzyis/delphi.market/server/router/middleware" ) type Context = context.Context func Init(e *echo.Echo, sc Context) { - e.Use(middleware.Session(sc)) + // e.Use(middleware.Session(sc)) e.GET("/", handler.HandleIndex(sc)) e.GET("/about", handler.HandleAbout(sc))