mirror of
https://github.com/cf-sonr/motr.git
synced 2026-01-12 02:59:13 +00:00
feat: introduce API client for market data
This commit is contained in:
@@ -9,10 +9,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/sonr-io/motr/config"
|
||||
"github.com/sonr-io/motr/middleware/database"
|
||||
"github.com/sonr-io/motr/middleware/kvstore"
|
||||
"github.com/sonr-io/motr/middleware/session"
|
||||
"github.com/sonr-io/motr/middleware/sonrapi"
|
||||
"github.com/sonr-io/motr/middleware/webauthn"
|
||||
"github.com/syumai/workers"
|
||||
"github.com/syumai/workers/cloudflare/cron"
|
||||
@@ -31,11 +31,10 @@ func loadHandler() http.Handler {
|
||||
session.Middleware(),
|
||||
database.Middleware(),
|
||||
kvstore.Middleware(),
|
||||
sonrapi.Middleware(),
|
||||
webauthn.Middleware(),
|
||||
)
|
||||
setupViews(e)
|
||||
setupPartials(e)
|
||||
config.RegisterViews(e)
|
||||
config.RegisterPartials(e)
|
||||
return e
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/sonr-io/motr/config"
|
||||
"github.com/sonr-io/motr/middleware/database"
|
||||
"github.com/sonr-io/motr/middleware/kvstore"
|
||||
"github.com/sonr-io/motr/middleware/session"
|
||||
"github.com/sonr-io/motr/middleware/sonrapi"
|
||||
"github.com/sonr-io/motr/middleware/webauthn"
|
||||
"github.com/syumai/workers"
|
||||
"github.com/syumai/workers/cloudflare/cron"
|
||||
@@ -31,11 +31,10 @@ func loadHandler() http.Handler {
|
||||
session.Middleware(),
|
||||
database.Middleware(),
|
||||
kvstore.Middleware(),
|
||||
sonrapi.Middleware(),
|
||||
webauthn.Middleware(),
|
||||
)
|
||||
setupViews(e)
|
||||
setupPartials(e)
|
||||
config.RegisterViews(e)
|
||||
config.RegisterPartials(e)
|
||||
return e
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/sonr-io/motr/handlers"
|
||||
"github.com/sonr-io/motr/internal/ui/home"
|
||||
"github.com/sonr-io/motr/internal/ui/login"
|
||||
"github.com/sonr-io/motr/internal/ui/register"
|
||||
"github.com/sonr-io/motr/middleware/render"
|
||||
)
|
||||
|
||||
// ╭────────────────────────────────────────────────╮
|
||||
// │ HTTP Routes │
|
||||
// ╰────────────────────────────────────────────────╯
|
||||
|
||||
func setupViews(e *echo.Echo) {
|
||||
e.GET("/", render.Page(home.HomeView()))
|
||||
e.GET("/login", render.Page(login.LoginView()))
|
||||
e.GET("/register", render.Page(register.RegisterView()))
|
||||
}
|
||||
|
||||
func setupPartials(e *echo.Echo) {
|
||||
e.POST("/login/:handle/check", handlers.HandleLoginCheck)
|
||||
e.POST("/login/:handle/finish", handlers.HandleLoginFinish)
|
||||
e.POST("/register/:handle", handlers.HandleRegisterStart)
|
||||
e.POST("/register/:handle/check", handlers.HandleRegisterCheck)
|
||||
e.POST("/register/:handle/finish", handlers.HandleRegisterFinish)
|
||||
e.POST("/status", handlers.HandleStatusCheck)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package main
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
@@ -9,20 +9,20 @@ import (
|
||||
"github.com/sonr-io/motr/internal/ui/home"
|
||||
"github.com/sonr-io/motr/internal/ui/login"
|
||||
"github.com/sonr-io/motr/internal/ui/register"
|
||||
"github.com/sonr-io/motr/middleware/render"
|
||||
"github.com/sonr-io/motr/pkg/render"
|
||||
)
|
||||
|
||||
// ╭────────────────────────────────────────────────╮
|
||||
// │ HTTP Routes │
|
||||
// ╰────────────────────────────────────────────────╯
|
||||
|
||||
func setupViews(e *echo.Echo) {
|
||||
func RegisterViews(e *echo.Echo) {
|
||||
e.GET("/", render.Page(home.HomeView()))
|
||||
e.GET("/login", render.Page(login.LoginView()))
|
||||
e.GET("/register", render.Page(register.RegisterView()))
|
||||
}
|
||||
|
||||
func setupPartials(e *echo.Echo) {
|
||||
func RegisterPartials(e *echo.Echo) {
|
||||
e.POST("/login/:handle/check", handlers.HandleLoginCheck)
|
||||
e.POST("/login/:handle/finish", handlers.HandleLoginFinish)
|
||||
e.POST("/register/:handle", handlers.HandleRegisterStart)
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/sonr-io/motr/internal/ui/login"
|
||||
"github.com/sonr-io/motr/internal/ui/register"
|
||||
"github.com/sonr-io/motr/middleware/render"
|
||||
"github.com/sonr-io/motr/pkg/render"
|
||||
)
|
||||
|
||||
func HandleLoginCheck(c echo.Context) error {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/sonr-io/motr/internal/ui/home"
|
||||
"github.com/sonr-io/motr/internal/ui/login"
|
||||
"github.com/sonr-io/motr/internal/ui/register"
|
||||
"github.com/sonr-io/motr/middleware/render"
|
||||
"github.com/sonr-io/motr/pkg/render"
|
||||
)
|
||||
|
||||
func RenderHomePage(c echo.Context) error {
|
||||
|
||||
35
internal/api/client.go
Normal file
35
internal/api/client.go
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/syumai/workers/cloudflare/fetch"
|
||||
)
|
||||
|
||||
type Response interface {
|
||||
UnmarshalJSON(data []byte) error
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
MarketAPI
|
||||
}
|
||||
|
||||
type client struct {
|
||||
fc *fetch.Client
|
||||
ctx context.Context
|
||||
MarketAPI
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context) *client {
|
||||
fc := fetch.NewClient()
|
||||
c := &client{
|
||||
fc: fc,
|
||||
ctx: ctx,
|
||||
}
|
||||
marketAPI := NewMarketAPI(c, ctx)
|
||||
c.MarketAPI = marketAPI
|
||||
return c
|
||||
}
|
||||
@@ -1,18 +1,67 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package marketapi
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/syumai/workers/cloudflare/fetch"
|
||||
)
|
||||
|
||||
type Response interface {
|
||||
UnmarshalJSON(data []byte) error
|
||||
const (
|
||||
kCryptoAPIURL = "https://api.alternative.me"
|
||||
kCryptoAPIListings = "/v2/listings"
|
||||
kCryptoAPITickers = "/v2/ticker"
|
||||
kCryptoAPIGlobal = "/v2/global"
|
||||
)
|
||||
|
||||
type MarketAPI interface {
|
||||
Listings(symbol string) (*ListingsResponse, error)
|
||||
Ticker(symbol string) (*TickersResponse, error)
|
||||
GlobalMarket() (*GlobalMarketResponse, error)
|
||||
}
|
||||
|
||||
type marketAPI struct {
|
||||
client *client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewMarketAPI(c *client, ctx context.Context) *marketAPI {
|
||||
return &marketAPI{
|
||||
client: c,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *marketAPI) Listings(symbol string) (*ListingsResponse, error) {
|
||||
r := buildRequest(m.ctx, fmt.Sprintf("%s/%s", kCryptoAPIListings, symbol))
|
||||
v := &ListingsResponse{}
|
||||
err := doFetch(m.client.fc, r, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (m *marketAPI) Ticker(symbol string) (*TickersResponse, error) {
|
||||
r := buildRequest(m.ctx, fmt.Sprintf("%s/%s", kCryptoAPITickers, symbol))
|
||||
v := &TickersResponse{}
|
||||
err := doFetch(m.client.fc, r, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (m *marketAPI) GlobalMarket() (*GlobalMarketResponse, error) {
|
||||
r := buildRequest(m.ctx, kCryptoAPIGlobal)
|
||||
v := &GlobalMarketResponse{}
|
||||
err := doFetch(m.client.fc, r, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type ListingsResponse struct {
|
||||
@@ -66,33 +115,3 @@ type GlobalMarketResponse struct {
|
||||
func (r *GlobalMarketResponse) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, r)
|
||||
}
|
||||
|
||||
func (c *Context) buildRequest(url string) *fetch.Request {
|
||||
r, err := fetch.NewRequest(c.Request().Context(), http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
r.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/111.0")
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Context) fetch(r *fetch.Request, v Response) error {
|
||||
resp, err := c.client.Do(r, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close() // Ensure body is always closed
|
||||
|
||||
// Check for non-200 status codes
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Directly decode JSON into the response struct
|
||||
if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
|
||||
return fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
43
internal/api/utils.go
Normal file
43
internal/api/utils.go
Normal file
@@ -0,0 +1,43 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/syumai/workers/cloudflare/fetch"
|
||||
)
|
||||
|
||||
func buildRequest(c context.Context, url string) *fetch.Request {
|
||||
r, err := fetch.NewRequest(c, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
r.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/111.0")
|
||||
return r
|
||||
}
|
||||
|
||||
func doFetch(c *fetch.Client, r *fetch.Request, v Response) error {
|
||||
resp, err := c.Do(r, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close() // Ensure body is always closed
|
||||
|
||||
// Check for non-200 status codes
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Directly decode JSON into the response struct
|
||||
if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
|
||||
return fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -4,7 +4,7 @@ type EventTrigger string
|
||||
|
||||
var (
|
||||
EventTriggerMinute = EventTrigger("0 * * * * *") // Every minute (with seconds)
|
||||
EventTriggerHourly = EventTrigger("0 0 * * * *") // Every hour at minute 0
|
||||
EventTriggerHourly = EventTrigger("0 */1 * * *") // Every hour at minute 0
|
||||
EventTriggerDaily = EventTrigger("0 0 0 * * *") // Every day at 00:00:00
|
||||
EventTriggerWeekly = EventTrigger("0 0 0 * * 0") // Every Sunday at 00:00:00
|
||||
EventTriggerMonthly = EventTrigger("0 0 0 1 * *") // First day of every month at 00:00:00
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package marketapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/syumai/workers/cloudflare/fetch"
|
||||
)
|
||||
|
||||
const (
|
||||
kCryptoAPIURL = "https://api.alternative.me"
|
||||
kCryptoAPIListings = "/v2/listings"
|
||||
kCryptoAPITickers = "/v2/ticker"
|
||||
kCryptoAPIGlobal = "/v2/global"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
echo.Context
|
||||
client *fetch.Client
|
||||
}
|
||||
|
||||
func (c *Context) Listings(symbol string) (*ListingsResponse, error) {
|
||||
r := c.buildRequest(fmt.Sprintf("%s/%s", kCryptoAPIListings, symbol))
|
||||
v := &ListingsResponse{}
|
||||
err := c.fetch(r, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *Context) Ticker(symbol string) (*TickersResponse, error) {
|
||||
r := c.buildRequest(fmt.Sprintf("%s/%s", kCryptoAPITickers, symbol))
|
||||
v := &TickersResponse{}
|
||||
err := c.fetch(r, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *Context) GlobalMarket() (*GlobalMarketResponse, error) {
|
||||
r := c.buildRequest(kCryptoAPIGlobal)
|
||||
v := &GlobalMarketResponse{}
|
||||
err := c.fetch(r, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Middleware is a middleware that adds a new key to the context
|
||||
func Middleware() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
f := fetch.NewClient()
|
||||
return func(c echo.Context) error {
|
||||
ctx := &Context{
|
||||
Context: c,
|
||||
client: f,
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap unwraps the session context
|
||||
func Unwrap(c echo.Context) (*Context, error) {
|
||||
cc := c.(*Context)
|
||||
if cc == nil {
|
||||
return nil, errors.New("failed to unwrap session context")
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
@@ -6,11 +6,11 @@ package session
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/segmentio/ksuid"
|
||||
"github.com/sonr-io/motr/middleware/cookies"
|
||||
"github.com/sonr-io/motr/pkg/cookies"
|
||||
)
|
||||
|
||||
// Context is a session context
|
||||
type SessionContext struct {
|
||||
type Context struct {
|
||||
echo.Context
|
||||
ID string `json:"id"`
|
||||
}
|
||||
@@ -19,7 +19,7 @@ type SessionContext struct {
|
||||
func Middleware() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := &SessionContext{
|
||||
ctx := &Context{
|
||||
Context: c,
|
||||
ID: DetermineID(c),
|
||||
}
|
||||
@@ -29,8 +29,8 @@ func Middleware() echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// Unwrap unwraps the session context
|
||||
func Unwrap(c echo.Context) *SessionContext {
|
||||
cc := c.(*SessionContext)
|
||||
func Unwrap(c echo.Context) *Context {
|
||||
cc := c.(*Context)
|
||||
if cc == nil {
|
||||
panic("failed to unwrap session context")
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package sonrapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
kCryptoAPIURL = "https://api.alternative.me"
|
||||
kCryptoAPIListings = "/v2/listings"
|
||||
kCryptoAPITickers = "/v2/ticker"
|
||||
kCryptoAPIGlobal = "/v2/global"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
echo.Context
|
||||
}
|
||||
|
||||
// Middleware is a middleware that adds a new key to the context
|
||||
func Middleware() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := &Context{
|
||||
Context: c,
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap unwraps the session context
|
||||
func Unwrap(c echo.Context) (*Context, error) {
|
||||
cc := c.(*Context)
|
||||
if cc == nil {
|
||||
return nil, errors.New("failed to unwrap session context")
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
Reference in New Issue
Block a user