Numerous Bug Fixes
Some checks failed
Automated Container Build / build-and-push (push) Failing after 8s
Some checks failed
Automated Container Build / build-and-push (push) Failing after 8s
This commit is contained in:
parent
02eefdac0e
commit
267d429122
959 changed files with 145571 additions and 221 deletions
|
|
@ -1,6 +1,8 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -89,13 +91,17 @@ func (h *FSHandler) ListDirectory(c *fiber.Ctx) error {
|
|||
|
||||
// Enrich with DB metadata (pinned status, etc.)
|
||||
var isPinned, isTrashed int
|
||||
var checksum sql.NullString
|
||||
err = h.DB.QueryRow(
|
||||
"SELECT is_pinned, is_trashed FROM files WHERE path = ?",
|
||||
"SELECT is_pinned, is_trashed, checksum FROM files WHERE path = ?",
|
||||
filepath.ToSlash(entryPath),
|
||||
).Scan(&isPinned, &isTrashed)
|
||||
).Scan(&isPinned, &isTrashed, &checksum)
|
||||
if err == nil {
|
||||
fi.IsPinned = isPinned == 1
|
||||
fi.IsTrashed = isTrashed == 1
|
||||
if checksum.Valid {
|
||||
fi.Checksum = checksum.String
|
||||
}
|
||||
}
|
||||
|
||||
// Don't show trashed items in normal listings
|
||||
|
|
@ -139,14 +145,16 @@ func (h *FSHandler) getFileInfo(c *fiber.Ctx, absPath string) error {
|
|||
}
|
||||
|
||||
var isPinned int
|
||||
var checksum string
|
||||
var checksum sql.NullString
|
||||
err = h.DB.QueryRow(
|
||||
"SELECT is_pinned, checksum FROM files WHERE path = ?",
|
||||
filepath.ToSlash(relativePath),
|
||||
).Scan(&isPinned, &checksum)
|
||||
if err == nil {
|
||||
fi.IsPinned = isPinned == 1
|
||||
fi.Checksum = checksum
|
||||
if checksum.Valid {
|
||||
fi.Checksum = checksum.String
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(fi)
|
||||
|
|
@ -166,21 +174,32 @@ func (h *FSHandler) CreateFolder(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "path is required"})
|
||||
}
|
||||
|
||||
// Validate path within jail
|
||||
fullPath, err := h.resolveSafe(body.Path)
|
||||
parentDir := filepath.Dir(body.Path)
|
||||
folderName := filepath.Base(body.Path)
|
||||
|
||||
parentFullPath, err := h.resolveSafe(parentDir)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
uniqueName := GetUniquePath(parentFullPath, folderName)
|
||||
fullPath := filepath.Join(parentFullPath, uniqueName)
|
||||
cleanPath := filepath.ToSlash(filepath.Join(parentDir, uniqueName))
|
||||
|
||||
// Remove any prefix like "." if cleanPath was "." and it got joined.
|
||||
// filepath.ToSlash cleans the path.
|
||||
if cleanPath == "." {
|
||||
// Just for safety, but base shouldn't be empty
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(fullPath, 0755); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to create folder"})
|
||||
}
|
||||
|
||||
// Record in DB
|
||||
cleanPath := filepath.ToSlash(body.Path)
|
||||
h.DB.Exec(
|
||||
"INSERT OR IGNORE INTO files (path, name, is_dir) VALUES (?, ?, 1)",
|
||||
cleanPath, filepath.Base(cleanPath),
|
||||
cleanPath, uniqueName,
|
||||
)
|
||||
|
||||
h.DB.AddAuditLog("folder_created", cleanPath, c.IP())
|
||||
|
|
@ -207,7 +226,10 @@ func (h *FSHandler) Rename(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
newPath := filepath.Join(filepath.Dir(body.OldPath), body.NewName)
|
||||
parentDir := filepath.Dir(oldFull)
|
||||
uniqueName := GetUniquePath(parentDir, body.NewName)
|
||||
newPath := filepath.Join(filepath.Dir(body.OldPath), uniqueName)
|
||||
|
||||
newFull, err := h.resolveSafe(newPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
|
|
@ -216,6 +238,9 @@ func (h *FSHandler) Rename(c *fiber.Ctx) error {
|
|||
if err := os.Rename(oldFull, newFull); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to rename"})
|
||||
}
|
||||
|
||||
// Update body.NewName to reflect the actual unique name used
|
||||
body.NewName = uniqueName
|
||||
|
||||
// Update DB
|
||||
h.DB.Exec(
|
||||
|
|
@ -251,8 +276,17 @@ func (h *FSHandler) Move(c *fiber.Ctx) error {
|
|||
|
||||
// If destination is a directory, move into it
|
||||
if info, err := os.Stat(dstFull); err == nil && info.IsDir() {
|
||||
dstFull = filepath.Join(dstFull, filepath.Base(srcFull))
|
||||
body.DestPath = filepath.Join(body.DestPath, filepath.Base(body.SourcePath))
|
||||
uniqueName := GetUniquePath(dstFull, filepath.Base(srcFull))
|
||||
dstFull = filepath.Join(dstFull, uniqueName)
|
||||
body.DestPath = filepath.Join(body.DestPath, uniqueName)
|
||||
} else {
|
||||
// If destination is not a directory but the file exists (rename), we don't overwrite if they want `(1)`
|
||||
// Wait, if they intentionally rename to an existing file, should it append (1)?
|
||||
// Yes, to prevent accidental overwrites.
|
||||
parentDir := filepath.Dir(dstFull)
|
||||
uniqueName := GetUniquePath(parentDir, filepath.Base(dstFull))
|
||||
dstFull = filepath.Join(parentDir, uniqueName)
|
||||
body.DestPath = filepath.Join(filepath.Dir(body.DestPath), uniqueName)
|
||||
}
|
||||
|
||||
if err := os.Rename(srcFull, dstFull); err != nil {
|
||||
|
|
@ -324,11 +358,16 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
|
|||
// Soft-delete: move to .trash directory
|
||||
trashPath := filepath.Join(h.Config.TrashDir, relativePath)
|
||||
if err := os.MkdirAll(filepath.Dir(trashPath), 0755); err != nil {
|
||||
fmt.Printf("MkdirAll error: %v\n", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to prepare trash"})
|
||||
}
|
||||
|
||||
if err := os.Rename(resolvedPath, trashPath); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to move to trash"})
|
||||
fmt.Printf("Rename error %s -> %s: %v. Attempting copy & delete fallback...\n", resolvedPath, trashPath, err)
|
||||
if err := copyFile(resolvedPath, trashPath); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to move to trash"})
|
||||
}
|
||||
os.RemoveAll(resolvedPath)
|
||||
}
|
||||
|
||||
// Update DB
|
||||
|
|
@ -438,12 +477,75 @@ func (h *FSHandler) EmptyTrash(c *fiber.Ctx) error {
|
|||
os.MkdirAll(h.Config.TrashDir, 0755)
|
||||
|
||||
h.DB.Exec("DELETE FROM files WHERE is_trashed = 1")
|
||||
|
||||
h.DB.AddAuditLog("trash_emptied", "All trashed items permanently deleted", c.IP())
|
||||
|
||||
h.DB.AddAuditLog("emptied_trash", "User emptied trash", c.IP())
|
||||
return c.JSON(fiber.Map{"message": "trash emptied"})
|
||||
}
|
||||
|
||||
// DownloadFolder creates and streams a ZIP archive of a folder.
|
||||
// GET /api/files/download-folder?path=...
|
||||
func (h *FSHandler) DownloadFolder(c *fiber.Ctx) error {
|
||||
folderPath := c.Query("path")
|
||||
if folderPath == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "path is required"})
|
||||
}
|
||||
|
||||
fullPath, err := h.resolveSafe(folderPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil || !info.IsDir() {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "folder not found"})
|
||||
}
|
||||
|
||||
c.Set("Content-Type", "application/zip")
|
||||
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filepath.Base(fullPath)))
|
||||
|
||||
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||
zipWriter := zip.NewWriter(w)
|
||||
defer zipWriter.Close()
|
||||
|
||||
filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if path == fullPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(fullPath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
// add directory entry
|
||||
relPath += "/"
|
||||
_, err = zipWriter.Create(relPath)
|
||||
return err
|
||||
}
|
||||
|
||||
// add file entry
|
||||
zipFile, err := zipWriter.Create(relPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fsFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fsFile.Close()
|
||||
|
||||
_, err = io.Copy(zipFile, fsFile)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TogglePin pins or unpins a file/folder.
|
||||
// POST /api/files/pin
|
||||
func (h *FSHandler) TogglePin(c *fiber.Ctx) error {
|
||||
|
|
@ -585,8 +687,9 @@ func (h *FSHandler) Upload(c *fiber.Ctx) error {
|
|||
|
||||
// If destination is a directory, save into it
|
||||
if info, err := os.Stat(fullDest); err == nil && info.IsDir() {
|
||||
fullDest = filepath.Join(fullDest, file.Filename)
|
||||
destPath = filepath.Join(destPath, file.Filename)
|
||||
uniqueName := GetUniquePath(fullDest, file.Filename)
|
||||
fullDest = filepath.Join(fullDest, uniqueName)
|
||||
destPath = filepath.Join(destPath, uniqueName)
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
|
|
@ -594,9 +697,6 @@ func (h *FSHandler) Upload(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to create directory"})
|
||||
}
|
||||
|
||||
// Version existing file if it exists
|
||||
CreateVersion(h.DB, h.Config, fullDest, filepath.ToSlash(destPath))
|
||||
|
||||
// Save the file
|
||||
if err := c.SaveFile(file, fullDest); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to save file"})
|
||||
|
|
|
|||
Reference in a new issue