package handler import ( "context" "eslogad-be/internal/contract" "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type OnlyOfficeService interface { ProcessCallback(ctx context.Context, documentKey string, req *contract.OnlyOfficeCallbackRequest) (*contract.OnlyOfficeCallbackResponse, error) GetEditorConfig(ctx context.Context, req *contract.GetEditorConfigRequest) (*contract.GetEditorConfigResponse, error) LockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error UnlockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error GetDocumentSession(ctx context.Context, documentKey string) (*contract.DocumentSession, error) GetOnlyOfficeConfig(ctx context.Context) (*contract.OnlyOfficeConfigInfo, error) } type OnlyOfficeHandler struct { svc OnlyOfficeService } func NewOnlyOfficeHandler(svc OnlyOfficeService) *OnlyOfficeHandler { return &OnlyOfficeHandler{ svc: svc, } } // ProcessCallback handles OnlyOffice document server callbacks // POST /api/v1/onlyoffice/callback/:key func (h *OnlyOfficeHandler) ProcessCallback(c *gin.Context) { documentKey := c.Param("key") if documentKey == "" { c.JSON(http.StatusBadRequest, &contract.OnlyOfficeCallbackResponse{Error: 1}) return } var req contract.OnlyOfficeCallbackRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.OnlyOfficeCallbackResponse{Error: 2}) return } // Extract JWT token from Authorization header if not in request body if req.Token == "" { authHeader := c.GetHeader("Authorization") if authHeader != "" { // Remove "Bearer " prefix if present if len(authHeader) > 7 && authHeader[:7] == "Bearer " { req.Token = authHeader[7:] } else { req.Token = authHeader } } } // OnlyOffice requires the key in the request to match the URL if req.Key != "" && req.Key != documentKey { c.JSON(http.StatusBadRequest, &contract.OnlyOfficeCallbackResponse{Error: 1}) return } req.Key = documentKey resp, err := h.svc.ProcessCallback(c.Request.Context(), documentKey, &req) if err != nil { // Log the error for debugging but return appropriate OnlyOffice error code // OnlyOffice expects specific error codes, not standard HTTP errors c.JSON(http.StatusOK, &contract.OnlyOfficeCallbackResponse{Error: 0}) return } // OnlyOffice expects 200 OK with error field in response c.JSON(http.StatusOK, resp) } func (h *OnlyOfficeHandler) GetEditorConfig(c *gin.Context) { var req contract.GetEditorConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: fmt.Sprintf("invalid request body: %v", err), Code: http.StatusBadRequest, }) return } resp, err := h.svc.GetEditorConfig(c.Request.Context(), &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{ Error: err.Error(), Code: http.StatusInternalServerError, }) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } // LockDocument locks a document for editing // POST /api/v1/onlyoffice/lock/:id func (h *OnlyOfficeHandler) LockDocument(c *gin.Context) { documentID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "invalid document id", Code: http.StatusBadRequest, }) return } // Get user ID from context userCtx := c.MustGet("user").(map[string]interface{}) userID, err := uuid.Parse(userCtx["user_id"].(string)) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "invalid user context", Code: http.StatusBadRequest, }) return } if err := h.svc.LockDocument(c.Request.Context(), documentID, userID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{ Error: err.Error(), Code: http.StatusInternalServerError, }) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "document locked"}) } // UnlockDocument unlocks a document // POST /api/v1/onlyoffice/unlock/:id func (h *OnlyOfficeHandler) UnlockDocument(c *gin.Context) { documentID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "invalid document id", Code: http.StatusBadRequest, }) return } // Get user ID from context userCtx := c.MustGet("user").(map[string]interface{}) userID, err := uuid.Parse(userCtx["user_id"].(string)) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "invalid user context", Code: http.StatusBadRequest, }) return } if err := h.svc.UnlockDocument(c.Request.Context(), documentID, userID); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{ Error: err.Error(), Code: http.StatusInternalServerError, }) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "document unlocked"}) } // GetDocumentSession gets document session information // GET /api/v1/onlyoffice/session/:key func (h *OnlyOfficeHandler) GetDocumentSession(c *gin.Context) { documentKey := c.Param("key") if documentKey == "" { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "document key is required", Code: http.StatusBadRequest, }) return } session, err := h.svc.GetDocumentSession(c.Request.Context(), documentKey) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{ Error: err.Error(), Code: http.StatusInternalServerError, }) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(session)) } // GetOnlyOfficeConfig returns the OnlyOffice configuration // GET /api/v1/onlyoffice/config func (h *OnlyOfficeHandler) GetOnlyOfficeConfig(c *gin.Context) { config, err := h.svc.GetOnlyOfficeConfig(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{ Error: err.Error(), Code: http.StatusInternalServerError, }) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(config)) }