Refactor router code
This commit is contained in:
parent
04ce96069b
commit
c2a8968120
138
src/auth.go
138
src/auth.go
|
@ -3,11 +3,17 @@ package main
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcutil/bech32"
|
"github.com/btcsuite/btcutil/bech32"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LnAuth struct {
|
type LnAuth struct {
|
||||||
|
@ -65,3 +71,135 @@ func lnAuthVerify(r *LnAuthResponse) (bool, error) {
|
||||||
ecdsaKey := ecdsa.PublicKey{Curve: btcec.S256(), X: key.X(), Y: key.Y()}
|
ecdsaKey := ecdsa.PublicKey{Curve: btcec.S256(), X: key.X(), Y: key.Y()}
|
||||||
return ecdsa.VerifyASN1(&ecdsaKey, k1Bytes, sigBytes), nil
|
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})
|
||||||
|
png, err := qrcode.Encode(lnauth.lnurl, qrcode.Medium, 256)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
qr := base64.StdEncoding.EncodeToString([]byte(png))
|
||||||
|
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, "/")
|
||||||
|
}
|
||||||
|
|
79
src/db.go
79
src/db.go
|
@ -11,9 +11,13 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DbUrl string
|
DbUrl string
|
||||||
db *sql.DB
|
db *DB
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
*sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
err := godotenv.Load()
|
err := godotenv.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -29,12 +33,12 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDb() *sql.DB {
|
func initDb() *DB {
|
||||||
db, err := sql.Open("postgres", DbUrl)
|
db, err := sql.Open("postgres", DbUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return db
|
return &DB{DB: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFlags() {
|
func validateFlags() {
|
||||||
|
@ -42,3 +46,72 @@ func validateFlags() {
|
||||||
log.Fatal("DATABASE_URL not set")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) FetchOrderBook(shareId string, orderBook *[]OrderBookEntry) error {
|
||||||
|
rows, err := db.Query(""+
|
||||||
|
"SELECT share_id, side, price, SUM(quantity)"+
|
||||||
|
"FROM orders WHERE share_id = $1"+
|
||||||
|
"GROUP BY (share_id, side, price)"+
|
||||||
|
"ORDER BY share_id DESC, side DESC, price DESC", shareId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
buyOrders := []Order{}
|
||||||
|
sellOrders := []Order{}
|
||||||
|
for rows.Next() {
|
||||||
|
var order Order
|
||||||
|
rows.Scan(&order.ShareId, &order.Side, &order.Price, &order.Quantity)
|
||||||
|
if order.Side == "BUY" {
|
||||||
|
buyOrders = append(buyOrders, Order{Price: order.Price, Quantity: order.Quantity})
|
||||||
|
} else {
|
||||||
|
sellOrders = append(sellOrders, Order{Price: order.Price, Quantity: order.Quantity})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buySum := 0
|
||||||
|
sellSum := 0
|
||||||
|
for i := 0; i < Max(len(buyOrders), len(sellOrders)); i++ {
|
||||||
|
buyPrice, buyQuantity, sellQuantity, sellPrice := 0, 0, 0, 0
|
||||||
|
if i < len(buyOrders) {
|
||||||
|
buyPrice = buyOrders[i].Price
|
||||||
|
buyQuantity = buySum + buyOrders[i].Quantity
|
||||||
|
}
|
||||||
|
if i < len(sellOrders) {
|
||||||
|
sellPrice = sellOrders[i].Price
|
||||||
|
sellQuantity = sellSum + sellOrders[i].Quantity
|
||||||
|
}
|
||||||
|
buySum += buyQuantity
|
||||||
|
sellSum += sellQuantity
|
||||||
|
*orderBook = append(
|
||||||
|
*orderBook,
|
||||||
|
OrderBookEntry{
|
||||||
|
BuyQuantity: buyQuantity,
|
||||||
|
BuyPrice: buyPrice,
|
||||||
|
SellPrice: sellPrice,
|
||||||
|
SellQuantity: sellQuantity,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
20
src/funcs.go
20
src/funcs.go
|
@ -1,20 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
func add(arg1 int, arg2 int) int {
|
|
||||||
return arg1 + arg2
|
|
||||||
}
|
|
||||||
|
|
||||||
func sub(arg1 int, arg2 int) int {
|
|
||||||
return arg1 - arg2
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
FuncMap template.FuncMap = template.FuncMap{
|
|
||||||
"add": add,
|
|
||||||
"sub": sub,
|
|
||||||
}
|
|
||||||
)
|
|
101
src/market.go
101
src/market.go
|
@ -1,9 +1,48 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Market struct {
|
||||||
|
Id int
|
||||||
|
Description string
|
||||||
|
Funding int
|
||||||
|
Active bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Share struct {
|
||||||
|
Id string
|
||||||
|
MarketId int
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
ShareId string
|
||||||
|
Side string
|
||||||
|
Price int
|
||||||
|
Quantity int
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderBookEntry struct {
|
||||||
|
BuyQuantity int
|
||||||
|
BuyPrice int
|
||||||
|
SellPrice int
|
||||||
|
SellQuantity int
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketDataRequest struct {
|
||||||
|
ShareId string `json:"share_id"`
|
||||||
|
OrderSide string `json:"side"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
func costFunction(b float64, q1 float64, q2 float64) float64 {
|
func costFunction(b float64, q1 float64, q2 float64) float64 {
|
||||||
// reference: http://blog.oddhead.com/2006/10/30/implementing-hansons-market-maker/
|
// 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))
|
return b * math.Log(math.Pow(math.E, q1/b)+math.Pow(math.E, q2/b))
|
||||||
|
@ -18,3 +57,65 @@ func BinaryLMSR(invariant int, funding int, q1 int, q2 int, dq1 int) float64 {
|
||||||
fdq1 := float64(dq1)
|
fdq1 := float64(dq1)
|
||||||
return costFunction(b, fq1+fdq1, fq2) - costFunction(b, fq1, fq2)
|
return costFunction(b, fq1+fdq1, fq2) - costFunction(b, fq1, fq2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trades(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
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"ENV": ENV,
|
||||||
|
"Id": market.Id,
|
||||||
|
"Description": market.Description,
|
||||||
|
"Shares": shares,
|
||||||
|
}
|
||||||
|
return c.Render(http.StatusOK, "bmarket_trade.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func orders(c echo.Context) error {
|
||||||
|
marketId, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
||||||
|
}
|
||||||
|
shareId := c.Param("sid")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if shareId == "" {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("/market/%d/%s", market.Id, shares[0].Id))
|
||||||
|
}
|
||||||
|
var orderBook []OrderBookEntry
|
||||||
|
if err = db.FetchOrderBook(shareId, &orderBook); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"session": c.Get("session"),
|
||||||
|
"ENV": ENV,
|
||||||
|
"Id": market.Id,
|
||||||
|
"Description": market.Description,
|
||||||
|
"ShareId": shareId,
|
||||||
|
"Shares": shares,
|
||||||
|
"OrderBook": orderBook,
|
||||||
|
}
|
||||||
|
return c.Render(http.StatusOK, "bmarket_order.html", data)
|
||||||
|
}
|
||||||
|
|
285
src/router.go
285
src/router.go
|
@ -1,55 +1,33 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/skip2/go-qrcode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Template struct {
|
type Template struct {
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
type Market struct {
|
func add(arg1 int, arg2 int) int {
|
||||||
Id int
|
return arg1 + arg2
|
||||||
Description string
|
|
||||||
Funding int
|
|
||||||
Active bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Share struct {
|
func sub(arg1 int, arg2 int) int {
|
||||||
Id string
|
return arg1 - arg2
|
||||||
MarketId int
|
|
||||||
Description string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Order struct {
|
var (
|
||||||
ShareId string
|
FuncMap template.FuncMap = template.FuncMap{
|
||||||
Side string
|
"add": add,
|
||||||
Price int
|
"sub": sub,
|
||||||
Quantity int
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
type OrderBookEntry struct {
|
|
||||||
BuyQuantity int
|
|
||||||
BuyPrice int
|
|
||||||
SellPrice int
|
|
||||||
SellQuantity int
|
|
||||||
}
|
|
||||||
|
|
||||||
type MarketDataRequest struct {
|
|
||||||
ShareId string `json:"share_id"`
|
|
||||||
OrderSide string `json:"side"`
|
|
||||||
Quantity int `json:"quantity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||||
return t.templates.ExecuteTemplate(w, name, data)
|
return t.templates.ExecuteTemplate(w, name, data)
|
||||||
|
@ -76,249 +54,6 @@ func index(c echo.Context) error {
|
||||||
return c.Render(http.StatusOK, "index.html", data)
|
return c.Render(http.StatusOK, "index.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
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})
|
|
||||||
png, err := qrcode.Encode(lnauth.lnurl, qrcode.Medium, 256)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
qr := base64.StdEncoding.EncodeToString([]byte(png))
|
|
||||||
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, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func trades(c echo.Context) error {
|
|
||||||
marketId := c.Param("id")
|
|
||||||
var market Market
|
|
||||||
err := db.QueryRow("SELECT id, description FROM markets WHERE id = $1 AND active = true", marketId).Scan(&market.Id, &market.Description)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
var shares []Share
|
|
||||||
for rows.Next() {
|
|
||||||
var share Share
|
|
||||||
rows.Scan(&share.Id, &share.MarketId, &share.Description)
|
|
||||||
shares = append(shares, share)
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"session": c.Get("session"),
|
|
||||||
"ENV": ENV,
|
|
||||||
"Id": market.Id,
|
|
||||||
"Description": market.Description,
|
|
||||||
"Shares": shares,
|
|
||||||
}
|
|
||||||
return c.Render(http.StatusOK, "bmarket_trade.html", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func orders(c echo.Context) error {
|
|
||||||
marketId := c.Param("id")
|
|
||||||
shareId := c.Param("sid")
|
|
||||||
var market Market
|
|
||||||
err := db.QueryRow("SELECT id, description FROM markets WHERE id = $1 AND active = true", marketId).Scan(&market.Id, &market.Description)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
var shares []Share
|
|
||||||
for rows.Next() {
|
|
||||||
var share Share
|
|
||||||
rows.Scan(&share.Id, &share.MarketId, &share.Description)
|
|
||||||
shares = append(shares, share)
|
|
||||||
}
|
|
||||||
if shareId == "" {
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("/market/%s/%s", marketId, shares[0].Id))
|
|
||||||
}
|
|
||||||
rows, err = db.Query(""+
|
|
||||||
"SELECT share_id, side, price, SUM(quantity)"+
|
|
||||||
"FROM orders WHERE share_id = $1"+
|
|
||||||
"GROUP BY (share_id, side, price)"+
|
|
||||||
"ORDER BY share_id DESC, side DESC, price DESC", shareId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
buyOrders := []Order{}
|
|
||||||
sellOrders := []Order{}
|
|
||||||
for rows.Next() {
|
|
||||||
var order Order
|
|
||||||
rows.Scan(&order.ShareId, &order.Side, &order.Price, &order.Quantity)
|
|
||||||
if order.Side == "BUY" {
|
|
||||||
buyOrders = append(buyOrders, Order{Price: order.Price, Quantity: order.Quantity})
|
|
||||||
} else {
|
|
||||||
sellOrders = append(sellOrders, Order{Price: order.Price, Quantity: order.Quantity})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
orderBook := []OrderBookEntry{}
|
|
||||||
buySum := 0
|
|
||||||
sellSum := 0
|
|
||||||
for i := 0; i < Max(len(buyOrders), len(sellOrders)); i++ {
|
|
||||||
buyPrice, buyQuantity, sellQuantity, sellPrice := 0, 0, 0, 0
|
|
||||||
if i < len(buyOrders) {
|
|
||||||
buyPrice = buyOrders[i].Price
|
|
||||||
buyQuantity = buySum + buyOrders[i].Quantity
|
|
||||||
}
|
|
||||||
if i < len(sellOrders) {
|
|
||||||
sellPrice = sellOrders[i].Price
|
|
||||||
sellQuantity = sellSum + sellOrders[i].Quantity
|
|
||||||
}
|
|
||||||
buySum += buyQuantity
|
|
||||||
sellSum += sellQuantity
|
|
||||||
orderBook = append(
|
|
||||||
orderBook,
|
|
||||||
OrderBookEntry{
|
|
||||||
BuyQuantity: buyQuantity,
|
|
||||||
BuyPrice: buyPrice,
|
|
||||||
SellPrice: sellPrice,
|
|
||||||
SellQuantity: sellQuantity,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
data := map[string]any{
|
|
||||||
"session": c.Get("session"),
|
|
||||||
"ENV": ENV,
|
|
||||||
"Id": market.Id,
|
|
||||||
"Description": market.Description,
|
|
||||||
"ShareId": shareId,
|
|
||||||
"Shares": shares,
|
|
||||||
"OrderBook": orderBook,
|
|
||||||
}
|
|
||||||
return c.Render(http.StatusOK, "bmarket_order.html", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serve500(c echo.Context) {
|
func serve500(c echo.Context) {
|
||||||
f, err := os.Open("public/500.html")
|
f, err := os.Open("public/500.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue