aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/main.go
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2024-07-19 19:05:55 +0900
committernsfisis <nsfisis@gmail.com>2024-07-19 19:19:55 +0900
commitdf5abfc272a151c51f0e5e82214cf7aff8cfa880 (patch)
tree87395be420f16296fab56b55a03f83f87af59366 /backend/main.go
parentb0662e8add4864fed69f49a4a5cfb0d8e26523a8 (diff)
downloadiosdc-japan-2025-albatross-df5abfc272a151c51f0e5e82214cf7aff8cfa880.tar.gz
iosdc-japan-2025-albatross-df5abfc272a151c51f0e5e82214cf7aff8cfa880.tar.zst
iosdc-japan-2025-albatross-df5abfc272a151c51f0e5e82214cf7aff8cfa880.zip
initial commit
Diffstat (limited to 'backend/main.go')
-rw-r--r--backend/main.go259
1 files changed, 259 insertions, 0 deletions
diff --git a/backend/main.go b/backend/main.go
new file mode 100644
index 0000000..68df25b
--- /dev/null
+++ b/backend/main.go
@@ -0,0 +1,259 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/jmoiron/sqlx"
+ _ "github.com/lib/pq"
+)
+
+type Config struct {
+ dbHost string
+ dbPort string
+ dbUser string
+ dbPassword string
+ dbName string
+}
+
+var config *Config
+
+var db *sqlx.DB
+
+func loadEnv() (*Config, error) {
+ dbHost, exists := os.LookupEnv("ALBATROSS_DB_HOST")
+ if !exists {
+ return nil, fmt.Errorf("ALBATROSS_DB_HOST not set")
+ }
+ dbPort, exists := os.LookupEnv("ALBATROSS_DB_PORT")
+ if !exists {
+ return nil, fmt.Errorf("ALBATROSS_DB_PORT not set")
+ }
+ dbUser, exists := os.LookupEnv("ALBATROSS_DB_USER")
+ if !exists {
+ return nil, fmt.Errorf("ALBATROSS_DB_USER not set")
+ }
+ dbPassword, exists := os.LookupEnv("ALBATROSS_DB_PASSWORD")
+ if !exists {
+ return nil, fmt.Errorf("ALBATROSS_DB_PASSWORD not set")
+ }
+ dbName, exists := os.LookupEnv("ALBATROSS_DB_NAME")
+ if !exists {
+ return nil, fmt.Errorf("ALBATROSS_DB_NAME not set")
+ }
+ return &Config{
+ dbHost: dbHost,
+ dbPort: dbPort,
+ dbUser: dbUser,
+ dbPassword: dbPassword,
+ dbName: dbName,
+ }, nil
+}
+
+const (
+ gameTypeGolf = "golf"
+ gameTypeRace = "race"
+)
+
+const (
+ gameStateWaiting = "waiting"
+ gameStateReady = "ready"
+ gameStatePlaying = "playing"
+ gameStateFinished = "finished"
+)
+
+type Game struct {
+ GameID int `db:"game_id"`
+ // "golf" or "race"
+ Type string `db:"type"`
+ CreatedAt string `db:"created_at"`
+ State string `db:"state"`
+}
+
+func initDB() error {
+ _, err := db.Exec(`
+ CREATE TABLE IF NOT EXISTS games (
+ game_id SERIAL PRIMARY KEY,
+ type VARCHAR(255) NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ state VARCHAR(255) NOT NULL
+ );
+ `)
+ return err
+}
+
+var gameHubs = map[int]*GameHub{}
+
+func startGame(game *Game) {
+ if gameHubs[game.GameID] != nil {
+ return
+ }
+ gameHubs[game.GameID] = NewGameHub(game)
+ go gameHubs[game.GameID].Run()
+}
+
+func handleGolfPost(w http.ResponseWriter, r *http.Request) {
+ var yourTeam string
+ waitingGolfGames := []Game{}
+ err := db.Select(&waitingGolfGames, "SELECT * FROM games WHERE type = $1 AND state = $2 ORDER BY created_at", gameTypeGolf, gameStateWaiting)
+ if err != nil {
+ http.Error(w, "Error getting games", http.StatusInternalServerError)
+ return
+ }
+ if len(waitingGolfGames) == 0 {
+ _, err = db.Exec("INSERT INTO games (type, state) VALUES ($1, $2)", gameTypeGolf, gameStateWaiting)
+ if err != nil {
+ http.Error(w, "Error creating game", http.StatusInternalServerError)
+ return
+ }
+ waitingGolfGames = []Game{}
+ err = db.Select(&waitingGolfGames, "SELECT * FROM games WHERE type = $1 AND state = $2 ORDER BY created_at", gameTypeGolf, gameStateWaiting)
+ if err != nil {
+ http.Error(w, "Error getting games", http.StatusInternalServerError)
+ return
+ }
+ yourTeam = "a"
+ startGame(&waitingGolfGames[0])
+ } else {
+ yourTeam = "b"
+ db.Exec("UPDATE games SET state = $1 WHERE game_id = $2", gameStateReady, waitingGolfGames[0].GameID)
+ }
+ waitingGame := waitingGolfGames[0]
+
+ http.Redirect(w, r, fmt.Sprintf("/golf/%d/%s/", waitingGame.GameID, yourTeam), http.StatusSeeOther)
+}
+
+func handleRacePost(w http.ResponseWriter, r *http.Request) {
+ http.Redirect(w, r, "/race/1/a/", http.StatusSeeOther)
+}
+
+func main() {
+ var err error
+ config, err = loadEnv()
+ if err != nil {
+ fmt.Printf("Error loading env %v", err)
+ return
+ }
+
+ for i := 0; i < 5; i++ {
+ db, err = sqlx.Connect("postgres", fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", config.dbHost, config.dbPort, config.dbUser, config.dbPassword, config.dbName))
+ if err == nil {
+ break
+ }
+ time.Sleep(5 * time.Second)
+ }
+ if err != nil {
+ log.Fatalf("Error connecting to db %v", err)
+ }
+ defer db.Close()
+
+ err = initDB()
+ if err != nil {
+ log.Fatalf("Error initializing db %v", err)
+ }
+
+ server := http.NewServeMux()
+
+ server.HandleFunc("GET /js/", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public"+r.URL.Path)
+ })
+
+ server.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public/index.html")
+ })
+
+ server.HandleFunc("GET /golf/{$}", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public/golf/index.html")
+ })
+
+ server.HandleFunc("POST /golf/{$}", func(w http.ResponseWriter, r *http.Request) {
+ handleGolfPost(w, r)
+ })
+
+ server.HandleFunc("GET /golf/{gameId}/watch/{$}", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public/golf/watch.html")
+ })
+
+ server.HandleFunc("GET /sock/golf/{gameId}/watch/{$}", func(w http.ResponseWriter, r *http.Request) {
+ gameId := r.PathValue("gameId")
+ gameIdInt, err := strconv.Atoi(gameId)
+ if err != nil {
+ http.Error(w, "Invalid game id", http.StatusBadRequest)
+ return
+ }
+ var hub *GameHub
+ for _, h := range gameHubs {
+ if h.game.GameID == gameIdInt {
+ hub = h
+ break
+ }
+ }
+ if hub == nil {
+ http.Error(w, "Game not found", http.StatusNotFound)
+ return
+ }
+ serveWsWatcher(hub, w, r)
+ })
+
+ server.HandleFunc("GET /golf/{gameId}/{team}/{$}", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public/golf/game.html")
+ })
+
+ server.HandleFunc("GET /sock/golf/{gameId}/{team}/{$}", func(w http.ResponseWriter, r *http.Request) {
+ gameId := r.PathValue("gameId")
+ gameIdInt, err := strconv.Atoi(gameId)
+ if err != nil {
+ http.Error(w, "Invalid game id", http.StatusBadRequest)
+ return
+ }
+ var hub *GameHub
+ for _, h := range gameHubs {
+ if h.game.GameID == gameIdInt {
+ hub = h
+ break
+ }
+ }
+ if hub == nil {
+ http.Error(w, "Game not found", http.StatusNotFound)
+ return
+ }
+ team := r.PathValue("team")
+ serveWs(hub, w, r, team)
+ })
+
+ server.HandleFunc("GET /race/{$}", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public/race/index.html")
+ })
+
+ server.HandleFunc("POST /race/{$}", func(w http.ResponseWriter, r *http.Request) {
+ handleRacePost(w, r)
+ })
+
+ server.HandleFunc("GET /race/{gameId}/watch/{$}", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public/race/watch.html")
+ })
+
+ server.HandleFunc("GET /sock/race/{gameId}/watch/{$}", func(w http.ResponseWriter, r *http.Request) {
+ // TODO
+ })
+
+ server.HandleFunc("GET /race/{gameId}/{team}/{$}", func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, "./public/race/game.html")
+ })
+
+ server.HandleFunc("GET /sock/race/{gameId}/{team}/{$}", func(w http.ResponseWriter, r *http.Request) {
+ // TODO
+ })
+
+ defer func() {
+ for _, hub := range gameHubs {
+ hub.Close()
+ }
+ }()
+
+ http.ListenAndServe(":80", server)
+}