Files
pfs/pfs_db/pfs_db.go
Vladimir V Maksimov 8e1f17a359 alpha
2026-02-10 16:08:35 +03:00

302 lines
5.4 KiB
Go

package pfs_db
import (
"errors"
"strings"
"time"
"git.gm6.ru/icewind/pfs/common"
"git.gm6.ru/icewind/pfs/models"
"git.gm6.ru/icewind/pfs/stat"
"github.com/google/uuid"
"gorm.io/gorm"
)
type PFSDB struct {
DB *gorm.DB
}
//
// ==================== DIR ====================
//
func (i *PFSDB) DirCreate(dir *models.Dir, userID uuid.UUID) error {
now := time.Now()
// Проверяем ownership родителя (если есть)
if dir.ParentID != nil {
var parent DirModel
err := i.DB.First(
&parent,
"id = ? AND owner_id = ?",
*dir.ParentID,
userID,
).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return common.ErrAccessDenied
}
return err
}
}
dbDir := DirModel{
ID: dir.ID,
ParentID: dir.ParentID,
Name: dir.Name,
OwnerID: dir.OwnerID,
IsPublic: dir.IsPublic,
Created: now,
LastModified: now,
}
return i.DB.Create(&dbDir).Error
}
func (i *PFSDB) DirRemove(dirID uuid.UUID, userID uuid.UUID) error {
var dir DirModel
if err := i.DB.First(
&dir,
"id = ? AND owner_id = ?",
dirID,
userID,
).Error; err != nil {
return err
}
// Cascade делает БД
return i.DB.Delete(&dir).Error
}
func (i *PFSDB) DirRead(dirID uuid.UUID, userID uuid.UUID) ([]stat.FSStat, error) {
var result []stat.FSStat
// Поддиректории
var dirs []DirModel
if err := i.DB.
Where("parent_id = ? AND owner_id = ?", dirID, userID).
Order("name").
Find(&dirs).Error; err != nil {
return nil, err
}
for _, d := range dirs {
result = append(result, stat.FSStat{
ID: d.ID,
Name: d.Name,
IsDir: true,
Created: d.Created,
LastModified: d.LastModified,
OwnerID: d.OwnerID,
})
}
// Файлы
var files []FileModel
if err := i.DB.
Where("dir_id = ? AND owner_id = ?", dirID, userID).
Order("name").
Find(&files).Error; err != nil {
return nil, err
}
for _, f := range files {
result = append(result, stat.FSStat{
ID: f.ID,
Name: f.Name,
IsDir: false,
Created: f.Created,
LastModified: f.LastModified,
OwnerID: f.OwnerID,
})
}
return result, nil
}
//
// ==================== FILE ====================
//
func (i *PFSDB) FileCreate(file *models.File) error {
file.ID = uuid.New()
now := time.Now()
return i.DB.Transaction(func(tx *gorm.DB) error {
dbFile := FileModel{
ID: file.ID,
DirID: file.DirID,
Name: file.Name,
OwnerID: file.OwnerID,
IsPublic: file.IsPublic,
Created: now,
LastModified: now,
}
if err := tx.Create(&dbFile).Error; err != nil {
return err
}
dbFileData := FileDataModel{
ID: uuid.New(),
FileID: file.ID,
Data: file.Data,
}
return tx.Create(&dbFileData).Error
})
}
func (i *PFSDB) FileRemove(fileID uuid.UUID, userID uuid.UUID) error {
var file FileModel
if err := i.DB.First(
&file,
"id = ? AND owner_id = ?",
fileID,
userID,
).Error; err != nil {
return err
}
// FileData удалится через CASCADE
return i.DB.Delete(&file).Error
}
func (i *PFSDB) FileRead(fileID uuid.UUID, userID uuid.UUID) (*models.File, error) {
var file FileModel
if err := i.DB.First(&file, "id = ?", fileID).Error; err != nil {
return nil, err
}
if !file.IsPublic && file.OwnerID != userID {
return nil, common.ErrAccessDenied
}
var fileData FileDataModel
if err := i.DB.First(
&fileData,
"file_id = ?",
fileID,
).Error; err != nil {
return nil, err
}
return &models.File{
ID: file.ID,
DirID: file.DirID,
Name: file.Name,
OwnerID: file.OwnerID,
IsPublic: file.IsPublic,
Data: fileData.Data,
}, nil
}
func splitPath(path string) []string {
path = strings.Trim(path, "/")
if path == "" {
return nil
}
return strings.Split(path, "/")
}
func (i *PFSDB) ResolveDirByPath(
path string,
userID uuid.UUID,
) (uuid.UUID, error) {
parts := splitPath(path)
if len(parts) == 0 {
return uuid.Nil, errors.New("empty path")
}
var current DirModel
// --- ищем root ---
if err := i.DB.Where(
"name = ? AND parent_id IS NULL AND owner_id = ?",
parts[0],
userID,
).First(&current).Error; err != nil {
return uuid.Nil, err
}
// --- идём по дереву ---
for _, name := range parts[1:] {
var next DirModel
err := i.DB.Where(
"name = ? AND parent_id = ? AND owner_id = ?",
name,
current.ID,
userID,
).First(&next).Error
if err != nil {
return uuid.Nil, err
}
current = next
}
return current.ID, nil
}
func (i *PFSDB) ResolveFileByPath(
path string,
userID uuid.UUID,
) (uuid.UUID, error) {
parts := splitPath(path)
if len(parts) < 1 {
return uuid.Nil, errors.New("invalid path")
}
fileName := parts[len(parts)-1]
dirPath := parts[:len(parts)-1]
var dirID *uuid.UUID
// если файл в root
if len(dirPath) > 0 {
id, err := i.ResolveDirByPath(
"/"+strings.Join(dirPath, "/"),
userID,
)
if err != nil {
return uuid.Nil, err
}
dirID = &id
}
var file FileModel
err := i.DB.Where(
"name = ? AND dir_id = ? AND owner_id = ?",
fileName,
dirID,
userID,
).First(&file).Error
if err != nil {
return uuid.Nil, err
}
return file.ID, nil
}
//
// ==================== MIGRATION ====================
//
func (i *PFSDB) Migrate() error {
return i.DB.AutoMigrate(
&DirModel{},
&FileModel{},
&FileDataModel{},
)
}