dukcapil/internal/handler/onlyoffice_handler.go
2025-08-29 16:10:05 +07:00

206 lines
6.0 KiB
Go

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))
}