Docker/Voltaserve/api/repo/file_repo.go
2024-04-17 20:22:30 +02:00

573 lines
16 KiB
Go

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
}