138 lines
3.3 KiB
Go
138 lines
3.3 KiB
Go
package files
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type FileInfo struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
IsDir bool `json:"isDir"`
|
|
Children []FileInfo `json:"children,omitempty"`
|
|
}
|
|
|
|
// UserDir returns the base directory for a user's files.
|
|
func UserDir(dataDir, userID string) string {
|
|
return filepath.Join(dataDir, "files", userID)
|
|
}
|
|
|
|
// EnsureUserDir creates the user's file directory if it doesn't exist.
|
|
func EnsureUserDir(dataDir, userID string) error {
|
|
return os.MkdirAll(UserDir(dataDir, userID), 0755)
|
|
}
|
|
|
|
// ReadFile reads a markdown file for a user.
|
|
func ReadFile(dataDir, userID, relPath string) (string, error) {
|
|
p := safePath(dataDir, userID, relPath)
|
|
if p == "" {
|
|
return "", os.ErrPermission
|
|
}
|
|
b, err := os.ReadFile(p)
|
|
return string(b), err
|
|
}
|
|
|
|
// WriteFile writes content to a markdown file for a user.
|
|
func WriteFile(dataDir, userID, relPath, content string) error {
|
|
p := safePath(dataDir, userID, relPath)
|
|
if p == "" {
|
|
return os.ErrPermission
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(p, []byte(content), 0644)
|
|
}
|
|
|
|
// CreateFolder creates a directory for a user.
|
|
func CreateFolder(dataDir, userID, relPath string) error {
|
|
p := safePath(dataDir, userID, relPath)
|
|
if p == "" {
|
|
return os.ErrPermission
|
|
}
|
|
return os.MkdirAll(p, 0755)
|
|
}
|
|
|
|
// DeleteFile removes a file or folder for a user.
|
|
func DeleteFile(dataDir, userID, relPath string) error {
|
|
p := safePath(dataDir, userID, relPath)
|
|
if p == "" {
|
|
return os.ErrPermission
|
|
}
|
|
return os.RemoveAll(p)
|
|
}
|
|
|
|
// MoveFile moves a file or folder to a new path.
|
|
func MoveFile(dataDir, userID, fromRel, toRel string) error {
|
|
from := safePath(dataDir, userID, fromRel)
|
|
to := safePath(dataDir, userID, toRel)
|
|
if from == "" || to == "" {
|
|
return os.ErrPermission
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(to), 0755); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(from, to)
|
|
}
|
|
|
|
// ListTree returns the file tree for a user.
|
|
func ListTree(dataDir, userID string) ([]FileInfo, error) {
|
|
root := UserDir(dataDir, userID)
|
|
if err := os.MkdirAll(root, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
return listDir(root, "")
|
|
}
|
|
|
|
func listDir(base, rel string) ([]FileInfo, error) {
|
|
dir := filepath.Join(base, rel)
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result []FileInfo
|
|
for _, e := range entries {
|
|
entryRel := filepath.Join(rel, e.Name())
|
|
info := FileInfo{
|
|
Name: e.Name(),
|
|
Path: entryRel,
|
|
IsDir: e.IsDir(),
|
|
}
|
|
if e.IsDir() {
|
|
children, err := listDir(base, entryRel)
|
|
if err == nil {
|
|
info.Children = children
|
|
}
|
|
}
|
|
result = append(result, info)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// safePath validates and returns the absolute path, preventing traversal.
|
|
func safePath(dataDir, userID, relPath string) string {
|
|
if strings.Contains(relPath, "..") {
|
|
return ""
|
|
}
|
|
root := UserDir(dataDir, userID)
|
|
p := filepath.Join(root, filepath.Clean(relPath))
|
|
if !strings.HasPrefix(p, root) {
|
|
return ""
|
|
}
|
|
return p
|
|
}
|
|
|
|
// GetCreatedTime returns the file's modification time as ISO string.
|
|
func GetCreatedTime(dataDir, userID, relPath string) string {
|
|
p := safePath(dataDir, userID, relPath)
|
|
if p == "" {
|
|
return ""
|
|
}
|
|
info, err := os.Stat(p)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return info.ModTime().UTC().Format("2006-01-02T15:04:05Z")
|
|
}
|