package handler import ( "strconv" "github.com/gofiber/fiber/v2" "github.com/gosec/gsc-ops-api/internal/middleware" "github.com/gosec/gsc-ops-api/internal/service" "github.com/gosec/gsc-ops-api/pkg/types" ) // CardDAVHandler handles CardDAV endpoints type CardDAVHandler struct { svc *service.CardDAVService } // NewCardDAVHandler creates a new CardDAV handler func NewCardDAVHandler(svc *service.CardDAVService) *CardDAVHandler { return &CardDAVHandler{svc: svc} } // --- Principals --- // ListPrincipals handles GET /api/v1/carddav/principals func (h *CardDAVHandler) ListPrincipals(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) principals, err := h.svc.ListPrincipals(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(principals, reqID)) } // GetPrincipal handles GET /api/v1/carddav/principals/:username func (h *CardDAVHandler) GetPrincipal(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) username := c.Params("username") principal, err := h.svc.GetPrincipal(c.Context(), username) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if principal == nil { apiErr := types.NewNotFound("Principal not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(principal, reqID)) } // CreatePrincipal handles POST /api/v1/carddav/principals func (h *CardDAVHandler) CreatePrincipal(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.CardDAVPrincipalCreate 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.Username == "" { apiErr := types.NewValidation("username is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } principal, err := h.svc.CreatePrincipal(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(principal, reqID)) } // DeletePrincipal handles DELETE /api/v1/carddav/principals/:username func (h *CardDAVHandler) DeletePrincipal(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) username := c.Params("username") if err := h.svc.DeletePrincipal(c.Context(), username); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"username": username, "deleted": true}, reqID)) } // --- Address Books --- // ListAddressBooks handles GET /api/v1/carddav/addressbooks func (h *CardDAVHandler) ListAddressBooks(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) principal := c.Query("principal") books, err := h.svc.ListAddressBooks(c.Context(), principal) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(books, reqID)) } // GetAddressBook handles GET /api/v1/carddav/addressbooks/:id func (h *CardDAVHandler) GetAddressBook(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } book, err := h.svc.GetAddressBook(c.Context(), id) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if book == nil { apiErr := types.NewNotFound("Address book not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(book, reqID)) } // CreateAddressBook handles POST /api/v1/carddav/addressbooks func (h *CardDAVHandler) CreateAddressBook(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.AddressBookCreate 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.PrincipalURI == "" || req.DisplayName == "" || req.URI == "" { apiErr := types.NewValidation("principalUri, displayName, and uri are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } book, err := h.svc.CreateAddressBook(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(book, reqID)) } // UpdateAddressBook handles PUT /api/v1/carddav/addressbooks/:id func (h *CardDAVHandler) UpdateAddressBook(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.AddressBookUpdate 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)) } book, err := h.svc.UpdateAddressBook(c.Context(), id, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if book == nil { apiErr := types.NewNotFound("Address book not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(book, reqID)) } // DeleteAddressBook handles DELETE /api/v1/carddav/addressbooks/:id func (h *CardDAVHandler) DeleteAddressBook(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.DeleteAddressBook(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)) } // --- Contacts --- // ListContacts handles GET /api/v1/carddav/addressbooks/:id/contacts func (h *CardDAVHandler) ListContacts(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } contacts, err := h.svc.ListContacts(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(contacts, reqID)) } // GetContact handles GET /api/v1/carddav/addressbooks/:id/contacts/:uri func (h *CardDAVHandler) GetContact(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } uri := c.Params("uri") contact, err := h.svc.GetContact(c.Context(), id, uri) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if contact == nil { apiErr := types.NewNotFound("Contact not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(contact, reqID)) } // CreateContact handles POST /api/v1/carddav/addressbooks/:id/contacts func (h *CardDAVHandler) CreateContact(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } var req types.ContactCreate 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.URI == "" || req.CardData == "" { apiErr := types.NewValidation("uri and cardData are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } contact, err := h.svc.CreateContact(c.Context(), id, &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(contact, reqID)) } // UpdateContact handles PUT /api/v1/carddav/addressbooks/:id/contacts/:uri func (h *CardDAVHandler) UpdateContact(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } uri := c.Params("uri") var req types.ContactUpdate 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.CardData == "" { apiErr := types.NewValidation("cardData is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } contact, err := h.svc.UpdateContact(c.Context(), id, uri, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if contact == nil { apiErr := types.NewNotFound("Contact not found") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(contact, reqID)) } // DeleteContact handles DELETE /api/v1/carddav/addressbooks/:id/contacts/:uri func (h *CardDAVHandler) DeleteContact(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) id, err := strconv.Atoi(c.Params("id")) if err != nil { apiErr := types.NewBadRequest("Invalid address book ID") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } uri := c.Params("uri") if err := h.svc.DeleteContact(c.Context(), id, uri); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"addressbookId": id, "uri": uri, "deleted": true}, reqID)) }