package repo import ( "errors" "time" "voltaserve/errorpkg" "voltaserve/helper" "voltaserve/infra" "voltaserve/model" "gorm.io/gorm" ) type FileInsertOptions struct { Name string WorkspaceID string ParentID *string Type string } type FileRepo interface { Insert(opts FileInsertOptions) (model.File, error) Find(id string) (model.File, error) FindChildren(id string) ([]model.File, error) FindPath(id string) ([]model.File, error) FindTree(id string) ([]model.File, error) GetIDsByWorkspace(workspaceID string) ([]string, error) AssignSnapshots(cloneID string, originalID string) error MoveSourceIntoTarget(targetID string, sourceID string) error Save(file model.File) error BulkInsert(values []model.File, chunkSize int) error BulkInsertPermissions(values []*UserPermission, chunkSize int) error Delete(id string) error GetChildrenIDs(id string) ([]string, error) GetItemCount(id string) (int64, error) IsGrandChildOf(id string, ancestorID string) (bool, error) GetSize(id string) (int64, error) GrantUserPermission(id string, userID string, permission string) error RevokeUserPermission(id string, userID string) error GrantGroupPermission(id string, groupID string, permission string) error RevokeGroupPermission(id string, groupID string) error } func NewFileRepo() FileRepo { return newFileRepo() } func NewFile() model.File { return &fileEntity{} } type fileEntity struct { ID string `json:"id" gorm:"column:id"` WorkspaceID string `json:"workspaceId" gorm:"column:workspace_id"` Name string `json:"name" gorm:"column:name"` Type string `json:"type" gorm:"column:type"` ParentID *string `json:"parentId,omitempty" gorm:"column:parent_id"` Snapshots []*snapshotEntity `json:"snapshots,omitempty" gorm:"-"` UserPermissions []*userPermissionValue `json:"userPermissions" gorm:"-"` GroupPermissions []*groupPermissionValue `json:"groupPermissions" gorm:"-"` Text *string `json:"text,omitempty" gorm:"-"` CreateTime string `json:"createTime" gorm:"column:create_time"` UpdateTime *string `json:"updateTime,omitempty" gorm:"column:update_time"` } func (*fileEntity) TableName() string { return "file" } func (f *fileEntity) BeforeCreate(*gorm.DB) (err error) { f.CreateTime = time.Now().UTC().Format(time.RFC3339) return nil } func (f *fileEntity) BeforeSave(*gorm.DB) (err error) { timeNow := time.Now().UTC().Format(time.RFC3339) f.UpdateTime = &timeNow return nil } func (f *fileEntity) GetID() string { return f.ID } func (f *fileEntity) GetWorkspaceID() string { return f.WorkspaceID } func (f *fileEntity) GetName() string { return f.Name } func (f *fileEntity) GetType() string { return f.Type } func (f *fileEntity) GetParentID() *string { return f.ParentID } func (f *fileEntity) GetSnapshots() []model.Snapshot { var res []model.Snapshot for _, s := range f.Snapshots { res = append(res, s) } return res } func (f *fileEntity) GetUserPermissions() []model.CoreUserPermission { var res []model.CoreUserPermission for _, p := range f.UserPermissions { res = append(res, p) } return res } func (f *fileEntity) GetGroupPermissions() []model.CoreGroupPermission { var res []model.CoreGroupPermission for _, p := range f.GroupPermissions { res = append(res, p) } return res } func (f *fileEntity) GetText() *string { return f.Text } func (f *fileEntity) GetCreateTime() string { return f.CreateTime } func (f *fileEntity) GetUpdateTime() *string { return f.UpdateTime } func (f *fileEntity) SetID(id string) { f.ID = id } func (f *fileEntity) SetParentID(parentID *string) { f.ParentID = parentID } func (f *fileEntity) SetWorkspaceID(workspaceID string) { f.WorkspaceID = workspaceID } func (f *fileEntity) SetType(fileType string) { f.Type = fileType } func (f *fileEntity) SetName(name string) { f.Name = name } func (f *fileEntity) SetText(text *string) { f.Text = text } func (f *fileEntity) SetCreateTime(createTime string) { f.CreateTime = createTime } func (f *fileEntity) SetUpdateTime(updateTime *string) { f.UpdateTime = updateTime } type fileRepo struct { db *gorm.DB snapshotRepo *snapshotRepo permissionRepo *permissionRepo } func newFileRepo() *fileRepo { return &fileRepo{ db: infra.GetDb(), snapshotRepo: newSnapshotRepo(), permissionRepo: newPermissionRepo(), } } func (repo *fileRepo) Insert(opts FileInsertOptions) (model.File, error) { id := helper.NewID() file := fileEntity{ ID: id, WorkspaceID: opts.WorkspaceID, Name: opts.Name, Type: opts.Type, ParentID: opts.ParentID, } if db := repo.db.Save(&file); db.Error != nil { return nil, db.Error } res, err := repo.find(id) if err != nil { return nil, err } if err := repo.populateModelFields([]*fileEntity{res}); err != nil { return nil, err } return res, nil } func (repo *fileRepo) Find(id string) (model.File, error) { file, err := repo.find(id) if err != nil { return nil, err } if err := repo.populateModelFields([]*fileEntity{file}); err != nil { return nil, err } return file, nil } func (repo *fileRepo) find(id string) (*fileEntity, error) { var res = fileEntity{} db := repo.db.Raw("SELECT * FROM file WHERE id = ?", id).Scan(&res) if db.Error != nil { if errors.Is(db.Error, gorm.ErrRecordNotFound) { return nil, errorpkg.NewFileNotFoundError(db.Error) } else { return nil, errorpkg.NewInternalServerError(db.Error) } } if len(res.ID) == 0 { return nil, errorpkg.NewFileNotFoundError(db.Error) } return &res, nil } func (repo *fileRepo) FindChildren(id string) ([]model.File, error) { var entities []*fileEntity db := repo.db.Raw("SELECT * FROM file WHERE parent_id = ? ORDER BY create_time ASC", id).Scan(&entities) if db.Error != nil { return nil, db.Error } if err := repo.populateModelFields(entities); err != nil { return nil, err } var res []model.File for _, f := range entities { res = append(res, f) } return res, nil } func (repo *fileRepo) FindPath(id string) ([]model.File, error) { var entities []*fileEntity if db := repo.db. Raw("WITH RECURSIVE rec (id, name, type, parent_id, workspace_id, create_time, update_time) AS "+ "(SELECT f.id, f.name, f.type, f.parent_id, f.workspace_id, f.create_time, f.update_time FROM file f WHERE f.id = ? "+ "UNION SELECT f.id, f.name, f.type, f.parent_id, f.workspace_id, f.create_time, f.update_time FROM rec, file f WHERE f.id = rec.parent_id) "+ "SELECT * FROM rec", id). Scan(&entities); db.Error != nil { return nil, db.Error } if err := repo.populateModelFields(entities); err != nil { return nil, err } var res []model.File for _, f := range entities { res = append(res, f) } return res, nil } func (repo *fileRepo) FindTree(id string) ([]model.File, error) { var entities []*fileEntity db := repo.db. Raw("WITH RECURSIVE rec (id, name, type, parent_id, workspace_id, create_time, update_time) AS "+ "(SELECT f.id, f.name, f.type, f.parent_id, f.workspace_id, f.create_time, f.update_time FROM file f WHERE f.id = ? "+ "UNION SELECT f.id, f.name, f.type, f.parent_id, f.workspace_id, f.create_time, f.update_time FROM rec, file f WHERE f.parent_id = rec.id) "+ "SELECT rec.* FROM rec ORDER BY create_time ASC", id). Scan(&entities) if db.Error != nil { return nil, db.Error } if err := repo.populateModelFields(entities); err != nil { return nil, err } var res []model.File for _, f := range entities { res = append(res, f) } return res, nil } func (repo *fileRepo) GetIDsByWorkspace(workspaceID string) ([]string, error) { type IDResult struct { Result string } var ids []IDResult db := repo.db.Raw("SELECT id result FROM file WHERE workspace_id = ? ORDER BY create_time ASC", workspaceID).Scan(&ids) if db.Error != nil { return nil, db.Error } res := []string{} for _, id := range ids { res = append(res, id.Result) } return res, nil } func (repo *fileRepo) AssignSnapshots(cloneID string, originalID string) error { if db := repo.db.Exec("INSERT INTO snapshot_file (snapshot_id, file_id) SELECT s.id, ? "+ "FROM snapshot s LEFT JOIN snapshot_file map ON s.id = map.snapshot_id "+ "WHERE map.file_id = ? ORDER BY s.version DESC LIMIT 1", cloneID, originalID); db.Error != nil { return db.Error } return nil } func (repo *fileRepo) MoveSourceIntoTarget(targetID string, sourceID string) error { if db := repo.db.Exec("UPDATE file SET parent_id = ? WHERE id = ?", targetID, sourceID); db.Error != nil { return db.Error } return nil } func (repo *fileRepo) Save(file model.File) error { if db := repo.db.Save(file); db.Error != nil { return db.Error } return nil } func (repo *fileRepo) BulkInsert(values []model.File, chunkSize int) error { var entities []*fileEntity for _, f := range values { entities = append(entities, f.(*fileEntity)) } if db := repo.db.CreateInBatches(entities, chunkSize); db.Error != nil { return db.Error } return nil } func (repo *fileRepo) BulkInsertPermissions(values []*UserPermission, chunkSize int) error { if db := repo.db.CreateInBatches(values, chunkSize); db.Error != nil { return db.Error } return nil } func (repo *fileRepo) Delete(id string) error { db := repo.db.Exec("DELETE FROM file WHERE id = ?", id) if db.Error != nil { return db.Error } db = repo.db.Exec("DELETE FROM userpermission WHERE resource_id = ?", id) if db.Error != nil { return db.Error } db = repo.db.Exec("DELETE FROM grouppermission WHERE resource_id = ?", id) if db.Error != nil { return db.Error } return nil } func (repo *fileRepo) GetChildrenIDs(id string) ([]string, error) { type Result struct { Result string } var results []Result db := repo.db.Raw("SELECT id result FROM file WHERE parent_id = ? ORDER BY create_time ASC", id).Scan(&results) if db.Error != nil { return []string{}, db.Error } res := []string{} for _, v := range results { res = append(res, v.Result) } return res, nil } func (repo *fileRepo) GetItemCount(id string) (int64, error) { type Result struct { Result int64 } var res Result db := repo.db. Raw("WITH RECURSIVE rec (id, parent_id) AS "+ "(SELECT f.id, f.parent_id FROM file f WHERE f.id = ? "+ "UNION SELECT f.id, f.parent_id FROM rec, file f WHERE f.parent_id = rec.id) "+ "SELECT count(rec.id) as result FROM rec", id). Scan(&res) if db.Error != nil { return 0, db.Error } return res.Result - 1, nil } func (repo *fileRepo) IsGrandChildOf(id string, ancestorID string) (bool, error) { type Result struct { Result bool } var res Result if db := repo.db. Raw("WITH RECURSIVE rec (id, parent_id) AS "+ "(SELECT f.id, f.parent_id FROM file f WHERE f.id = ? "+ "UNION SELECT f.id, f.parent_id FROM rec, file f WHERE f.parent_id = rec.id) "+ "SELECT count(rec.id) > 0 as result FROM rec WHERE rec.id = ?", ancestorID, id). Scan(&res); db.Error != nil { return false, db.Error } return res.Result, nil } func (repo *fileRepo) GetSize(id string) (int64, error) { type Result struct { Result int64 } var res Result db := repo.db. Raw("WITH RECURSIVE rec (id, parent_id) AS "+ "(SELECT f.id, f.parent_id FROM file f WHERE f.id = ? "+ "UNION SELECT f.id, f.parent_id FROM rec, file f WHERE f.parent_id = rec.id) "+ "SELECT coalesce(sum((s.original->>'size')::int), 0) as result FROM snapshot s, rec "+ "LEFT JOIN snapshot_file map ON rec.id = map.file_id WHERE map.snapshot_id = s.id", id). Scan(&res) if db.Error != nil { return res.Result, db.Error } return res.Result, nil } func (repo *fileRepo) GrantUserPermission(id string, userID string, permission string) error { /* Grant permission to workspace */ db := repo.db.Exec("INSERT INTO userpermission (id, user_id, resource_id, permission) "+ "(SELECT ?, ?, w.id, 'viewer' FROM file f "+ "INNER JOIN workspace w ON w.id = f.workspace_id AND f.id = ?) "+ "ON CONFLICT DO NOTHING", helper.NewID(), userID, id) if db.Error != nil { return db.Error } /* Grant 'viewer' permission to path files */ path, err := repo.FindPath(id) if err != nil { return err } for _, f := range path { db := repo.db.Exec("INSERT INTO userpermission (id, user_id, resource_id, permission) "+ "VALUES (?, ?, ?, 'viewer') ON CONFLICT DO NOTHING", helper.NewID(), userID, f.GetID()) if db.Error != nil { return db.Error } } /* Grant the requested permission to tree files */ tree, err := repo.FindTree(id) if err != nil { return err } for _, f := range tree { db := repo.db.Exec("INSERT INTO userpermission (id, user_id, resource_id, permission) "+ "VALUES (?, ?, ?, ?) ON CONFLICT (user_id, resource_id) DO UPDATE SET permission = ?", helper.NewID(), userID, f.GetID(), permission, permission) if db.Error != nil { return db.Error } } return nil } func (repo *fileRepo) RevokeUserPermission(id string, userID string) error { tree, err := repo.FindTree(id) if err != nil { return err } for _, f := range tree { db := repo.db.Exec("DELETE FROM userpermission WHERE user_id = ? AND resource_id = ?", userID, f.GetID()) if db.Error != nil { return db.Error } } return nil } func (repo *fileRepo) GrantGroupPermission(id string, groupID string, permission string) error { /* Grant permission to workspace */ db := repo.db.Exec("INSERT INTO grouppermission (id, group_id, resource_id, permission) "+ "(SELECT ?, ?, w.id, 'viewer' FROM file f "+ "INNER JOIN workspace w ON w.id = f.workspace_id AND f.id = ?) "+ "ON CONFLICT DO NOTHING", helper.NewID(), groupID, id) if db.Error != nil { return db.Error } /* Grant 'viewer' permission to path files */ path, err := repo.FindPath(id) if err != nil { return err } for _, f := range path { db := repo.db.Exec("INSERT INTO grouppermission (id, group_id, resource_id, permission) "+ "VALUES (?, ?, ?, 'viewer') ON CONFLICT DO NOTHING", helper.NewID(), groupID, f.GetID()) if db.Error != nil { return db.Error } } /* Grant the requested permission to tree files */ tree, err := repo.FindTree(id) if err != nil { return err } for _, f := range tree { db := repo.db.Exec("INSERT INTO grouppermission (id, group_id, resource_id, permission) "+ "VALUES (?, ?, ?, ?) ON CONFLICT (group_id, resource_id) DO UPDATE SET permission = ?", helper.NewID(), groupID, f.GetID(), permission, permission) if db.Error != nil { return db.Error } } return nil } func (repo *fileRepo) RevokeGroupPermission(id string, groupID string) error { tree, err := repo.FindTree(id) if err != nil { return err } for _, f := range tree { db := repo.db.Exec("DELETE FROM grouppermission WHERE group_id = ? AND resource_id = ?", groupID, f.GetID()) if db.Error != nil { return db.Error } } return nil } func (repo *fileRepo) populateModelFields(entities []*fileEntity) error { for _, f := range entities { f.UserPermissions = make([]*userPermissionValue, 0) userPermissions, err := repo.permissionRepo.GetUserPermissions(f.ID) if err != nil { return err } for _, p := range userPermissions { f.UserPermissions = append(f.UserPermissions, &userPermissionValue{ UserID: p.UserID, Value: p.Permission, }) } f.GroupPermissions = make([]*groupPermissionValue, 0) groupPermissions, err := repo.permissionRepo.GetGroupPermissions(f.ID) if err != nil { return err } for _, p := range groupPermissions { f.GroupPermissions = append(f.GroupPermissions, &groupPermissionValue{ GroupID: p.GroupID, Value: p.Permission, }) } snapshots, err := repo.snapshotRepo.findAllForFile(f.ID) if err != nil { return nil } f.Snapshots = snapshots } return nil }