Initial onlyoffice integration, sidebar changes
Some checks failed
Automated Container Build / build-and-push (push) Failing after 28s
Some checks failed
Automated Container Build / build-and-push (push) Failing after 28s
This commit is contained in:
parent
4110657557
commit
d80e31e7f5
7 changed files with 504 additions and 12 deletions
129
backend/handlers/onlyoffice.go
Normal file
129
backend/handlers/onlyoffice.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
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"
|
||||
)
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@ func main() {
|
|||
archiveHandler := &handlers.ArchiveHandler{DB: db, Config: cfg}
|
||||
shareGuard := middleware.NewBruteForceGuard(3, 60)
|
||||
shareHandler := &handlers.ShareHandler{DB: db, Config: cfg, Guard: shareGuard}
|
||||
onlyOfficeHandler := &handlers.OnlyOfficeHandler{Config: cfg}
|
||||
|
||||
// Initialize background workers
|
||||
workers.InitTaskManager(cfg)
|
||||
|
|
@ -135,6 +136,9 @@ func main() {
|
|||
public.Post("/share/:id", shareGuard.Check(), shareHandler.GetPublicShare)
|
||||
public.Get("/download/:id", shareHandler.DownloadPublicShare)
|
||||
|
||||
// OnlyOffice callback (public but validates query token)
|
||||
public.Post("/onlyoffice/callback", onlyOfficeHandler.Callback)
|
||||
|
||||
// Auth routes (with brute force protection)
|
||||
auth := app.Group("/api/auth")
|
||||
auth.Post("/login", guard.Check(), authHandler.Login)
|
||||
|
|
@ -184,6 +188,9 @@ func main() {
|
|||
protected.Post("/files/upload", fsHandler.Upload)
|
||||
protected.Post("/files/check-conflicts", fsHandler.CheckConflicts)
|
||||
|
||||
// OnlyOffice blanks
|
||||
protected.Post("/onlyoffice/create", onlyOfficeHandler.CreateBlank)
|
||||
|
||||
protected.Get("/files/folders-tree", fsHandler.GetFolderTree)
|
||||
|
||||
// File listing and download (with path jail)
|
||||
|
|
|
|||
Reference in a new issue