Files
sprintpadawan/main.go
T
2026-04-23 22:33:33 -06:00

167 lines
4.3 KiB
Go

package main
import (
"context"
"encoding/json"
"html/template"
"log"
"net/http"
"path/filepath"
"time"
"sprintpadawan/lib"
)
type PingResponse struct {
Status string `json:"status"`
Message string `json:"message"`
}
var templates *template.Template
type contextKey string
const userKey contextKey = "user"
func main() {
lib.InitDB()
var err error
templates, err = template.ParseGlob(filepath.Join("templates", "*.html"))
if err != nil {
log.Fatalf("failed to parse templates: %v", err)
}
mux := http.NewServeMux()
// serve static assets
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// public routes
mux.HandleFunc("/login", handleLogin)
mux.HandleFunc("/register", handleRegister)
mux.HandleFunc("/logout", handleLogout)
// private routes
mux.HandleFunc("/", requireAuth(handleIndex))
mux.HandleFunc("/api/ping", requireAuth(handlePing))
mux.HandleFunc("/api/ping-partial", requireAuth(handlePingPartial))
addr := ":8080"
log.Printf("Starting SprintPadawan server on http://localhost%s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatalf("server error: %v", err)
}
}
func requireAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_token")
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
user, err := lib.GetUserFromSession(cookie.Value)
if err != nil || user == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
ctx := context.WithValue(r.Context(), userKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
}
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
templates.ExecuteTemplate(w, "login.html", nil)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
user, err := lib.GetUserByUsername(username)
if err != nil || !lib.CheckPasswordHash(password, user.PasswordHash) {
templates.ExecuteTemplate(w, "login.html", map[string]string{"Error": "Invalid credentials"})
return
}
sessionID, _ := lib.CreateSession(user.ID)
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: sessionID,
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Path: "/",
})
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func handleRegister(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
templates.ExecuteTemplate(w, "register.html", nil)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
confirm := r.FormValue("confirm_password")
if password != confirm {
templates.ExecuteTemplate(w, "register.html", map[string]string{"Error": "Passwords do not match"})
return
}
err := lib.CreateUser(username, password)
if err != nil {
templates.ExecuteTemplate(w, "register.html", map[string]string{"Error": "Username taken"})
return
}
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
func handleLogout(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_token")
if err == nil {
lib.DeleteSession(cookie.Value)
}
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Expires: time.Now().Add(-1 * time.Hour),
Path: "/",
})
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
func handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
user := r.Context().Value(userKey).(*lib.User)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := templates.ExecuteTemplate(w, "index.html", user); err != nil {
log.Printf("template error: %v", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
}
}
func handlePing(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
resp := PingResponse{Status: "ok", Message: "pong"}
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("json encode error: %v", err)
}
}
func handlePingPartial(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := templates.ExecuteTemplate(w, "ping_result.html", PingResponse{Status: "ok", Message: "pong"}); err != nil {
log.Printf("template error: %v", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
}
}