diff --git a/handlers.go b/handlers.go index f5b3a8c..068ee0a 100644 --- a/handlers.go +++ b/handlers.go @@ -3,9 +3,11 @@ package main import ( "encoding/json" "html" + "log" "math/rand" "net/http" "strconv" + "strings" "time" "github.com/google/uuid" @@ -63,19 +65,21 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { } // ---> HIER RUFEN WIR JETZT DIE SIMULATION AUF <--- - isDead, score, obstacles := simulateChunk(req.SessionID, req.Inputs, req.TotalTicks, vals) + isDead, score, obstacles, powerUpState, serverTick := simulateChunk(req.SessionID, req.Inputs, req.TotalTicks, vals) status := "alive" if isDead { status = "dead" rdb.HSet(ctx, key, "is_dead", 1) } - rdb.Expire(ctx, key, 4000*time.Hour) + rdb.Expire(ctx, key, 1*time.Hour) json.NewEncoder(w).Encode(ValidateResponse{ Status: status, VerifiedScore: score, ServerObs: obstacles, + PowerUps: powerUpState, + ServerTick: serverTick, }) } @@ -86,8 +90,19 @@ func handleSubmitName(w http.ResponseWriter, r *http.Request) { return } + // Validierung + if len(req.Name) > 4 { + http.Error(w, "Zu lang", 400) + return + } + if containsBadWord(req.Name) { + http.Error(w, "Name verboten", 400) + return + } + safeName := html.EscapeString(req.Name) sessionKey := "session:" + req.SessionID + scoreVal, err := rdb.HGet(ctx, sessionKey, "score").Result() if err != nil { http.Error(w, "Session expired", 404) @@ -104,10 +119,13 @@ func handleSubmitName(w http.ResponseWriter, r *http.Request) { "created_at": timestamp, }) - rdb.ZAdd(ctx, "leaderboard:unverified", redis.Z{ - Score: float64(scoreInt), - Member: req.SessionID, - }) + // Leaderboard Eintrag + rdb.ZAdd(ctx, "leaderboard:unverified", redis.Z{Score: float64(scoreInt), Member: req.SessionID}) + rdb.ZAdd(ctx, "leaderboard:public", redis.Z{Score: float64(scoreInt), Member: req.SessionID}) + + rdb.Persist(ctx, sessionKey) + + rdb.HDel(ctx, sessionKey, "obstacles", "rng_state", "pos_y", "vel_y", "p_god_lives", "p_has_bat", "p_boot_ticks") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(SubmitResponse{ClaimCode: claimCode}) @@ -191,6 +209,9 @@ func handleAdminAction(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad Request", 400) return } + + log.Printf("👮 ADMIN ACTION: %s on %s", req.Action, req.SessionID) + if req.Action == "approve" { score, err := rdb.ZScore(ctx, "leaderboard:unverified", req.SessionID).Result() if err == nil { @@ -200,7 +221,10 @@ func handleAdminAction(w http.ResponseWriter, r *http.Request) { } else if req.Action == "delete" { rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) rdb.ZRem(ctx, "leaderboard:public", req.SessionID) + + rdb.Del(ctx, "session:"+req.SessionID) } + w.WriteHeader(http.StatusOK) } @@ -210,16 +234,28 @@ func handleClaimDelete(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad Request", 400) return } + sessionKey := "session:" + req.SessionID realCode, err := rdb.HGet(ctx, sessionKey, "claim_code").Result() - if err != nil || realCode != req.ClaimCode { - http.Error(w, "Error", 403) + if err != nil || realCode == "" { + http.Error(w, "Not found", 404) return } + + if realCode != req.ClaimCode { + http.Error(w, "Wrong Code", 403) + return + } + + log.Printf("🗑️ USER DELETE: Session %s deleted via code", req.SessionID) + + // Aus Listen entfernen rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) rdb.ZRem(ctx, "leaderboard:public", req.SessionID) - rdb.HDel(ctx, sessionKey, "name") + + rdb.Del(ctx, sessionKey) + w.WriteHeader(http.StatusOK) } @@ -231,3 +267,37 @@ func generateClaimCode() string { } return string(b) } + +func handleAdminBadwords(w http.ResponseWriter, r *http.Request) { + key := "config:badwords" + + // GET: Liste abrufen + if r.Method == http.MethodGet { + words, _ := rdb.SMembers(ctx, key).Result() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(words) + return + } + + // POST: Hinzufügen oder Löschen + if r.Method == http.MethodPost { + // Wir nutzen ein einfaches Struct für den Request + type WordReq struct { + Word string `json:"word"` + Action string `json:"action"` // "add" oder "remove" + } + var req WordReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Bad Request", 400) + return + } + + if req.Action == "add" && req.Word != "" { + rdb.SAdd(ctx, key, strings.ToLower(req.Word)) + } else if req.Action == "remove" && req.Word != "" { + rdb.SRem(ctx, key, strings.ToLower(req.Word)) + } + + w.WriteHeader(http.StatusOK) + } +} diff --git a/main.go b/main.go index d73215c..c2421a6 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ func main() { } initGameConfig() + initBadWords() fs := http.FileServer(http.Dir("./static")) http.Handle("/", fs) @@ -44,6 +45,7 @@ func main() { // Admin Routes (Logger + BasicAuth kombinieren) http.HandleFunc("/admin", Logger(BasicAuth(handleAdminPage))) + http.HandleFunc("/api/admin/badwords", Logger(BasicAuth(handleAdminBadwords))) http.HandleFunc("/api/admin/list", Logger(BasicAuth(handleAdminList))) http.HandleFunc("/api/admin/action", Logger(BasicAuth(handleAdminAction))) diff --git a/secure/admin.html b/secure/admin.html index 1e4a820..95549f2 100644 --- a/secure/admin.html +++ b/secure/admin.html @@ -12,7 +12,7 @@ input[type="text"] { padding: 10px; font-size: 16px; width: 250px; - background: #333; border: 1px solid #666; color: white; + background: #333; border: 1px solid #666; color: white; outline: none; } /* Tabs */ @@ -22,14 +22,15 @@ } .tab-btn.active { background: #4caf50; color: white; } .tab-btn#tab-public.active { background: #2196F3; } /* Blau für Public */ + .tab-btn#tab-badwords.active { background: #f44336; } /* Rot für Badwords */ - /* Liste */ + /* Normale Liste */ .entry { background: #333; padding: 15px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; border-left: 5px solid #555; } - .entry.highlight { border-left-color: #ffeb3b; background: #444; } /* Suchtreffer */ + .entry.highlight { border-left-color: #ffeb3b; background: #444; } .info { font-size: 1.1em; } .meta { font-size: 0.85em; color: #aaa; margin-top: 4px; font-family: monospace; } @@ -37,10 +38,42 @@ /* Buttons */ button { cursor: pointer; padding: 8px 15px; border: none; font-weight: bold; color: white; border-radius: 4px; } - .btn-approve { background: #4caf50; margin-right: 5px; } .btn-delete { background: #f44336; } .btn-delete:hover { background: #d32f2f; } + /* BADWORD STYLES (Tags) */ + #badword-container { + display: flex; + flex-wrap: wrap; + gap: 10px; + padding: 20px; + background: #333; + border: 1px solid #555; + } + + .badword-tag { + background: #222; + padding: 8px 15px; + border-radius: 20px; + display: flex; + align-items: center; + gap: 10px; + border: 1px solid #f44336; + color: #ff8a80; + font-weight: bold; + } + + .badword-remove { + cursor: pointer; + color: #f44336; + font-weight: bold; + background: rgba(255,255,255,0.1); + width: 20px; height: 20px; + display: flex; align-items: center; justify-content: center; + border-radius: 50%; + } + .badword-remove:hover { background: #f44336; color: white; } + .empty { text-align: center; padding: 40px; color: #666; font-style: italic; } @@ -52,28 +85,43 @@