diff --git a/db/market.go b/db/market.go index 87d2f1d..5ac0b39 100644 --- a/db/market.go +++ b/db/market.go @@ -219,3 +219,50 @@ func (db *DB) FindOrderMatches(tx *sql.Tx, ctx context.Context, o1 *Order, o2 *O "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) } + +// [ +// +// { "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, + } + log.Println(timestamp, description, score) + *stats = append(*stats, stat) + } + return nil +} diff --git a/server/router/handler/market.go b/server/router/handler/market.go index 9f31077..52fff47 100644 --- a/server/router/handler/market.go +++ b/server/router/handler/market.go @@ -228,3 +228,20 @@ func HandleOrders(sc context.ServerContext) echo.HandlerFunc { return c.JSON(http.StatusOK, orders) } } + +func HandleMarketStats(sc context.ServerContext) 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) + } +} diff --git a/server/router/router.go b/server/router/router.go index c1fb901..e6cfb46 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -47,6 +47,7 @@ func addBackendRoutes(e *echo.Echo, sc ServerContext) { middleware.LNDGuard) GET(e, sc, "/api/market/:id", handler.HandleMarket) GET(e, sc, "/api/market/:id/orders", handler.HandleMarketOrders) + GET(e, sc, "/api/market/:id/stats", handler.HandleMarketStats) POST(e, sc, "/api/order", handler.HandleOrder, middleware.SessionGuard, diff --git a/vue/package.json b/vue/package.json index 260bc1c..064869d 100644 --- a/vue/package.json +++ b/vue/package.json @@ -9,12 +9,14 @@ }, "dependencies": { "@vitejs/plugin-vue": "^4.4.0", + "chart.js": "^4.4.0", "core-js": "^3.8.3", "pinia": "^2.1.7", "register-service-worker": "^1.7.2", "s-ago": "^2.2.0", "vite": "^4.5.0", "vue": "^3.2.13", + "vue-chartjs": "^5.2.0", "vue-router": "4" }, "devDependencies": { diff --git a/vue/src/components/Market.vue b/vue/src/components/Market.vue index 023ad79..b1d765a 100644 --- a/vue/src/components/Market.vue +++ b/vue/src/components/Market.vue @@ -14,6 +14,7 @@ diff --git a/vue/src/components/MarketStats.vue b/vue/src/components/MarketStats.vue new file mode 100644 index 0000000..23422df --- /dev/null +++ b/vue/src/components/MarketStats.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/vue/src/main.js b/vue/src/main.js index 299b429..b0cb0d9 100644 --- a/vue/src/main.js +++ b/vue/src/main.js @@ -15,6 +15,7 @@ import UserInvoices from '@/components/UserInvoices' import UserOrders from '@/components/UserOrders' import OrderForm from '@/components/OrderForm' import MarketOrders from '@/components/MarketOrders' +import MarketStats from '@/components/MarketStats' const routes = [ { @@ -37,7 +38,8 @@ const routes = [ component: MarketView, children: [ { path: 'form', name: 'form', component: OrderForm }, - { path: 'orders', name: 'market-orders', component: MarketOrders } + { path: 'orders', name: 'market-orders', component: MarketOrders }, + { path: 'stats', name: 'market-stats', component: MarketStats } ] }, { diff --git a/vue/yarn.lock b/vue/yarn.lock index bbd5f2a..fb0e97e 100644 --- a/vue/yarn.lock +++ b/vue/yarn.lock @@ -1205,6 +1205,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -2045,6 +2050,13 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chart.js@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.0.tgz#df843fdd9ec6bd88d7f07e2b95348d221bd2698c" + integrity sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ== + dependencies: + "@kurkle/color" "^0.3.0" + chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -4582,6 +4594,11 @@ vite@^4.5.0: optionalDependencies: fsevents "~2.3.2" +vue-chartjs@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-5.2.0.tgz#3d0076ccf8016d1bf8fab5ccd837e7fb81005ded" + integrity sha512-d3zpKmGZr2OWHQ1xmxBcAn5ShTG917+/UCLaSpaCDDqT0U7DBsvFzTs69ZnHCgKoXT55GZDW8YEj9Av+dlONLA== + vue-demi@>=0.14.5: version "0.14.6" resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.6.tgz#dc706582851dc1cdc17a0054f4fec2eb6df74c92"