Files
drive/apps/backend/internal/catalog/http.go
2025-12-02 22:08:50 +00:00

185 lines
4.1 KiB
Go

package catalog
import (
"errors"
"fmt"
"github.com/get-drexa/drexa/internal/account"
"github.com/get-drexa/drexa/internal/httperr"
"github.com/get-drexa/drexa/internal/virtualfs"
"github.com/gofiber/fiber/v2"
"github.com/uptrace/bun"
)
type HTTPHandler struct {
vfs *virtualfs.VirtualFS
db *bun.DB
}
type patchFileRequest struct {
Name string `json:"name"`
}
func NewHTTPHandler(vfs *virtualfs.VirtualFS, db *bun.DB) *HTTPHandler {
return &HTTPHandler{vfs: vfs, db: db}
}
func (h *HTTPHandler) RegisterRoutes(api fiber.Router) {
g := api.Group("/files/:fileID")
g.Use(h.currentFileMiddleware)
g.Get("/", h.fetchFile)
g.Get("/content", h.downloadFile)
g.Patch("/", h.patchFile)
g.Delete("/", h.deleteFile)
}
func mustCurrentFileNode(c *fiber.Ctx) *virtualfs.Node {
return c.Locals("file").(*virtualfs.Node)
}
func (h *HTTPHandler) currentFileMiddleware(c *fiber.Ctx) error {
account := account.CurrentAccount(c)
if account == nil {
return c.SendStatus(fiber.StatusUnauthorized)
}
fileID := c.Params("fileID")
node, err := h.vfs.FindNodeByPublicID(c.Context(), h.db, account.ID, fileID)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
c.Locals("file", node)
return c.Next()
}
func (h *HTTPHandler) fetchFile(c *fiber.Ctx) error {
node := mustCurrentFileNode(c)
i := FileInfo{
ID: node.PublicID,
Name: node.Name,
Size: node.Size,
MimeType: node.MimeType,
CreatedAt: node.CreatedAt,
UpdatedAt: node.UpdatedAt,
}
if node.DeletedAt != nil {
i.DeletedAt = node.DeletedAt
}
return c.JSON(i)
}
func (h *HTTPHandler) downloadFile(c *fiber.Ctx) error {
node := mustCurrentFileNode(c)
content, err := h.vfs.ReadFile(c.Context(), h.db, node)
if err != nil {
if errors.Is(err, virtualfs.ErrUnsupportedOperation) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
if content.URL != "" {
return c.Redirect(content.URL, fiber.StatusTemporaryRedirect)
}
if content.Reader != nil {
if node.MimeType != "" {
c.Set("Content-Type", node.MimeType)
}
if content.Size > 0 {
return c.SendStream(content.Reader, int(content.Size))
}
return c.SendStream(content.Reader)
}
return httperr.Internal(errors.New("vfs returned neither a reader nor a URL"))
}
func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
node := mustCurrentFileNode(c)
patch := new(patchFileRequest)
if err := c.BodyParser(patch); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
}
tx, err := h.db.BeginTx(c.Context(), nil)
if err != nil {
return httperr.Internal(err)
}
defer tx.Rollback()
if patch.Name != "" {
err := h.vfs.RenameNode(c.Context(), tx, node, patch.Name)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
fmt.Printf("node deleted at: %v\n", node.DeletedAt)
return c.JSON(FileInfo{
ID: node.PublicID,
Name: node.Name,
Size: node.Size,
MimeType: node.MimeType,
CreatedAt: node.CreatedAt,
UpdatedAt: node.UpdatedAt,
DeletedAt: node.DeletedAt,
})
}
func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
node := mustCurrentFileNode(c)
tx, err := h.db.BeginTx(c.Context(), nil)
if err != nil {
return httperr.Internal(err)
}
defer tx.Rollback()
shouldTrash := c.Query("trash") == "true"
if shouldTrash {
err = h.vfs.SoftDeleteNode(c.Context(), tx, node)
if err != nil {
return httperr.Internal(err)
}
return c.JSON(FileInfo{
ID: node.PublicID,
Name: node.Name,
Size: node.Size,
MimeType: node.MimeType,
CreatedAt: node.CreatedAt,
UpdatedAt: node.UpdatedAt,
DeletedAt: node.DeletedAt,
})
} else {
err = h.vfs.PermanentlyDeleteNode(c.Context(), tx, node)
if err != nil {
return httperr.Internal(err)
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
return c.SendStatus(fiber.StatusNoContent)
}
}