Shared file bug fixes, redesigned pin system, new info button
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
This commit is contained in:
parent
71ba029fbc
commit
2bcfca983c
7 changed files with 511 additions and 276 deletions
|
|
@ -160,6 +160,66 @@ func (h *FSHandler) getFileInfo(c *fiber.Ctx, absPath string) error {
|
|||
return c.JSON(fi)
|
||||
}
|
||||
|
||||
// GetExtendedFileInfo gets recursive size and file count for info modal
|
||||
// GET /api/files/info/*
|
||||
func (h *FSHandler) GetExtendedFileInfo(c *fiber.Ctx) error {
|
||||
resolvedPath := c.Locals("resolvedPath").(string)
|
||||
relativePath := c.Locals("relativePath").(string)
|
||||
|
||||
info, err := os.Stat(resolvedPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file not found"})
|
||||
}
|
||||
|
||||
fi := FileInfo{
|
||||
Name: info.Name(),
|
||||
Path: filepath.ToSlash(relativePath),
|
||||
IsDir: info.IsDir(),
|
||||
Size: info.Size(),
|
||||
MimeType: mime.TypeByExtension(filepath.Ext(info.Name())),
|
||||
ModTime: info.ModTime(),
|
||||
}
|
||||
|
||||
var isPinned int
|
||||
var isTrashed int
|
||||
var checksum sql.NullString
|
||||
var createdAt string
|
||||
err = h.DB.QueryRow(
|
||||
"SELECT is_pinned, is_trashed, checksum, created_at FROM files WHERE path = ?",
|
||||
filepath.ToSlash(relativePath),
|
||||
).Scan(&isPinned, &isTrashed, &checksum, &createdAt)
|
||||
|
||||
if err == nil {
|
||||
fi.IsPinned = isPinned == 1
|
||||
fi.IsTrashed = isTrashed == 1
|
||||
if checksum.Valid {
|
||||
fi.Checksum = checksum.String
|
||||
}
|
||||
}
|
||||
|
||||
fileCount := 0
|
||||
if fi.IsDir {
|
||||
var totalSize int64 = 0
|
||||
filepath.Walk(resolvedPath, func(p string, i os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !i.IsDir() {
|
||||
totalSize += i.Size()
|
||||
fileCount++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
fi.Size = totalSize
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"info": fi,
|
||||
"file_count": fileCount,
|
||||
"created_at": createdAt,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateFolder creates a new directory.
|
||||
// POST /api/files/mkdir
|
||||
func (h *FSHandler) CreateFolder(c *fiber.Ctx) error {
|
||||
|
|
|
|||
|
|
@ -35,10 +35,16 @@ func (h *ShareHandler) CreateShare(c *fiber.Ctx) error {
|
|||
|
||||
// Validate path
|
||||
fullPath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(req.Path))
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file not found"})
|
||||
}
|
||||
|
||||
isDirInt := 0
|
||||
if info.IsDir() {
|
||||
isDirInt = 1
|
||||
}
|
||||
|
||||
id := uuid.New().String()
|
||||
var hash *string
|
||||
if req.Password != "" {
|
||||
|
|
@ -60,10 +66,10 @@ func (h *ShareHandler) CreateShare(c *fiber.Ctx) error {
|
|||
maxDownloads = &req.MaxDownloads
|
||||
}
|
||||
|
||||
_, err := h.DB.Exec(`
|
||||
INSERT INTO share_links (id, file_path, password_hash, expires_at, max_downloads)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, id, req.Path, hash, expiresAt, maxDownloads)
|
||||
_, err = h.DB.Exec(`
|
||||
INSERT INTO share_links (token, target_path, is_dir, password_hash, expires_at, max_downloads, download_count)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 0)
|
||||
`, id, req.Path, isDirInt, hash, expiresAt, maxDownloads)
|
||||
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to create share"})
|
||||
|
|
@ -81,8 +87,8 @@ func (h *ShareHandler) CreateShare(c *fiber.Ctx) error {
|
|||
// GET /api/share
|
||||
func (h *ShareHandler) ListShares(c *fiber.Ctx) error {
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT id, file_path, expires_at, downloads, max_downloads, created_at,
|
||||
(password_hash IS NOT NULL) as has_password
|
||||
SELECT token, target_path, expires_at, download_count, max_downloads, created_at,
|
||||
(password_hash IS NOT NULL AND password_hash != '') as has_password
|
||||
FROM share_links
|
||||
ORDER BY created_at DESC
|
||||
`)
|
||||
|
|
@ -93,16 +99,29 @@ func (h *ShareHandler) ListShares(c *fiber.Ctx) error {
|
|||
|
||||
var shares []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var id, path, createdAt string
|
||||
var token, path, createdAt string
|
||||
var expiresAt *string
|
||||
var downloads int
|
||||
var maxDownloads *int
|
||||
var hasPassword bool
|
||||
|
||||
rows.Scan(&id, &path, &expiresAt, &downloads, &maxDownloads, &createdAt, &hasPassword)
|
||||
rows.Scan(&token, &path, &expiresAt, &downloads, &maxDownloads, &createdAt, &hasPassword)
|
||||
|
||||
// Skip expired shares
|
||||
if expiresAt != nil {
|
||||
t, err := time.Parse("2006-01-02 15:04:05-07:00", *expiresAt)
|
||||
if err == nil && time.Now().After(t) {
|
||||
// To handle different time formats, we could parse it more robustly
|
||||
// but for now, we'll let frontend also check or we just check here.
|
||||
}
|
||||
t2, err2 := time.Parse(time.RFC3339, *expiresAt)
|
||||
if err2 == nil && time.Now().After(t2) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
shares = append(shares, map[string]interface{}{
|
||||
"id": id,
|
||||
"id": token,
|
||||
"path": path,
|
||||
"expires_at": expiresAt,
|
||||
"downloads": downloads,
|
||||
|
|
@ -119,7 +138,7 @@ func (h *ShareHandler) ListShares(c *fiber.Ctx) error {
|
|||
// DELETE /api/share/:id
|
||||
func (h *ShareHandler) RevokeShare(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
_, err := h.DB.Exec(`DELETE FROM share_links WHERE id = ?`, id)
|
||||
_, err := h.DB.Exec(`DELETE FROM share_links WHERE token = ?`, id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to revoke"})
|
||||
}
|
||||
|
|
@ -145,8 +164,8 @@ func (h *ShareHandler) GetPublicShare(c *fiber.Ctx) error {
|
|||
var downloads int
|
||||
|
||||
err := h.DB.QueryRow(`
|
||||
SELECT file_path, password_hash, expires_at, max_downloads, downloads
|
||||
FROM share_links WHERE id = ?
|
||||
SELECT target_path, password_hash, expires_at, max_downloads, download_count
|
||||
FROM share_links WHERE token = ?
|
||||
`, id).Scan(&path, &hash, &expiresAt, &maxDownloads, &downloads)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -204,8 +223,8 @@ func (h *ShareHandler) DownloadPublicShare(c *fiber.Ctx) error {
|
|||
var downloads int
|
||||
|
||||
err := h.DB.QueryRow(`
|
||||
SELECT file_path, password_hash, expires_at, max_downloads, downloads
|
||||
FROM share_links WHERE id = ?
|
||||
SELECT target_path, password_hash, expires_at, max_downloads, download_count
|
||||
FROM share_links WHERE token = ?
|
||||
`, id).Scan(&path, &hash, &expiresAt, &maxDownloads, &downloads)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -229,7 +248,7 @@ func (h *ShareHandler) DownloadPublicShare(c *fiber.Ctx) error {
|
|||
fullPath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(path))
|
||||
|
||||
// Increment download counter
|
||||
h.DB.Exec(`UPDATE share_links SET downloads = downloads + 1 WHERE id = ?`, id)
|
||||
h.DB.Exec(`UPDATE share_links SET download_count = download_count + 1 WHERE token = ?`, id)
|
||||
h.DB.AddAuditLog("share_downloaded", fmt.Sprintf("Downloaded share %s", id), c.IP())
|
||||
|
||||
return c.SendFile(fullPath)
|
||||
|
|
|
|||
Reference in a new issue