package handlers import ( "fmt" "io" "net/http" "os" "path/filepath" "git.elijahkuntz.com/Elijah/drive/config" "git.elijahkuntz.com/Elijah/drive/middleware" "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v5" ) type OnlyOfficeHandler struct { Config *config.Config } type CallbackRequest struct { Status int `json:"status"` Url string `json:"url"` Key string `json:"key"` } func (h *OnlyOfficeHandler) Callback(c *fiber.Ctx) error { path := c.Query("path") token := c.Query("token") if path == "" { return c.Status(400).SendString("path required") } // Validate the token to ensure the callback is authorized if err := middleware.ValidateDownloadToken(token, h.Config.JWTSecret, ""); err != nil { return c.Status(401).SendString("unauthorized") } var req CallbackRequest if err := c.BodyParser(&req); err != nil { return c.Status(400).JSON(fiber.Map{"error": "invalid json"}) } // Status 2: Document is ready for saving // Status 6: Document is being edited, force save was triggered if req.Status == 2 || req.Status == 6 { if req.Url == "" { return c.Status(400).JSON(fiber.Map{"error": "url required"}) } // Download modified file from Document Server resp, err := http.Get(req.Url) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "failed to download file"}) } defer resp.Body.Close() targetPath := filepath.Join(h.Config.StorageDir, path) // Save file out, err := os.Create(targetPath) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "failed to open file for writing"}) } defer out.Close() if _, err := io.Copy(out, resp.Body); err != nil { return c.Status(500).JSON(fiber.Map{"error": "failed to write file"}) } } // Send successful response according to OnlyOffice API return c.JSON(fiber.Map{"error": 0}) } // CreateBlank creates an empty document for Docs or Slides mode func (h *OnlyOfficeHandler) CreateBlank(c *fiber.Ctx) error { req := new(struct { Type string `json:"type"` Name string `json:"name"` }) if err := c.BodyParser(req); err != nil { return c.Status(400).JSON(fiber.Map{"error": "invalid request"}) } if req.Name == "" { req.Name = "Untitled Document" if req.Type == "slide" { req.Name = "Untitled Presentation" } } ext := ".docx" if req.Type == "slide" { ext = ".pptx" } // Add extension if missing if filepath.Ext(req.Name) != ext { req.Name += ext } // Make sure the file name doesn't collide basePath := filepath.Join(h.Config.StorageDir, req.Name) finalPath := basePath counter := 1 for { if _, err := os.Stat(finalPath); os.IsNotExist(err) { break } finalPath = filepath.Join(h.Config.StorageDir, fmt.Sprintf("%s (%d)%s", req.Name[:len(req.Name)-len(ext)], counter, ext)) counter++ } // Create an empty file. OnlyOffice will initialize it when opened. f, err := os.Create(finalPath) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "failed to create file"}) } f.Close() // Return the relative path relativePath, _ := filepath.Rel(h.Config.StorageDir, finalPath) return c.JSON(fiber.Map{ "path": filepath.ToSlash(relativePath), "name": filepath.Base(finalPath), }) } // SignConfig takes the OnlyOffice configuration payload and signs it with the JWT secret func (h *OnlyOfficeHandler) SignConfig(c *fiber.Ctx) error { if h.Config.OnlyOfficeJWT == "" { return c.Status(500).JSON(fiber.Map{"error": "JWT secret is not configured on the server"}) } var payload map[string]interface{} if err := c.BodyParser(&payload); err != nil { return c.Status(400).JSON(fiber.Map{"error": "invalid json payload"}) } // Create a new JWT token using the HS256 algorithm token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload)) // Sign the token with our secret signedToken, err := token.SignedString([]byte(h.Config.OnlyOfficeJWT)) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "failed to sign token"}) } return c.JSON(fiber.Map{ "token": signedToken, }) }