alpha
This commit is contained in:
301
pfs_db/pfs_db.go
Normal file
301
pfs_db/pfs_db.go
Normal file
@@ -0,0 +1,301 @@
|
||||
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(¤t).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{},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user