Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions backend/backend/get_matches.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package backend

import (
"fmt"
"io"
"net/http"
"os"

"github.com/gorilla/mux"
"github.com/joho/godotenv"
)

func init() {
err := godotenv.Load()
if err != nil {
panic("Error loading .env file")
}
PandaScoreAPIToken = os.Getenv("PANDASCORE_API_TOKEN")
}

var BaseURL = "https://api.pandascore.co"
var PandaScoreAPIToken string

func GetTeamFromID(w http.ResponseWriter, r *http.Request) {
teamID := mux.Vars(r)["teamID"]
fmt.Println("Fetching team with ID:", teamID)
url := fmt.Sprintf("%s/csgo/teams?filter[acronym]=%s", BaseURL, teamID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
http.Error(w, fmt.Sprintf("http.NewRequest(\"GET\", url, nil): %v", err), http.StatusInternalServerError)
return
}

req.Header.Add("Authorization", "Bearer "+PandaScoreAPIToken)
req.Header.Add("Accept", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("http.DefaultClient.Do(req): %v", err), http.StatusInternalServerError)
return
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
http.Error(w, fmt.Sprintf("unexpected status code: %d", res.StatusCode), res.StatusCode)
return
}

// Copy the response body directly to the client
w.Header().Set("Content-Type", "application/json")
if _, err := io.Copy(w, res.Body); err != nil {
http.Error(w, fmt.Sprintf("io.Copy(w, res.Body): %v", err), http.StatusInternalServerError)
return
}
}

func GetMatchByID(w http.ResponseWriter, r *http.Request) {
matchID := mux.Vars(r)["matchID"]
fmt.Println("Fetching match with ID:", matchID)
url := fmt.Sprintf("%s/csgo/matches/running?filter[id]=%s", BaseURL, matchID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
http.Error(w, fmt.Sprintf("http.NewRequest(\"GET\", url, nil): %v", err), http.StatusInternalServerError)
return
}

req.Header.Add("Authorization", "Bearer "+PandaScoreAPIToken)
req.Header.Add("Accept", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("http.DefaultClient.Do(req): %v", err), http.StatusInternalServerError)
return
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
http.Error(w, fmt.Sprintf("unexpected status code: %d", res.StatusCode), res.StatusCode)
return
}

w.Header().Set("Content-Type", "application/json")
if _, err := io.Copy(w, res.Body); err != nil {
http.Error(w, fmt.Sprintf("io.Copy(w, res.Body): %v", err), http.StatusInternalServerError)
return
}
}

func GetCurrentMatches(w http.ResponseWriter, r *http.Request) {
url := fmt.Sprintf("%s/matches/running", BaseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
http.Error(w, fmt.Sprintf("http.NewRequest(\"GET\", url, nil): %v", err), http.StatusInternalServerError)
return
}

req.Header.Add("Authorization", "Bearer "+PandaScoreAPIToken)
req.Header.Add("Accept", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("http.DefaultClient.Do(req): %v", err), http.StatusInternalServerError)
return
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
http.Error(w, fmt.Sprintf("unexpected status code: %d", res.StatusCode), res.StatusCode)
return
}

// Copy the response body directly to the client
w.Header().Set("Content-Type", "application/json")
if _, err := io.Copy(w, res.Body); err != nil {
http.Error(w, fmt.Sprintf("io.Copy(w, res.Body): %v", err), http.StatusInternalServerError)
return
}

fmt.Println("Successfully fetched matches from PandaScore API")
}

// SetupRoutes registers the REST API endpoints and returns a mux.Router.
func SetupRoutes() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/TeamFromID/{teamID}", GetTeamFromID).Methods("GET")
router.HandleFunc("/CurrentMatches", GetCurrentMatches).Methods("GET")
router.HandleFunc("/MatchByID/{matchID}", GetMatchByID).Methods("GET")
router.HandleFunc("/tx", AddTransaction).Methods("POST")
return router
}
103 changes: 103 additions & 0 deletions backend/backend/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package backend

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
)

type addTxRequest struct {
RawTx string `json:"rawTx"`
}

type jsonRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
ID int `json:"id"`
}

type jsonRPCError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}

type jsonRPCResponse struct {
JSONRPC string `json:"jsonrpc"`
Result string `json:"result"`
Error *jsonRPCError `json:"error,omitempty"`
ID int `json:"id"`
}

func AddTransaction(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

var req addTxRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, fmt.Sprintf("json.NewDecoder.Decode(): %v", err), http.StatusBadRequest)
return
}

if req.RawTx == "" || !strings.HasPrefix(req.RawTx, "0x") {
http.Error(w, "invalid rawTx: must be a non-empty hex string starting with 0x", http.StatusBadRequest)
return
}

rpcURL := os.Getenv("CLIENT_ETH")
if rpcURL == "" {
http.Error(w, "CLIENT_ETH not configured", http.StatusInternalServerError)
return
}

payload := jsonRPCRequest{
JSONRPC: "2.0",
Method: "eth_sendRawTransaction",
Params: []interface{}{req.RawTx},
ID: 1,
}

body, err := json.Marshal(payload)
if err != nil {
http.Error(w, fmt.Sprintf("json.Marshal(payload): %v", err), http.StatusInternalServerError)
return
}

resp, err := http.Post(rpcURL, "application/json", bytes.NewReader(body))
if err != nil {
http.Error(w, fmt.Sprintf("http.Post(rpcURL): %v", err), http.StatusBadGateway)
return
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
http.Error(w, fmt.Sprintf("io.ReadAll(resp.Body): %v", err), http.StatusInternalServerError)
return
}

if resp.StatusCode != http.StatusOK {
http.Error(w, fmt.Sprintf("upstream RPC error: status=%d body=%s", resp.StatusCode, string(respBody)), http.StatusBadGateway)
return
}

var rpcResp jsonRPCResponse
if err := json.Unmarshal(respBody, &rpcResp); err != nil {
http.Error(w, fmt.Sprintf("json.Unmarshal(respBody): %v", err), http.StatusInternalServerError)
return
}

if rpcResp.Error != nil {
http.Error(w, fmt.Sprintf("eth_sendRawTransaction error: code=%d message=%s", rpcResp.Error.Code, rpcResp.Error.Message), http.StatusBadGateway)
return
}

w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"txHash": rpcResp.Result})
}


8 changes: 8 additions & 0 deletions backend/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module esportoracle-backend

go 1.24.2

require (
github.com/gorilla/mux v1.8.1
github.com/joho/godotenv v1.5.1
)
4 changes: 4 additions & 0 deletions backend/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
33 changes: 33 additions & 0 deletions backend/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"esportoracle-backend/backend"
"log"
"net/http"
)

func enableCORS(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
}

func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
enableCORS(w, r)

if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}

next.ServeHTTP(w, r)
})
}

func main() {
router := backend.SetupRoutes()
handler := corsMiddleware(router)
log.Println("Server running on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
8 changes: 8 additions & 0 deletions front/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100
1 change: 1 addition & 0 deletions front/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
35 changes: 35 additions & 0 deletions front/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*.tsbuildinfo

test-results/
playwright-report/

package-lock.json
6 changes: 6 additions & 0 deletions front/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}
10 changes: 10 additions & 0 deletions front/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"recommendations": [
"Vue.volar",
"vitest.explorer",
"ms-playwright.playwright",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode"
]
}
Loading
Loading