Skip to content

Commit 928b4da

Browse files
committed
let's revive this old project with htmx?
1 parent cae5d05 commit 928b4da

File tree

8 files changed

+164
-98
lines changed

8 files changed

+164
-98
lines changed

go.mod

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1-
// +heroku goVersion go1.16
21
module github.com/monban/ucg
32

4-
go 1.16
3+
go 1.21
54

6-
require github.com/matryer/is v1.4.0
5+
require (
6+
github.com/charmbracelet/log v0.2.5
7+
github.com/matryer/is v1.4.0
8+
)
9+
10+
require (
11+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
12+
github.com/charmbracelet/lipgloss v0.8.0 // indirect
13+
github.com/go-logfmt/logfmt v0.6.0 // indirect
14+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
15+
github.com/mattn/go-isatty v0.0.18 // indirect
16+
github.com/mattn/go-runewidth v0.0.14 // indirect
17+
github.com/muesli/reflow v0.3.0 // indirect
18+
github.com/muesli/termenv v0.15.2 // indirect
19+
github.com/rivo/uniseg v0.2.0 // indirect
20+
golang.org/x/sys v0.7.0 // indirect
21+
)

go.sum

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,35 @@
1+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
2+
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
3+
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
4+
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
5+
github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7Wc=
6+
github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k=
7+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9+
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
10+
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
11+
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
12+
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
113
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
214
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
15+
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
16+
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
17+
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
18+
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
19+
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
20+
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
21+
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
22+
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
23+
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
24+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
25+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
26+
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
27+
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
28+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
29+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
30+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
31+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
32+
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
33+
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
35+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package main
22

33
import (
44
"fmt"
5-
"log"
5+
"github.com/charmbracelet/log"
66
"net/http"
77
"os"
88
)

mock_gamemanager_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package main
22

33
type MockGameManager struct {
4-
log printfer
4+
log logger
55
ListCall struct {
66
Receives struct {
77
}

routes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import "net/http"
55
func (s *server) routes() {
66
s.router.Handle(http.MethodGet, "/", s.rootHandler)
77
s.router.HandleFunc(http.MethodGet, "/games", s.getGames())
8+
s.router.Handle(http.MethodGet, "/games/", http.FileServer(http.Dir("./www")))
89
s.router.HandleFunc(http.MethodPost, "/games", s.createGameHandler())
9-
s.router.HandleFunc(http.MethodGet, "/games/", s.getGamesHandler())
1010
s.router.HandleFunc(http.MethodPost, "/games/", s.postGamesHandler())
1111
s.router.HandleFunc(http.MethodPost, "/users", s.newUser())
1212
}

server.go

Lines changed: 46 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@ import (
44
"encoding/json"
55
"fmt"
66
"io"
7-
"io/ioutil"
8-
"log"
97
"net/http"
108
"net/url"
9+
"os"
1110
"strconv"
1211
"strings"
1312
)
1413

1514
type server struct {
1615
router router
17-
log printfer
16+
log logger
1817
gm gameManager
1918
pm playerManager
2019
rootHandler http.Handler
@@ -26,8 +25,10 @@ type router interface {
2625
HandleFunc(string, string, func(http.ResponseWriter, *http.Request))
2726
}
2827

29-
type printfer interface {
28+
type logger interface {
3029
Printf(string, ...interface{})
30+
Info(any, ...any)
31+
Error(any, ...any)
3132
}
3233

3334
type gameManager interface {
@@ -42,8 +43,8 @@ type playerManager interface {
4243
NewPlayer(string) *Player
4344
}
4445

45-
func newServer(l printfer, gm gameManager, pm playerManager) (*server, error) {
46-
l.Printf("Setting up new server")
46+
func newServer(l logger, gm gameManager, pm playerManager) (*server, error) {
47+
l.Info("Setting up new server")
4748
s := &server{
4849
router: &methodRouter{},
4950
log: l,
@@ -57,7 +58,7 @@ func newServer(l printfer, gm gameManager, pm playerManager) (*server, error) {
5758

5859
func (s *server) handleRoot() http.HandlerFunc {
5960
return func(w http.ResponseWriter, r *http.Request) {
60-
i, err := ioutil.ReadFile("index.html")
61+
i, err := os.ReadFile("index.html")
6162
if err != nil {
6263
http.NotFound(w, r)
6364
return
@@ -67,14 +68,15 @@ func (s *server) handleRoot() http.HandlerFunc {
6768
}
6869

6970
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
70-
s.log.Printf("%v %v %v%v", r.Proto, r.Method, r.Host, r.URL)
71+
s.log.Info("request", "protocol", r.Proto, "method", r.Method, "host", r.Host, "uri", r.URL)
7172
s.router.ServeHTTP(w, r)
7273
}
7374

7475
func (s *server) getGames() http.HandlerFunc {
7576
return func(w http.ResponseWriter, r *http.Request) {
7677
data, err := json.Marshal(s.gm.List())
7778
if err != nil {
79+
s.log.Error("Error marshaling games", "err", err)
7880
http.Error(w, "Error marshaling games", http.StatusInternalServerError)
7981
return
8082
}
@@ -87,16 +89,16 @@ func (s *server) postGamesHandler() http.HandlerFunc {
8789
return func(w http.ResponseWriter, r *http.Request) {
8890
player, err := PlayerFromReq(r, s.pm)
8991
if err != nil {
90-
s.log.Printf(err.Error())
92+
s.log.Error("Error reading body", "err", err.Error())
9193
http.Error(w, "Error occured reading body", http.StatusBadRequest)
9294
return
9395
}
9496
pathElements := strings.Split(r.URL.Path, "/")
95-
s.log.Printf("Last element of path is %v", pathElements[len(pathElements)-1])
97+
s.log.Info("Last element of path is %v", pathElements[len(pathElements)-1])
9698
if pathElements[len(pathElements)-1] == "join" {
9799
gidint, err := strconv.ParseUint(pathElements[len(pathElements)-2], 10, 64)
98100
if err != nil {
99-
s.log.Printf(err.Error())
101+
s.log.Error("parsing game id", "err", err)
100102
http.Error(w, "Error occured parsing game id", http.StatusBadRequest)
101103
}
102104
s.gm.AddPlayerToGame(player, gameId(gidint))
@@ -136,51 +138,44 @@ func (s *server) getGamesHandler() http.HandlerFunc {
136138

137139
func (s *server) createGameHandler() http.HandlerFunc {
138140
return func(w http.ResponseWriter, r *http.Request) {
139-
var err error
140-
p, err := PlayerFromReq(r, s.pm)
141-
if err != nil {
142-
s.log.Printf(err.Error())
143-
http.Error(w, "Unable to create game with this user", http.StatusBadRequest)
144-
return
145-
}
146-
postData, _ := io.ReadAll(r.Body)
147-
var jd newGameData
148-
err = json.Unmarshal(postData, &jd)
149-
if err != nil {
150-
log.Printf("ERROR: failed to unmarshal postData: %v", err.Error())
151-
http.Error(w, "Unable to create game with provided data", http.StatusBadRequest)
152-
return
153-
}
154-
s.log.Printf("%v creating new game: %v\n", p, jd)
155-
g := s.gm.CreateGame(jd.Name, p)
156-
if g == nil {
157-
http.Error(w, "Unable to create game", http.StatusInternalServerError)
158-
return
159-
}
160-
w.Header().Set("Content-Type", "application/json")
161-
w.WriteHeader(http.StatusCreated)
162-
body, _ := json.Marshal(g)
163-
w.Write(body)
141+
// var err error
142+
// p, err := PlayerFromReq(r, s.pm)
143+
// if err != nil {
144+
// s.log.Error("PlayerFromReq", "err", err)
145+
// http.Error(w, "Unable to create game with this user", http.StatusBadRequest)
146+
// return
147+
// }
148+
// postData, _ := io.ReadAll(r.Body)
149+
// var jd newGameData
150+
// err = json.Unmarshal(postData, &jd)
151+
// if err != nil {
152+
// s.log.Error("ERROR: failed to unmarshal postData", "err", err)
153+
// http.Error(w, "Unable to create game with provided data", http.StatusBadRequest)
154+
// return
155+
// }
156+
// s.log.Info("creating new game: %v\n", "player", p, "gameData", jd)
157+
// g := s.gm.CreateGame(jd.Name, p)
158+
// if g == nil {
159+
// http.Error(w, "Unable to create game", http.StatusInternalServerError)
160+
// return
161+
// }
162+
// w.Header().Set("Content-Type", "application/json")
163+
// w.WriteHeader(http.StatusCreated)
164+
// body, _ := json.Marshal(g)
165+
// w.Write(body)
164166
}
165167
}
166168

167169
func (s *server) newUser() http.HandlerFunc {
168170
return func(w http.ResponseWriter, r *http.Request) {
169-
if r.Method != http.MethodPost {
170-
http.Error(w, "You can only POST to this endpoint", http.StatusMethodNotAllowed)
171-
return
172-
}
173-
body, _ := io.ReadAll(r.Body)
174-
s.log.Printf("%v", string(body))
175-
pd := struct{ Name string }{}
176-
err := json.Unmarshal(body, &pd)
171+
err := r.ParseForm()
177172
if err != nil {
178-
s.log.Printf("Unable to create player with data: %+v, error: %v", string(body), err.Error())
179-
http.Error(w, "Invalid format", http.StatusBadRequest)
180-
return
173+
s.log.Error("Unable to parse form", "err", err)
181174
}
182-
p := s.pm.NewPlayer(pd.Name)
183-
s.log.Printf("Creating new player: %v(%d)", p.Name, p.Id)
175+
data := r.PostForm
176+
s.log.Info("newUser", "data", data)
177+
p := s.pm.NewPlayer(data.Get("name"))
178+
s.log.Info("Creating new player", "name", p.Name, "id", p.Id)
184179
rbody, _ := json.Marshal(p)
185180
w.WriteHeader(http.StatusCreated)
186181
w.Write(rbody)
@@ -212,11 +207,11 @@ func PlayerFromReq(r *http.Request, pm playerManager) (*Player, error) {
212207
pidstring := r.Header.Get("X-Player-Id")
213208
pidint, err := strconv.ParseUint(pidstring, 10, 64)
214209
if err != nil {
215-
return nil, err
210+
return nil, fmt.Errorf("unable to parse uint from string ''%s': %w", pidstring, err)
216211
}
217212
player, err := pm.FindPlayer(PlayerId(pidint))
218213
if err != nil {
219-
return nil, err
214+
return nil, fmt.Errorf("unable to find player with pid %d: %w", pidint, err)
220215
}
221216
return player, nil
222217
}

www/games/new.html

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>Untitled Card Game</title>
6+
<link rel="stylesheet" href="/css/index.css">
7+
<link rel="license" href="https://www.gnu.org/licenses/agpl-3.0.en.html">
8+
<script src="https://unpkg.com/[email protected]"
9+
integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"
10+
crossorigin="anonymous"></script>
11+
</head>
12+
13+
<body hx-boot="true">
14+
<main id="new-game">
15+
<h2>Create Game</h2>
16+
<form id="form-game-create" method="post" action="/games">
17+
<p>
18+
<label for="input-game-name">Name</label>
19+
<input name="gameName" type="text" id="input-text-game-name" required>
20+
</p>
21+
<button>Create Game</button>
22+
</form>
23+
<input type="button" value="Exit" id="exit-new-game">
24+
</main>
25+
</body>
26+
27+
</html>

www/index.html

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,42 @@
1+
<!DOCTYPE html>
12
<html>
2-
<head>
3-
<title>Untitled Card Game</title>
4-
<link rel="stylesheet" href="/css/index.css">
5-
<link rel="license" href="https://www.gnu.org/licenses/agpl-3.0.en.html">
6-
</head>
7-
<body>
8-
<script src="/js/index.js" defer></script>
9-
<main id="game-list" hidden>
10-
<h2>Games</h2>
11-
<img src="/img/4213447-arrow-load-loading-refresh-reload-restart-sync_115423.svg" alt="refresh" width=24 id="refresh-button">
12-
<ul id="games"></ul>
13-
<input type="button" value="Create Game" id="create-game-button">
14-
</main>
153

16-
<main id="new-game" hidden>
17-
<h2>Create Game</h2>
18-
<form id="form-game-create">
19-
<p>
20-
<label for="input-game-name">Name</label>
21-
<input name="gameName" type="text" id="input-text-game-name">
22-
</p>
23-
<button>Create Game</button>
24-
</form>
25-
<input type="button" value="Exit" id="exit-new-game">
26-
</main>
4+
<head>
5+
<title>Untitled Card Game</title>
6+
<link rel="stylesheet" href="/css/index.css">
7+
<link rel="license" href="https://www.gnu.org/licenses/agpl-3.0.en.html">
8+
<script src="https://unpkg.com/[email protected]"
9+
integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"
10+
crossorigin="anonymous"></script>
11+
</head>
2712

28-
<main id="game" hidden>
29-
</main>
13+
<body hx-boot="true">
14+
<main id="game-list">
15+
<h2>Games</h2>
16+
<img src="/img/4213447-arrow-load-loading-refresh-reload-restart-sync_115423.svg" alt="refresh" width=24
17+
id="refresh-button">
18+
<ul id="games"></ul>
19+
<a href="/games/new.html">Create Game</a>
20+
</main>
3021

31-
<main id="game-preview" hidden>
32-
<h2 id="game-name"></h2>
33-
<ul id="game-players"></ul>
34-
<input type="button" value="Join" id="input-button-join-game">
35-
<input type="button">
36-
</main>
3722

38-
<main id="login" hidden>
39-
<h2>Who are you?</h2>
40-
<form id="form-login">
41-
<input type="text" name="userName">
42-
<button>Okay</button>
43-
</form>
44-
</main>
45-
</body>
46-
</html>
23+
<main id="game" hidden>
24+
</main>
25+
26+
<main id="game-preview" hidden>
27+
<h2 id="game-name"></h2>
28+
<ul id="game-players"></ul>
29+
<input type="button" value="Join" id="input-button-join-game">
30+
<input type="button">
31+
</main>
32+
33+
<main id="login" hidden>
34+
<h2>Who are you?</h2>
35+
<form id="form-login">
36+
<input type="text" name="userName">
37+
<button>Okay</button>
38+
</form>
39+
</main>
40+
</body>
41+
42+
</html>

0 commit comments

Comments
 (0)