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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user