package handler import ( "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/gosec/gsc-ops-api/internal/middleware" "github.com/gosec/gsc-ops-api/internal/service" "github.com/gosec/gsc-ops-api/pkg/types" ) // PBXHandler handles PBX management endpoints type PBXHandler struct { svc *service.PBXService } // NewPBXHandler creates a new PBX handler func NewPBXHandler(svc *service.PBXService) *PBXHandler { return &PBXHandler{svc: svc} } // ============================================================================ // Trunks // ============================================================================ // ListTrunks handles GET /api/v1/pbx/trunks func (h *PBXHandler) ListTrunks(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) params := types.ListParams{ Limit: c.QueryInt("limit", 50), Offset: c.QueryInt("offset", 0), Search: c.Query("search"), Status: c.Query("status"), } trunks, total, err := h.svc.ListTrunks(c.Context(), params) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewPagedResponse(trunks, total, params.Limit, params.Offset, reqID)) } // GetTrunk handles GET /api/v1/pbx/trunks/:id func (h *PBXHandler) GetTrunk(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } trunk, err := h.svc.GetTrunk(c.Context(), id) if err != nil { apiErr := types.NewNotFound("Trunk not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(trunk, reqID)) } // CreateTrunk handles POST /api/v1/pbx/trunks func (h *PBXHandler) CreateTrunk(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.PBXTrunkCreate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.Name == "" || req.Host == "" || req.TenantID == uuid.Nil { apiErr := types.NewValidation("tenantId, name, and host are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } trunk, err := h.svc.CreateTrunk(c.Context(), &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(trunk, reqID)) } // UpdateTrunk handles PUT /api/v1/pbx/trunks/:id func (h *PBXHandler) UpdateTrunk(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.PBXTrunkUpdate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } trunk, err := h.svc.UpdateTrunk(c.Context(), id, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(trunk, reqID)) } // DeleteTrunk handles DELETE /api/v1/pbx/trunks/:id func (h *PBXHandler) DeleteTrunk(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.DeleteTrunk(c.Context(), id); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"id": id, "deleted": true}, reqID)) } // ActivateTrunk handles POST /api/v1/pbx/trunks/:id/activate func (h *PBXHandler) ActivateTrunk(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } trunk, err := h.svc.ActivateTrunk(c.Context(), id) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(trunk, reqID)) } // DeactivateTrunk handles POST /api/v1/pbx/trunks/:id/deactivate func (h *PBXHandler) DeactivateTrunk(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } trunk, err := h.svc.DeactivateTrunk(c.Context(), id) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(trunk, reqID)) } // ============================================================================ // Trunk DIDs // ============================================================================ // ListTrunkDIDs handles GET /api/v1/pbx/trunks/:id/dids func (h *PBXHandler) ListTrunkDIDs(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) trunkID, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } dids, err := h.svc.ListTrunkDIDs(c.Context(), trunkID) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(dids, reqID)) } // CreateTrunkDID handles POST /api/v1/pbx/trunks/:id/dids func (h *PBXHandler) CreateTrunkDID(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) trunkID, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.PBXTrunkDIDCreate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.DIDNumber == "" || req.TenantID == uuid.Nil { apiErr := types.NewValidation("tenantId and didNumber are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } did, err := h.svc.CreateTrunkDID(c.Context(), trunkID, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(did, reqID)) } // DeleteTrunkDID handles DELETE /api/v1/pbx/trunks/:id/dids/:didId func (h *PBXHandler) DeleteTrunkDID(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) trunkID, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid trunk ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } didID, err := uuid.Parse(c.Params("didId")) if err != nil { apiErr := types.NewBadRequest("Invalid DID ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.DeleteTrunkDID(c.Context(), trunkID, didID); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"id": didID, "deleted": true}, reqID)) } // ============================================================================ // Extensions // ============================================================================ // ListExtensions handles GET /api/v1/pbx/extensions func (h *PBXHandler) ListExtensions(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) params := types.ListParams{ Limit: c.QueryInt("limit", 50), Offset: c.QueryInt("offset", 0), Search: c.Query("search"), Status: c.Query("status"), } exts, total, err := h.svc.ListExtensions(c.Context(), params) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewPagedResponse(exts, total, params.Limit, params.Offset, reqID)) } // GetExtension handles GET /api/v1/pbx/extensions/:id func (h *PBXHandler) GetExtension(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid extension ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } ext, err := h.svc.GetExtension(c.Context(), id) if err != nil { apiErr := types.NewNotFound("Extension not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(ext, reqID)) } // CreateExtension handles POST /api/v1/pbx/extensions func (h *PBXHandler) CreateExtension(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.PBXExtensionCreate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.Extension == "" || req.Name == "" || req.TenantID == uuid.Nil { apiErr := types.NewValidation("tenantId, extension, and name are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } ext, err := h.svc.CreateExtension(c.Context(), &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(ext, reqID)) } // UpdateExtension handles PUT /api/v1/pbx/extensions/:id func (h *PBXHandler) UpdateExtension(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid extension ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.PBXExtensionUpdate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } ext, err := h.svc.UpdateExtension(c.Context(), id, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(ext, reqID)) } // DeleteExtension handles DELETE /api/v1/pbx/extensions/:id func (h *PBXHandler) DeleteExtension(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid extension ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.DeleteExtension(c.Context(), id); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"id": id, "deleted": true}, reqID)) } // ============================================================================ // Inbound Routes // ============================================================================ // ListInboundRoutes handles GET /api/v1/pbx/inbound-routes func (h *PBXHandler) ListInboundRoutes(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) params := types.ListParams{ Limit: c.QueryInt("limit", 50), Offset: c.QueryInt("offset", 0), Search: c.Query("search"), } routes, total, err := h.svc.ListInboundRoutes(c.Context(), params) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewPagedResponse(routes, total, params.Limit, params.Offset, reqID)) } // GetInboundRoute handles GET /api/v1/pbx/inbound-routes/:id func (h *PBXHandler) GetInboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid route ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } route, err := h.svc.GetInboundRoute(c.Context(), id) if err != nil { apiErr := types.NewNotFound("Inbound route not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(route, reqID)) } // CreateInboundRoute handles POST /api/v1/pbx/inbound-routes func (h *PBXHandler) CreateInboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.PBXInboundRouteCreate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.Name == "" || req.DestinationType == "" || req.TenantID == uuid.Nil { apiErr := types.NewValidation("tenantId, name, and destinationType are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } route, err := h.svc.CreateInboundRoute(c.Context(), &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(route, reqID)) } // UpdateInboundRoute handles PUT /api/v1/pbx/inbound-routes/:id func (h *PBXHandler) UpdateInboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid route ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.PBXInboundRouteUpdate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } route, err := h.svc.UpdateInboundRoute(c.Context(), id, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(route, reqID)) } // DeleteInboundRoute handles DELETE /api/v1/pbx/inbound-routes/:id func (h *PBXHandler) DeleteInboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid route ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.DeleteInboundRoute(c.Context(), id); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"id": id, "deleted": true}, reqID)) } // ============================================================================ // Outbound Routes // ============================================================================ // ListOutboundRoutes handles GET /api/v1/pbx/outbound-routes func (h *PBXHandler) ListOutboundRoutes(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) params := types.ListParams{ Limit: c.QueryInt("limit", 50), Offset: c.QueryInt("offset", 0), Search: c.Query("search"), } routes, total, err := h.svc.ListOutboundRoutes(c.Context(), params) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewPagedResponse(routes, total, params.Limit, params.Offset, reqID)) } // GetOutboundRoute handles GET /api/v1/pbx/outbound-routes/:id func (h *PBXHandler) GetOutboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid route ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } route, err := h.svc.GetOutboundRoute(c.Context(), id) if err != nil { apiErr := types.NewNotFound("Outbound route not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(route, reqID)) } // CreateOutboundRoute handles POST /api/v1/pbx/outbound-routes func (h *PBXHandler) CreateOutboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.PBXOutboundRouteCreate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.Name == "" || len(req.DialPatterns) == 0 || req.TenantID == uuid.Nil { apiErr := types.NewValidation("tenantId, name, and dialPatterns are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } route, err := h.svc.CreateOutboundRoute(c.Context(), &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(route, reqID)) } // UpdateOutboundRoute handles PUT /api/v1/pbx/outbound-routes/:id func (h *PBXHandler) UpdateOutboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid route ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.PBXOutboundRouteUpdate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } route, err := h.svc.UpdateOutboundRoute(c.Context(), id, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(route, reqID)) } // DeleteOutboundRoute handles DELETE /api/v1/pbx/outbound-routes/:id func (h *PBXHandler) DeleteOutboundRoute(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := uuid.Parse(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid route ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.DeleteOutboundRoute(c.Context(), id); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"id": id, "deleted": true}, reqID)) } // ============================================================================ // System Operations // ============================================================================ // Status handles GET /api/v1/pbx/status func (h *PBXHandler) Status(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) status, err := h.svc.GetStatus(c.Context()) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(status, reqID)) } // Reload handles POST /api/v1/pbx/reload func (h *PBXHandler) Reload(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) result, err := h.svc.Reload(c.Context()) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(result, reqID)) }