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{}, ) }