package handlers import ( "encoding/json" "net/http" "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/interfaces/middleware" "github.com/gorilla/mux" "go.mongodb.org/mongo-driver/v2/bson" "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/application/dto" "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/application/services" ) // AdminHandler handles admin-level HTTP requests type AdminHandler struct { adminService *services.AdminService } // NewAdminHandler creates a new AdminHandler func NewAdminHandler(adminService *services.AdminService) *AdminHandler { return &AdminHandler{adminService: adminService} } // ListUsers handles GET /admin/users func (h *AdminHandler) ListUsers(w http.ResponseWriter, r *http.Request) { users, err := h.adminService.ListUsers(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"users": users}) } // DeleteUser handles DELETE /admin/users/{userId} func (h *AdminHandler) DeleteUser(w http.ResponseWriter, r *http.Request) { targetUserID, err := bson.ObjectIDFromHex(mux.Vars(r)["userId"]) if err != nil { http.Error(w, "invalid user id", http.StatusBadRequest) return } currentUserIDHex, err := middleware.GetUserIDFromContext(r.Context()) if err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } currentUserID, err := bson.ObjectIDFromHex(currentUserIDHex) if err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } if err := h.adminService.DeleteUser(r.Context(), currentUserID, targetUserID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) } // UpdateUserGroups handles PUT /admin/users/{userId}/groups func (h *AdminHandler) UpdateUserGroups(w http.ResponseWriter, r *http.Request) { userID, err := bson.ObjectIDFromHex(mux.Vars(r)["userId"]) if err != nil { http.Error(w, "invalid user id", http.StatusBadRequest) return } var req dto.UpdateUserGroupsRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } groupIDs := make([]bson.ObjectID, 0, len(req.GroupIDs)) for _, groupID := range req.GroupIDs { parsed, err := bson.ObjectIDFromHex(groupID) if err != nil { http.Error(w, "invalid group id", http.StatusBadRequest) return } groupIDs = append(groupIDs, parsed) } user, err := h.adminService.UpdateUserGroups(r.Context(), userID, groupIDs) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } // DeleteGroup handles DELETE /admin/groups/{groupId} func (h *AdminHandler) DeleteGroup(w http.ResponseWriter, r *http.Request) { groupID, err := bson.ObjectIDFromHex(mux.Vars(r)["groupId"]) if err != nil { http.Error(w, "invalid group id", http.StatusBadRequest) return } if err := h.adminService.DeleteGroup(r.Context(), groupID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) } // ListGroups handles GET /admin/groups func (h *AdminHandler) ListGroups(w http.ResponseWriter, r *http.Request) { groups, err := h.adminService.ListGroups(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"groups": groups}) } // CreateGroup handles POST /admin/groups func (h *AdminHandler) CreateGroup(w http.ResponseWriter, r *http.Request) { var req dto.CreatePermissionGroupRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } group, err := h.adminService.CreateGroup(r.Context(), &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(group) } // UpdateGroup handles PUT /admin/groups/{groupId} func (h *AdminHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) { groupID, err := bson.ObjectIDFromHex(mux.Vars(r)["groupId"]) if err != nil { http.Error(w, "invalid group id", http.StatusBadRequest) return } var req dto.UpdatePermissionGroupRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } group, err := h.adminService.UpdateGroup(r.Context(), groupID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(group) } // ListAllSpaces handles GET /admin/spaces func (h *AdminHandler) ListAllSpaces(w http.ResponseWriter, r *http.Request) { spaces, err := h.adminService.ListAllSpaces(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"spaces": spaces}) } // UpdateSpace handles PUT /admin/spaces/{spaceId} func (h *AdminHandler) UpdateSpace(w http.ResponseWriter, r *http.Request) { spaceID, err := bson.ObjectIDFromHex(mux.Vars(r)["spaceId"]) if err != nil { http.Error(w, "invalid space id", http.StatusBadRequest) return } var req dto.CreateSpaceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } space, err := h.adminService.UpdateSpace(r.Context(), spaceID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(space) } // SetSpaceVisibility handles PUT /admin/spaces/{spaceId}/visibility func (h *AdminHandler) SetSpaceVisibility(w http.ResponseWriter, r *http.Request) { spaceID, err := bson.ObjectIDFromHex(mux.Vars(r)["spaceId"]) if err != nil { http.Error(w, "invalid space id", http.StatusBadRequest) return } var req struct { IsPublic bool `json:"is_public"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } if err := h.adminService.SetSpaceVisibility(r.Context(), spaceID, req.IsPublic); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "visibility updated"}) } // AddSpaceMember handles POST /admin/spaces/{spaceId}/members func (h *AdminHandler) AddSpaceMember(w http.ResponseWriter, r *http.Request) { spaceID, err := bson.ObjectIDFromHex(mux.Vars(r)["spaceId"]) if err != nil { http.Error(w, "invalid space id", http.StatusBadRequest) return } var req dto.AddSpaceMemberRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } userID, err := bson.ObjectIDFromHex(req.UserID) if err != nil { http.Error(w, "invalid user id", http.StatusBadRequest) return } if err := h.adminService.AddSpaceMember(r.Context(), spaceID, userID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"message": "member added"}) } // ListSpaceMembers handles GET /admin/spaces/{spaceId}/members func (h *AdminHandler) ListSpaceMembers(w http.ResponseWriter, r *http.Request) { spaceID, err := bson.ObjectIDFromHex(mux.Vars(r)["spaceId"]) if err != nil { http.Error(w, "invalid space id", http.StatusBadRequest) return } members, err := h.adminService.ListSpaceMembers(r.Context(), spaceID) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"members": members}) } // RemoveSpaceMember handles DELETE /admin/spaces/{spaceId}/members/{userId} func (h *AdminHandler) RemoveSpaceMember(w http.ResponseWriter, r *http.Request) { spaceID, err := bson.ObjectIDFromHex(mux.Vars(r)["spaceId"]) if err != nil { http.Error(w, "invalid space id", http.StatusBadRequest) return } userID, err := bson.ObjectIDFromHex(mux.Vars(r)["userId"]) if err != nil { http.Error(w, "invalid user id", http.StatusBadRequest) return } if err := h.adminService.RemoveSpaceMember(r.Context(), spaceID, userID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) } // DeleteSpace handles DELETE /admin/spaces/{spaceId} func (h *AdminHandler) DeleteSpace(w http.ResponseWriter, r *http.Request) { spaceID, err := bson.ObjectIDFromHex(mux.Vars(r)["spaceId"]) if err != nil { http.Error(w, "invalid space id", http.StatusBadRequest) return } if err := h.adminService.DeleteSpace(r.Context(), spaceID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) } // GetFeatureFlags handles GET /admin/feature-flags func (h *AdminHandler) GetFeatureFlags(w http.ResponseWriter, r *http.Request) { flags, err := h.adminService.GetFeatureFlags(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(flags) } // UpdateFeatureFlags handles PUT /admin/feature-flags func (h *AdminHandler) UpdateFeatureFlags(w http.ResponseWriter, r *http.Request) { var req dto.UpdateFeatureFlagsRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } flags, err := h.adminService.UpdateFeatureFlags(r.Context(), &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(flags) } // DeleteProvider handles DELETE /admin/auth/providers/{providerId} func (h *AdminHandler) DeleteProvider(w http.ResponseWriter, r *http.Request) { providerID, err := bson.ObjectIDFromHex(mux.Vars(r)["providerId"]) if err != nil { http.Error(w, "invalid provider id", http.StatusBadRequest) return } if err := h.adminService.DeleteProvider(r.Context(), providerID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) }