Browse Source

Refactoring for structure

pull/1/head
Stephanie Gredell 7 months ago
parent
commit
ec4c7a53e5
  1. 2
      Procfile
  2. 2
      air.toml
  3. 33
      cmd/systemdesigngame/main.go
  4. 133
      internal/auth/auth.go
  5. 0
      internal/db/progress.go
  6. 0
      internal/db/subscriptions.go
  7. 0
      internal/db/users.go
  8. 0
      internal/design/design.go
  9. 0
      internal/level/level.go
  10. 0
      internal/level/levels_test.go
  11. 51
      internal/server/server.go
  12. 17
      router/handlers/home.go
  13. 47
      router/handlers/play.go
  14. 20
      router/router.go

2
Procfile

@ -1 +1 @@ @@ -1 +1 @@
web: go run main.go
web: go run ./cmd/systemdesigngame

2
air.toml

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
# .air.toml
[build]
cmd = "go build -o ./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/systemdesigngame"
bin = "tmp/main"
full_bin = "tmp/main"
include_ext = ["go", "tpl", "tmpl", "html", "js"]

33
cmd/systemdesigngame/main.go

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
package main
import (
"github.com/joho/godotenv"
"html/template"
"net/http"
"os"
"systemdesigngame/internals/auth"
"systemdesigngame/internals/router"
"systemdesigngame/internals/server"
)
func main() {
err := godotenv.Load()
if err != nil {
panic("failed to load .env")
}
// set up JWT secret used for authentication
auth.JwtSecret = []byte(os.Getenv("JWT_SECRET"))
tmpl := template.Must(template.ParseGlob("static/*.html"))
server.InitApp()
mux := app.SetupRoutes(tmpl)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
}
server.GracefulShutdown(srv)
}

133
main.go → internal/auth/auth.go

@ -1,126 +1,33 @@ @@ -1,126 +1,33 @@
package main
package auth
import (
"context"
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"systemdesigngame/internals/level"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/joho/godotenv"
)
var jwtSecret []byte
var tmpl = template.Must(template.ParseGlob("static/*.html"))
type contextKey string
var JwtSecret []byte
const (
userIDKey = contextKey("userID")
userLoginKey = contextKey("userLogin")
userAvatarKey = contextKey("userAvatar")
UserIDKey = contextKey("userID")
UserLoginKey = contextKey("userLogin")
UserAvatarKey = contextKey("userAvatar")
)
func main() {
err := godotenv.Load()
if err != nil {
panic("failed to load .env")
}
jwtSecret = []byte(os.Getenv("JWT_SECRET"))
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.HandleFunc("/", index)
mux.HandleFunc("/play/", requireAuth(play))
mux.HandleFunc("/login", loginHandler)
mux.HandleFunc("/callback", callbackHandler)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
}
levels, err := level.LoadLevels("data/levels.json")
if err != nil {
panic("failed to load levels: " + err.Error())
}
level.InitRegistry(levels)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Println("Server failed: " + err.Error())
}
}()
<-ctx.Done()
stop()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(shutdownCtx)
}
func index(w http.ResponseWriter, r *http.Request) {
data := struct {
Title string
}{
Title: "Title",
}
if err := tmpl.ExecuteTemplate(w, "index.html", data); err != nil {
http.Error(w, "Template Error", http.StatusInternalServerError)
}
}
func play(w http.ResponseWriter, r *http.Request) {
levelName := r.URL.Path[len("/play/"):]
levelName, err := url.PathUnescape(levelName)
avatar := r.Context().Value(userAvatarKey).(string)
username := r.Context().Value(userLoginKey).(string)
if err != nil {
http.Error(w, "Invalid level name", http.StatusBadRequest)
return
}
lvl, err := level.GetLevel(strings.ToLower(levelName), level.DifficultyEasy)
if err != nil {
http.Error(w, "Level not found"+err.Error(), http.StatusNotFound)
return
}
allLevels := level.AllLevels()
data := struct {
Levels []level.Level
Level *level.Level
Avatar string
Username string
}{
Levels: allLevels,
Level: lvl,
Avatar: avatar,
Username: username,
}
tmpl.ExecuteTemplate(w, "game.html", data)
}
type contextKey string
func loginHandler(w http.ResponseWriter, r *http.Request) {
func LoginHandler(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("auth_token")
if err == nil {
token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
return JwtSecret, nil
})
if err != nil && token.Valid {
@ -140,7 +47,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) { @@ -140,7 +47,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, url, http.StatusFound)
}
func callbackHandler(w http.ResponseWriter, r *http.Request) {
func CallbackHandler(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "No code in query", http.StatusBadRequest)
@ -200,7 +107,7 @@ func callbackHandler(w http.ResponseWriter, r *http.Request) { @@ -200,7 +107,7 @@ func callbackHandler(w http.ResponseWriter, r *http.Request) {
// Generate JWT
fmt.Printf("generating jwt: %s, %s, %s\n", fmt.Sprintf("%d", userInfo.ID), userInfo.Login, userInfo.AvatarUrl)
jwtToken, err := generateJWT(fmt.Sprintf("%d", userInfo.ID), userInfo.Login, userInfo.AvatarUrl)
jwtToken, err := GenerateJWT(fmt.Sprintf("%d", userInfo.ID), userInfo.Login, userInfo.AvatarUrl)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
@ -218,7 +125,7 @@ func callbackHandler(w http.ResponseWriter, r *http.Request) { @@ -218,7 +125,7 @@ func callbackHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/play", http.StatusFound)
}
func generateJWT(userID string, login string, avatarUrl string) (string, error) {
func GenerateJWT(userID string, login string, avatarUrl string) (string, error) {
claims := jwt.MapClaims{
"sub": userID,
"login": login,
@ -227,11 +134,11 @@ func generateJWT(userID string, login string, avatarUrl string) (string, error) @@ -227,11 +134,11 @@ func generateJWT(userID string, login string, avatarUrl string) (string, error)
"iat": time.Now().Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
return token.SignedString(JwtSecret)
}
func requireAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func RequireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("auth_token")
if err != nil {
http.Redirect(w, r, "/login", http.StatusFound)
@ -239,7 +146,7 @@ func requireAuth(next http.HandlerFunc) http.HandlerFunc { @@ -239,7 +146,7 @@ func requireAuth(next http.HandlerFunc) http.HandlerFunc {
}
token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
return JwtSecret, nil
})
if err != nil || !token.Valid {
@ -261,9 +168,9 @@ func requireAuth(next http.HandlerFunc) http.HandlerFunc { @@ -261,9 +168,9 @@ func requireAuth(next http.HandlerFunc) http.HandlerFunc {
return
}
ctx := context.WithValue(r.Context(), userIDKey, userID)
ctx = context.WithValue(ctx, userLoginKey, login)
ctx = context.WithValue(ctx, userAvatarKey, avatar)
next(w, r.WithContext(ctx))
}
ctx := context.WithValue(r.Context(), UserIDKey, userID)
ctx = context.WithValue(ctx, UserLoginKey, login)
ctx = context.WithValue(ctx, UserAvatarKey, avatar)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

0
internals/db/progress.go → internal/db/progress.go

0
internals/db/subscriptions.go → internal/db/subscriptions.go

0
internals/db/users.go → internal/db/users.go

0
internals/design/design.go → internal/design/design.go

0
internals/level/level.go → internal/level/level.go

0
internals/level/levels_test.go → internal/level/levels_test.go

51
internal/server/server.go

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
package server
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"systemdesigngame/internals/auth"
"systemdesigngame/internals/level"
"time"
"github.com/joho/godotenv"
)
func InitApp() {
if err := godotenv.Load(); err != nil {
log.Fatal("failed to load .env")
}
auth.JwtSecret = []byte(os.Getenv("JWT_SECRET"))
levels, err := level.LoadLevels("data/levels.json")
if err != nil {
log.Fatal("failed to load levels: " + err.Error())
}
level.InitRegistry(levels)
}
func GracefulShutdown(srv *http.Server) {
// setup graceful shutdown
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// start http server in a separate goroutine
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Println("Server failed: " + err.Error())
}
}()
// wait for shutdown signal
<-ctx.Done()
stop()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(shutdownCtx)
}

17
router/handlers/home.go

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
package handlers
import (
"html/template"
"net/http"
)
type HomeHandler struct {
Tmpl *template.Template
}
func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
data := struct{ Title string }{Title: "Title"}
if err := h.Tmpl.ExecuteTemplate(w, "index.html", data); err != nil {
http.Error(w, "Template Error", http.StatusInternalServerError)
}
}

47
router/handlers/play.go

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
package handlers
import (
"html/template"
"net/http"
"net/url"
"strings"
"systemdesigngame/internals/auth"
"systemdesigngame/internals/level"
)
type PlayHandler struct {
Tmpl *template.Template
}
func (h *PlayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
levelName := strings.TrimPrefix(r.URL.Path, "/play/")
levelName, err := url.PathUnescape(levelName)
if err != nil {
http.Error(w, "Invalid level name", http.StatusBadRequest)
return
}
username := r.Context().Value(auth.UserLoginKey).(string)
avatar := r.Context().Value(auth.UserAvatarKey).(string)
lvl, err := level.GetLevel(strings.ToLower(levelName), level.DifficultyEasy)
if err != nil {
http.Error(w, "Level not found: "+err.Error(), http.StatusNotFound)
return
}
allLevels := level.AllLevels()
data := struct {
Levels []level.Level
Level *level.Level
Avatar string
Username string
}{
Levels: allLevels,
Level: lvl,
Avatar: avatar,
Username: username,
}
h.Tmpl.ExecuteTemplate(w, "game.html", data)
}

20
router/router.go

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package app
import (
"html/template"
"net/http"
"systemdesigngame/internal/auth"
"systemdesigngame/router/handlers"
)
func SetupRoutes(tmpl *template.Template) *http.ServeMux {
// initialize http routes and handlers
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.Handle("/", &handlers.HomeHandler{Tmpl: tmpl})
mux.Handle("/play/", auth.RequireAuth(&handlers.PlayHandler{Tmpl: tmpl}))
mux.HandleFunc("/login", auth.LoginHandler)
mux.HandleFunc("/callback", auth.CallbackHandler)
return mux
}
Loading…
Cancel
Save