Introduce Player Insights and Context Menu components

- Added `PlayerInsights` for detailed player data visualization and note management.
- Added `PlayerContextMenu` for performing player actions like viewing insights, kick, and ban.
- Refactored gateway response handling to use `writeError` for improved consistency.
- Simplified community onboarding logic for new registrations, now using an empty community state.
This commit is contained in:
Sebastian Unterschütz
2026-05-01 14:35:08 +02:00
parent f5466f9062
commit 3d0eea5782
29 changed files with 3767 additions and 247 deletions

View File

@@ -524,7 +524,7 @@ func (g *Gateway) broadcast(messageType int, data []byte) {
func (g *Gateway) handleRegister(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
@@ -535,7 +535,7 @@ func (g *Gateway) handleRegister(w http.ResponseWriter, r *http.Request) {
CommunityName string `json:"communityName"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
@@ -552,7 +552,7 @@ func (g *Gateway) handleRegister(w http.ResponseWriter, r *http.Request) {
hash, err := auth.HashPassword(req.Password)
if err != nil {
http.Error(w, "Failed to hash password", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to hash password")
return
}
@@ -565,19 +565,6 @@ func (g *Gateway) handleRegister(w http.ResponseWriter, r *http.Request) {
return
}
communityID := uuid.NewString()
communityName := req.CommunityName
if communityName == "" {
communityName = req.Username + "'s Team"
}
community := &DevCommunity{
ID: communityID,
Name: communityName,
CreatedAt: time.Now(),
}
g.store.communities[communityID] = community
userID := uuid.NewString()
user := &DevUser{
ID: userID,
@@ -589,28 +576,23 @@ func (g *Gateway) handleRegister(w http.ResponseWriter, r *http.Request) {
g.store.users[userID] = user
g.store.usersByName[strings.ToLower(req.Username)] = user
membership := DevMembership{
UserID: userID,
CommunityID: communityID,
Role: "owner",
}
g.store.memberships = append(g.store.memberships, membership)
log.Printf("[DEV] Registered user: %s (Pending onboarding)", req.Username)
log.Printf("[DEV] Registered user: %s, Created community: %s", req.Username, communityName)
token, err := auth.GenerateJWT(userID, communityID, req.Username, 7*24*time.Hour)
// Token with empty communityId indicates onboarding state
token, err := auth.GenerateJWT(userID, "", req.Username, 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to generate token")
return
}
writeJSON(w, http.StatusOK, map[string]string{
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": "success",
"token": token,
"userId": userID,
"communityId": communityID,
"communityId": "",
"username": req.Username,
"role": "owner",
"role": "",
"communities": []interface{}{},
"masterKey": "this-is-a-32-byte-master-key-xyz",
})
return
@@ -618,19 +600,19 @@ func (g *Gateway) handleRegister(w http.ResponseWriter, r *http.Request) {
// Non-dev mode fallback
userID := "user-" + req.Username
communityID := "comm-" + req.Username
token, err := auth.GenerateJWT(userID, communityID, req.Username, 7*24*time.Hour)
token, err := auth.GenerateJWT(userID, "", req.Username, 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to generate token")
return
}
writeJSON(w, http.StatusOK, map[string]string{
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": "success",
"token": token,
"userId": userID,
"communityId": communityID,
"communityId": "",
"username": req.Username,
"role": "owner",
"role": "",
"communities": []interface{}{},
"masterKey": "this-is-a-32-byte-master-key-xyz",
})
}
@@ -642,7 +624,7 @@ func (g *Gateway) handleRegisterPasskeyBegin(w http.ResponseWriter, r *http.Requ
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
@@ -654,7 +636,7 @@ func (g *Gateway) handleRegisterPasskeyBegin(w http.ResponseWriter, r *http.Requ
"localhost",
)
if err != nil {
http.Error(w, "Failed to create options", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to create options")
return
}
@@ -751,13 +733,13 @@ func (g *Gateway) handlePasskeys(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
func (g *Gateway) handleAddPasskeyBegin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
claims, err := g.requireAuth(r)
@@ -785,7 +767,7 @@ func (g *Gateway) handleAddPasskeyBegin(w http.ResponseWriter, r *http.Request)
func (g *Gateway) handleAddPasskeyFinish(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
claims, err := g.requireAuth(r)
@@ -861,7 +843,7 @@ func (g *Gateway) handleNodes(w http.ResponseWriter, r *http.Request) {
Endpoint string `json:"endpoint"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
req.Name = strings.TrimSpace(req.Name)
@@ -917,14 +899,14 @@ func (g *Gateway) handleNodes(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
func (g *Gateway) handleNodeWebSocket(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if token == "" {
http.Error(w, "Missing token", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Missing token")
return
}
@@ -939,7 +921,7 @@ func (g *Gateway) handleNodeWebSocket(w http.ResponseWriter, r *http.Request) {
g.store.mu.RUnlock()
if node == nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Invalid token")
return
}
@@ -987,11 +969,11 @@ func (g *Gateway) handleNodeWebSocket(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleDevNodePing(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
if !g.devMode {
http.Error(w, "Not found", http.StatusNotFound)
writeError(w, http.StatusNotFound, "Not found")
return
}
nodeID := r.URL.Query().Get("id")
@@ -1043,7 +1025,7 @@ func (g *Gateway) monitorNodes() {
func (g *Gateway) handleLoginPassword(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
@@ -1052,7 +1034,7 @@ func (g *Gateway) handleLoginPassword(w http.ResponseWriter, r *http.Request) {
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
@@ -1105,7 +1087,7 @@ func (g *Gateway) handleLoginPassword(w http.ResponseWriter, r *http.Request) {
token, err := auth.GenerateJWT(user.ID, defaultMembership.CommunityID, user.Username, 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to generate token")
return
}
@@ -1128,17 +1110,20 @@ func (g *Gateway) handleLoginPassword(w http.ResponseWriter, r *http.Request) {
communityID := "comm-" + req.Username
token, err := auth.GenerateJWT(userID, communityID, req.Username, 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to generate token")
return
}
writeJSON(w, http.StatusOK, map[string]string{
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": "success",
"token": token,
"userId": userID,
"communityId": communityID,
"username": req.Username,
"role": "owner",
"masterKey": "this-is-a-32-byte-master-key-xyz",
"communities": []map[string]string{
{"id": communityID, "name": req.Username + "'s Team", "role": "owner"},
},
"masterKey": "this-is-a-32-byte-master-key-xyz",
})
}
@@ -1147,13 +1132,13 @@ func (g *Gateway) handleLoginPasskeyBegin(w http.ResponseWriter, r *http.Request
Username string `json:"username"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
options, err := webauthn.CreateAuthenticationOptions("localhost", []string{})
if err != nil {
http.Error(w, "Failed to create options", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to create options")
return
}
@@ -1164,7 +1149,7 @@ func (g *Gateway) handleLoginPasskeyBegin(w http.ResponseWriter, r *http.Request
func (g *Gateway) handleLoginPasskeyFinish(w http.ResponseWriter, r *http.Request) {
token, err := auth.GenerateJWT("user-demo", "comm-demo", "demo", 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to generate token")
return
}
writeJSON(w, http.StatusOK, map[string]string{
@@ -1190,7 +1175,7 @@ func (g *Gateway) handleLogout(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleMe(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1204,8 +1189,14 @@ func (g *Gateway) handleMe(w http.ResponseWriter, r *http.Request) {
if g.devMode {
g.store.mu.RLock()
if user, exists := g.store.users[claims.UserID]; exists {
resp["role"] = user.Role
resp["email"] = user.Email
// Find role in current community
for _, m := range g.store.memberships {
if m.UserID == claims.UserID && m.CommunityID == claims.CommunityID {
resp["role"] = m.Role
break
}
}
}
g.store.mu.RUnlock()
}
@@ -1216,7 +1207,7 @@ func (g *Gateway) handleMe(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleProfile(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1233,7 +1224,12 @@ func (g *Gateway) handleProfile(w http.ResponseWriter, r *http.Request) {
g.store.mu.RLock()
if user, exists := g.store.users[claims.UserID]; exists {
resp["email"] = user.Email
resp["role"] = user.Role
for _, m := range g.store.memberships {
if m.UserID == claims.UserID && m.CommunityID == claims.CommunityID {
resp["role"] = m.Role
break
}
}
}
g.store.mu.RUnlock()
}
@@ -1247,7 +1243,7 @@ func (g *Gateway) handleProfile(w http.ResponseWriter, r *http.Request) {
NewPassword string `json:"newPassword"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
@@ -1261,7 +1257,7 @@ func (g *Gateway) handleProfile(w http.ResponseWriter, r *http.Request) {
user, exists := g.store.users[claims.UserID]
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
writeError(w, http.StatusNotFound, "User not found")
return
}
@@ -1292,15 +1288,23 @@ func (g *Gateway) handleProfile(w http.ResponseWriter, r *http.Request) {
}
hash, err := auth.HashPassword(req.NewPassword)
if err != nil {
http.Error(w, "Failed to hash password", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to hash password")
return
}
user.PasswordHash = hash
}
token, err := auth.GenerateJWT(user.ID, user.CommunityID, user.Username, 7*24*time.Hour)
var role string
for _, m := range g.store.memberships {
if m.UserID == user.ID && m.CommunityID == claims.CommunityID {
role = m.Role
break
}
}
token, err := auth.GenerateJWT(user.ID, claims.CommunityID, user.Username, 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
writeError(w, http.StatusInternalServerError, "Failed to generate token")
return
}
@@ -1309,12 +1313,12 @@ func (g *Gateway) handleProfile(w http.ResponseWriter, r *http.Request) {
"token": token,
"username": user.Username,
"email": user.Email,
"role": user.Role,
"communityId": user.CommunityID,
"role": role,
"communityId": claims.CommunityID,
})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
@@ -1322,10 +1326,117 @@ func (g *Gateway) handleProfile(w http.ResponseWriter, r *http.Request) {
// SERVER MANAGEMENT
// ============================================================================
// ============================================================================
// COMMUNITIES
// ============================================================================
func (g *Gateway) handleCreateCommunity(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
var req struct {
Name string `json:"name"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
communityName := strings.TrimSpace(req.Name)
if communityName == "" {
writeError(w, http.StatusBadRequest, "Community name is required")
return
}
g.store.mu.Lock()
defer g.store.mu.Unlock()
communityID := uuid.NewString()
community := &DevCommunity{
ID: communityID,
Name: communityName,
CreatedAt: time.Now(),
}
g.store.communities[communityID] = community
membership := DevMembership{
UserID: claims.UserID,
CommunityID: communityID,
Role: "owner",
}
g.store.memberships = append(g.store.memberships, membership)
log.Printf("[DEV] User %s created community: %s", claims.Username, communityName)
// Issue a new token for the new community
token, _ := auth.GenerateJWT(claims.UserID, communityID, claims.Username, 7*24*time.Hour)
writeJSON(w, http.StatusCreated, map[string]interface{}{
"token": token,
"communityId": communityID,
"name": communityName,
"role": "owner",
})
}
func (g *Gateway) handleJoinCommunity(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
var req struct {
CommunityID string `json:"communityId"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
g.store.mu.Lock()
defer g.store.mu.Unlock()
community, exists := g.store.communities[req.CommunityID]
if !exists {
writeError(w, http.StatusNotFound, "Community not found")
return
}
// Check if already a member
for _, m := range g.store.memberships {
if m.UserID == claims.UserID && m.CommunityID == req.CommunityID {
writeError(w, http.StatusConflict, "Already a member")
return
}
}
membership := DevMembership{
UserID: claims.UserID,
CommunityID: req.CommunityID,
Role: "member",
}
g.store.memberships = append(g.store.memberships, membership)
log.Printf("[DEV] User %s joined community: %s", claims.Username, community.Name)
token, _ := auth.GenerateJWT(claims.UserID, req.CommunityID, claims.Username, 7*24*time.Hour)
writeJSON(w, http.StatusOK, map[string]interface{}{
"token": token,
"communityId": req.CommunityID,
"name": community.Name,
"role": "member",
})
}
func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1355,7 +1466,7 @@ func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
EncryptedAutoMessages string `json:"encryptedAutoMessages"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
req.Name = strings.TrimSpace(req.Name)
@@ -1407,7 +1518,7 @@ func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
EncryptedAutoMessages string `json:"encryptedAutoMessages"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
@@ -1416,7 +1527,7 @@ func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
server, exists := g.store.servers[req.ID]
if !exists || server.CommunityID != claims.CommunityID {
http.Error(w, "Server not found", http.StatusNotFound)
writeError(w, http.StatusNotFound, "Server not found")
return
}
@@ -1446,7 +1557,7 @@ func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
case http.MethodDelete:
serverID := r.URL.Query().Get("id")
if serverID == "" {
http.Error(w, "Missing id", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Missing id")
return
}
@@ -1454,7 +1565,7 @@ func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
server, exists := g.store.servers[serverID]
if !exists || server.CommunityID != claims.CommunityID {
g.store.mu.Unlock()
http.Error(w, "Not found", http.StatusNotFound)
writeError(w, http.StatusNotFound, "Not found")
return
}
delete(g.store.servers, serverID)
@@ -1474,7 +1585,7 @@ func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
@@ -1485,7 +1596,7 @@ func (g *Gateway) handleServers(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleUserSearch(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1502,15 +1613,17 @@ func (g *Gateway) handleUserSearch(w http.ResponseWriter, r *http.Request) {
}
var results []userResult
for _, u := range g.store.users {
if u.CommunityID == claims.CommunityID && u.ID != claims.UserID {
if query == "" || strings.Contains(strings.ToLower(u.Username), query) {
results = append(results, userResult{
ID: u.ID,
Username: u.Username,
Email: u.Email,
Role: u.Role,
})
for _, m := range g.store.memberships {
if m.CommunityID == claims.CommunityID && m.UserID != claims.UserID {
if u, ok := g.store.users[m.UserID]; ok {
if query == "" || strings.Contains(strings.ToLower(u.Username), query) {
results = append(results, userResult{
ID: u.ID,
Username: u.Username,
Email: u.Email,
Role: m.Role,
})
}
}
}
}
@@ -1528,7 +1641,7 @@ func (g *Gateway) handleUserSearch(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handlePermissions(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1557,7 +1670,7 @@ func (g *Gateway) handlePermissions(w http.ResponseWriter, r *http.Request) {
Scopes []string `json:"scopes"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
if req.UserID == "" {
@@ -1609,21 +1722,21 @@ func (g *Gateway) handlePermissions(w http.ResponseWriter, r *http.Request) {
case http.MethodDelete:
permID := r.URL.Query().Get("id")
if permID == "" {
http.Error(w, "Missing id", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Missing id")
return
}
g.store.mu.Lock()
defer g.store.mu.Unlock()
perm, exists := g.store.permissions[permID]
if !exists || perm.CommunityID != claims.CommunityID {
http.Error(w, "Not found", http.StatusNotFound)
writeError(w, http.StatusNotFound, "Not found")
return
}
delete(g.store.permissions, permID)
writeJSON(w, http.StatusOK, map[string]string{"status": "revoked"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
@@ -1634,7 +1747,7 @@ func (g *Gateway) handlePermissions(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handlePlayerNotes(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1667,7 +1780,7 @@ func (g *Gateway) handlePlayerNotes(w http.ResponseWriter, r *http.Request) {
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
@@ -1688,14 +1801,14 @@ func (g *Gateway) handlePlayerNotes(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, note)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
func (g *Gateway) handlePlayers(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1728,12 +1841,12 @@ func (g *Gateway) handlePlayers(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleKick(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1742,7 +1855,7 @@ func (g *Gateway) handleKick(w http.ResponseWriter, r *http.Request) {
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
if req.PlayerNameHash == "" {
@@ -1779,12 +1892,12 @@ func (g *Gateway) handleKick(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleBan(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1794,7 +1907,7 @@ func (g *Gateway) handleBan(w http.ResponseWriter, r *http.Request) {
DurationMinutes int `json:"durationMinutes"` // 0 = permanent
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
if req.PlayerNameHash == "" {
@@ -1837,7 +1950,7 @@ func (g *Gateway) handleBan(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleBans(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1862,14 +1975,14 @@ func (g *Gateway) handleBans(w http.ResponseWriter, r *http.Request) {
case http.MethodDelete:
banID := r.URL.Query().Get("id")
if banID == "" {
http.Error(w, "Missing id", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Missing id")
return
}
g.store.mu.Lock()
ban, exists := g.store.bans[banID]
if !exists || ban.CommunityID != claims.CommunityID {
g.store.mu.Unlock()
http.Error(w, "Not found", http.StatusNotFound)
writeError(w, http.StatusNotFound, "Not found")
return
}
delete(g.store.bans, banID)
@@ -1877,7 +1990,7 @@ func (g *Gateway) handleBans(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "unbanned"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
@@ -1888,7 +2001,7 @@ func (g *Gateway) handleBans(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handlePlayerSearch(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1922,7 +2035,7 @@ func (g *Gateway) handlePlayerSearch(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleLogs(w http.ResponseWriter, r *http.Request) {
claims, err := g.requireAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Unauthorized")
return
}
@@ -1944,7 +2057,7 @@ func (g *Gateway) handleLogs(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleDSGVOExport(w http.ResponseWriter, r *http.Request) {
playerID := r.URL.Query().Get("playerId")
if playerID == "" {
http.Error(w, "Missing playerId", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Missing playerId")
return
}
w.Header().Set("Content-Type", "application/json")
@@ -1961,7 +2074,7 @@ func (g *Gateway) handleDSGVODelete(w http.ResponseWriter, r *http.Request) {
PlayerID string `json:"playerId"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Invalid request")
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
@@ -1969,17 +2082,17 @@ func (g *Gateway) handleDSGVODelete(w http.ResponseWriter, r *http.Request) {
func (g *Gateway) handleIngest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
apiKey := r.Header.Get("X-API-Key")
if apiKey == "" {
http.Error(w, "Missing API key", http.StatusUnauthorized)
writeError(w, http.StatusUnauthorized, "Missing API key")
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
writeError(w, http.StatusBadRequest, "Failed to read body")
return
}
communityID := "comm-123-abc"
@@ -2052,6 +2165,9 @@ func main() {
http.HandleFunc("/api/dev/nodes/ping", gateway.handleDevNodePing)
}
http.HandleFunc("/api/communities/create", gateway.handleCreateCommunity)
http.HandleFunc("/api/communities/join", gateway.handleJoinCommunity)
http.HandleFunc("/api/servers", gateway.handleServers)
http.HandleFunc("/api/users/search", gateway.handleUserSearch)
http.HandleFunc("/api/permissions", gateway.handlePermissions)