Ctrl K
ComunidadGuías…

internal/handler/organization_handler.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"encoding/json"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"
	"github.com/johnzastrow/actalog/internal/service"
	"github.com/johnzastrow/actalog/pkg/logger"
	"github.com/johnzastrow/actalog/pkg/middleware"
)

type OrganizationHandler struct {
	orgService *service.OrganizationService
	logger     *logger.Logger
}

func NewOrganizationHandler(orgService *service.OrganizationService, logger *logger.Logger) *OrganizationHandler {
	return &OrganizationHandler{
		orgService: orgService,
		logger:     logger,
	}
}

// CreateOrganization handles POST /api/admin/organizations
// @Summary      Create organization (Admin)
// @Description  Create a new organization (admin only)
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        request body object true "Organization name and description"
// @Success      201 {object} map[string]interface{} "Created organization"
// @Failure      400 {object} ErrorResponse "Invalid request"
// @Failure      409 {object} ErrorResponse "Organization name exists"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/organizations [post]
func (h *OrganizationHandler) CreateOrganization(w http.ResponseWriter, r *http.Request) {
	adminUserID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	var req struct {
		Name        string  `json:"name"`
		Description *string `json:"description"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	if req.Name == "" {
		respondError(w, http.StatusBadRequest, "Organization name is required")
		return
	}

	org, err := h.orgService.Create(adminUserID, req.Name, req.Description)
	if err != nil {
		if err == service.ErrOrganizationNameExists {
			respondError(w, http.StatusConflict, "Organization name already exists")
			return
		}
		h.logger.Error("Failed to create organization: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to create organization")
		return
	}

	respondJSON(w, http.StatusCreated, org)
}

// ListOrganizations handles GET /api/admin/organizations
// @Summary      List organizations (Admin)
// @Description  Get a paginated list of all organizations
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        limit query int false "Max results (default 50)"
// @Param        offset query int false "Skip N results (default 0)"
// @Success      200 {object} map[string]interface{} "Organizations list with total count"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/organizations [get]
func (h *OrganizationHandler) ListOrganizations(w http.ResponseWriter, r *http.Request) {
	limit := 50
	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
			limit = parsedLimit
		}
	}

	offset := 0
	if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
		if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
			offset = parsedOffset
		}
	}

	orgs, total, err := h.orgService.List(limit, offset)
	if err != nil {
		h.logger.Error("Failed to list organizations: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to list organizations")
		return
	}

	response := map[string]interface{}{
		"organizations": orgs,
		"total":         total,
		"limit":         limit,
		"offset":        offset,
	}

	respondJSON(w, http.StatusOK, response)
}

// GetOrganization handles GET /api/admin/organizations/:id
// @Summary      Get organization (Admin)
// @Description  Get details of a specific organization
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "Organization ID"
// @Success      200 {object} map[string]interface{} "Organization details"
// @Failure      400 {object} ErrorResponse "Invalid organization ID"
// @Failure      404 {object} ErrorResponse "Organization not found"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/organizations/{id} [get]
func (h *OrganizationHandler) GetOrganization(w http.ResponseWriter, r *http.Request) {
	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid organization ID")
		return
	}

	org, err := h.orgService.GetByID(id)
	if err != nil {
		if err == service.ErrOrganizationNotFound {
			respondError(w, http.StatusNotFound, "Organization not found")
			return
		}
		h.logger.Error("Failed to get organization: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get organization")
		return
	}

	respondJSON(w, http.StatusOK, org)
}

// UpdateOrganization handles PUT /api/admin/organizations/:id
// @Summary      Update organization (Admin)
// @Description  Update an existing organization's details
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "Organization ID"
// @Param        request body object true "Updated organization details"
// @Success      200 {object} map[string]interface{} "Updated organization"
// @Failure      400 {object} ErrorResponse "Invalid request"
// @Failure      404 {object} ErrorResponse "Organization not found"
// @Failure      409 {object} ErrorResponse "Organization name exists"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/organizations/{id} [put]
func (h *OrganizationHandler) UpdateOrganization(w http.ResponseWriter, r *http.Request) {
	adminUserID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid organization ID")
		return
	}

	var req struct {
		Name        string  `json:"name"`
		Description *string `json:"description"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	org, err := h.orgService.Update(adminUserID, id, req.Name, req.Description)
	if err != nil {
		if err == service.ErrOrganizationNotFound {
			respondError(w, http.StatusNotFound, "Organization not found")
			return
		}
		if err == service.ErrOrganizationNameExists {
			respondError(w, http.StatusConflict, "Organization name already exists")
			return
		}
		h.logger.Error("Failed to update organization: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to update organization")
		return
	}

	respondJSON(w, http.StatusOK, org)
}

// DeleteOrganization handles DELETE /api/admin/organizations/:id
// @Summary      Delete organization (Admin)
// @Description  Delete an organization (must have no assigned users)
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "Organization ID"
// @Success      200 {object} MessageResponse "Success message"
// @Failure      400 {object} ErrorResponse "Invalid organization ID"
// @Failure      404 {object} ErrorResponse "Organization not found"
// @Failure      409 {object} ErrorResponse "Organization has assigned users"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/organizations/{id} [delete]
func (h *OrganizationHandler) DeleteOrganization(w http.ResponseWriter, r *http.Request) {
	adminUserID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid organization ID")
		return
	}

	err = h.orgService.Delete(adminUserID, id)
	if err != nil {
		if err == service.ErrOrganizationNotFound {
			respondError(w, http.StatusNotFound, "Organization not found")
			return
		}
		if err == service.ErrOrganizationHasUsers {
			respondError(w, http.StatusConflict, "Cannot delete organization with assigned users")
			return
		}
		h.logger.Error("Failed to delete organization: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to delete organization")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{
		"message": "Organization deleted successfully",
	})
}

// AssignUserToOrganization handles POST /api/admin/users/:id/organization
// @Summary      Assign user to organization (Admin)
// @Description  Assign a user to an organization
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "User ID"
// @Param        request body object true "Organization ID to assign"
// @Success      200 {object} MessageResponse "Success message"
// @Failure      400 {object} ErrorResponse "Invalid request"
// @Failure      404 {object} ErrorResponse "User or organization not found"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/users/{id}/organization [post]
func (h *OrganizationHandler) AssignUserToOrganization(w http.ResponseWriter, r *http.Request) {
	adminUserID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	userIDStr := chi.URLParam(r, "id")
	userID, err := strconv.ParseInt(userIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid user ID")
		return
	}

	var req struct {
		OrganizationID int64 `json:"organization_id"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	if req.OrganizationID == 0 {
		respondError(w, http.StatusBadRequest, "Organization ID is required")
		return
	}

	err = h.orgService.AssignUserToOrganization(adminUserID, userID, req.OrganizationID)
	if err != nil {
		if err == service.ErrOrganizationNotFound {
			respondError(w, http.StatusNotFound, "Organization not found")
			return
		}
		if err == service.ErrUserNotFound {
			respondError(w, http.StatusNotFound, "User not found")
			return
		}
		h.logger.Error("Failed to assign user to organization: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to assign user")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{
		"message": "User assigned to organization successfully",
	})
}

// RemoveUserFromOrganization handles DELETE /api/admin/users/:id/organization/:org_id
// @Summary      Remove user from organization (Admin)
// @Description  Remove a user from an organization
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "User ID"
// @Param        org_id path int true "Organization ID"
// @Success      200 {object} MessageResponse "Success message"
// @Failure      400 {object} ErrorResponse "Invalid ID"
// @Failure      404 {object} ErrorResponse "User or organization not found"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/users/{id}/organization/{org_id} [delete]
func (h *OrganizationHandler) RemoveUserFromOrganization(w http.ResponseWriter, r *http.Request) {
	adminUserID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	userIDStr := chi.URLParam(r, "id")
	userID, err := strconv.ParseInt(userIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid user ID")
		return
	}

	orgIDStr := chi.URLParam(r, "org_id")
	orgID, err := strconv.ParseInt(orgIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid organization ID")
		return
	}

	err = h.orgService.RemoveUserFromOrganization(adminUserID, userID, orgID)
	if err != nil {
		if err == service.ErrUserNotFound {
			respondError(w, http.StatusNotFound, "User not found")
			return
		}
		if err == service.ErrOrganizationNotFound {
			respondError(w, http.StatusNotFound, "Organization not found")
			return
		}
		h.logger.Error("Failed to remove user from organization: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to remove user")
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{
		"message": "User removed from organization successfully",
	})
}

// GetUserOrganizations handles GET /api/admin/users/:id/organizations
// @Summary      Get user organizations (Admin)
// @Description  Get all organizations a user belongs to
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "User ID"
// @Success      200 {object} map[string]interface{} "List of organizations"
// @Failure      400 {object} ErrorResponse "Invalid user ID"
// @Failure      404 {object} ErrorResponse "User not found"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/users/{id}/organizations [get]
func (h *OrganizationHandler) GetUserOrganizations(w http.ResponseWriter, r *http.Request) {
	userIDStr := chi.URLParam(r, "id")
	userID, err := strconv.ParseInt(userIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid user ID")
		return
	}

	orgs, err := h.orgService.GetUserOrganizations(userID)
	if err != nil {
		if err == service.ErrUserNotFound {
			respondError(w, http.StatusNotFound, "User not found")
			return
		}
		h.logger.Error("Failed to get user organizations: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get organizations")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"organizations": orgs,
		"count":         len(orgs),
	})
}

// GetOrganizationUsers handles GET /api/admin/organizations/:id/users
// @Summary      Get organization users (Admin)
// @Description  Get all users belonging to an organization
// @Tags         organizations
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "Organization ID"
// @Success      200 {object} map[string]interface{} "List of users"
// @Failure      400 {object} ErrorResponse "Invalid organization ID"
// @Failure      404 {object} ErrorResponse "Organization not found"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/organizations/{id}/users [get]
func (h *OrganizationHandler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
	orgIDStr := chi.URLParam(r, "id")
	orgID, err := strconv.ParseInt(orgIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid organization ID")
		return
	}

	users, err := h.orgService.GetOrganizationUsers(orgID)
	if err != nil {
		if err == service.ErrOrganizationNotFound {
			respondError(w, http.StatusNotFound, "Organization not found")
			return
		}
		h.logger.Error("Failed to get organization users: %v", err)
		respondError(w, http.StatusInternalServerError, "Failed to get users")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"users": users,
		"count": len(users),
	})
}