mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-05-06 04:09:31 -05:00
WIP: AFilesystem
This commit is contained in:
@@ -19,7 +19,7 @@ const errCodeResourceNotCollection = "resource_not_collection"
|
||||
|
||||
type resourceResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Parent *uuid.UUID `json:"parent"`
|
||||
Parent uuid.UUID `json:"parent"`
|
||||
Name string `json:"name"`
|
||||
Dir bool `json:"dir"`
|
||||
Created time.Time `json:"created"`
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shroff/phylum/server/internal/app/core"
|
||||
"github.com/shroff/phylum/server/internal/db"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
@@ -19,6 +23,15 @@ func Initialize(db *db.DbHandler, debug bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.Queries().CreateResource(context.Background(), sql.CreateResourceParams{
|
||||
ID: uuid.UUID{},
|
||||
Parent: uuid.UUID{},
|
||||
Name: "root",
|
||||
Dir: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Default = &App{
|
||||
Debug: debug,
|
||||
Db: db,
|
||||
@@ -27,3 +40,7 @@ func Initialize(db *db.DbHandler, debug bool) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a App) OpenFileSystem(ctx context.Context, user int32) (core.FileSystem, error) {
|
||||
return core.OpenFileSystem(a.Db, ctx, nil, user)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/shroff/phylum/server/internal/db"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type FileSystem interface {
|
||||
OpenWithRoot(Resource) FileSystem
|
||||
ResourceByPath(path string) (Resource, error)
|
||||
ResourceByID(id uuid.UUID) (Resource, error)
|
||||
OpenRead(r Resource, start, length int64) (io.ReadCloser, error)
|
||||
OpenWrite(r Resource) (io.WriteCloser, error)
|
||||
ReadDir(r Resource) ([]Resource, error)
|
||||
CreateMemberResource(r Resource, id uuid.UUID, name string, dir bool) (Resource, error)
|
||||
DeleteRecursive(r Resource, hardDelete bool) error
|
||||
UpdateName(r Resource, name string) error
|
||||
UpdateParent(r Resource, parent uuid.UUID) error
|
||||
UpdatePermissions(r Resource, userID int32, permission int32) error
|
||||
}
|
||||
|
||||
type filesystem struct {
|
||||
db *db.DbHandler
|
||||
ctx context.Context
|
||||
root Resource
|
||||
user int32
|
||||
}
|
||||
|
||||
func OpenFileSystem(db *db.DbHandler, ctx context.Context, root Resource, user int32) (FileSystem, error) {
|
||||
if root == nil {
|
||||
root = resource{
|
||||
id: uuid.UUID{},
|
||||
permission: 0,
|
||||
parentID: uuid.UUID{},
|
||||
name: "root",
|
||||
size: 0,
|
||||
collection: true,
|
||||
modTime: time.Time{},
|
||||
etag: "",
|
||||
}
|
||||
}
|
||||
return filesystem{
|
||||
db: db,
|
||||
ctx: ctx,
|
||||
root: root,
|
||||
user: user,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f filesystem) OpenWithRoot(root Resource) FileSystem {
|
||||
return filesystem{
|
||||
db: f.db,
|
||||
ctx: f.ctx,
|
||||
root: root,
|
||||
user: f.user,
|
||||
}
|
||||
}
|
||||
|
||||
func (f filesystem) ResourceByPath(path string) (Resource, error) {
|
||||
path = strings.Trim(path, "/")
|
||||
segments := strings.Split(path, "/")
|
||||
if path == "" {
|
||||
// Calling strings.Split on an empty string returns a slice of length 1. That breaks the query
|
||||
segments = []string{}
|
||||
}
|
||||
|
||||
res, err := f.db.Queries().ResourceByPath(f.ctx, sql.ResourceByPathParams{Root: f.root.ID(), Search: segments, UserID: f.user})
|
||||
if err != nil {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
|
||||
if !res.Permission.Valid || res.Permission.Int32 == 0 {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
|
||||
return resource{
|
||||
id: res.ID,
|
||||
permission: res.Permission.Int32,
|
||||
parentID: res.Parent,
|
||||
name: res.Name,
|
||||
size: res.Size.Int64,
|
||||
collection: res.Dir,
|
||||
modTime: res.Modified.Time,
|
||||
etag: res.Etag.String,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f filesystem) ResourceByID(id uuid.UUID) (Resource, error) {
|
||||
res, err := f.db.Queries().ResourceByIdWithPermissions(f.ctx, sql.ResourceByIdWithPermissionsParams{Root: f.root.ID(), ResourceID: id, UserID: f.user})
|
||||
if err == pgx.ErrNoRows || res.Permission.Int32 == 0 {
|
||||
err = fs.ErrNotExist
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resource{
|
||||
id: res.ID,
|
||||
permission: res.Permission.Int32,
|
||||
parentID: res.Parent,
|
||||
name: res.Name,
|
||||
size: res.Size.Int64,
|
||||
collection: res.Dir,
|
||||
modTime: res.Modified.Time,
|
||||
etag: res.Etag.String,
|
||||
}, nil
|
||||
}
|
||||
func (f filesystem) OpenRead(r Resource, start, length int64) (io.ReadCloser, error) {
|
||||
if r.Permission() < PermissionReadOnly {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
return r.storage.OpenRead(r.ID(), start, length)
|
||||
}
|
||||
|
||||
func (f filesystem) OpenWrite(r Resource) (io.WriteCloser, error) {
|
||||
if r.Permission() < PermissionReadWrite {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
return r.storage.OpenWrite(r.id, func(len int, etag string) error {
|
||||
return r.db.Queries().UpdateResourceContents(f.ctx, sql.UpdateResourceContentsParams{
|
||||
ID: r.id,
|
||||
Size: pgtype.Int8{Int64: int64(len), Valid: true},
|
||||
Etag: pgtype.Text{String: etag, Valid: true},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (f filesystem) ReadDir(r Resource) ([]Resource, error) {
|
||||
if r.Permission() < PermissionReadOnly {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
children, err := f.db.Queries().ReadDir(f.ctx, sql.ReadDirParams{ID: r.ID(), UserID: f.user, IncludeRoot: false, Recursive: false})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]Resource, len(children))
|
||||
for i, c := range children {
|
||||
permission := r.Permission()
|
||||
if c.Permission.Valid && c.Permission.Int32 > permission {
|
||||
permission = c.Permission.Int32
|
||||
|
||||
}
|
||||
result[i] = resource{
|
||||
id: c.ID,
|
||||
permission: permission,
|
||||
parentID: r.ID(),
|
||||
name: c.Name,
|
||||
size: c.Size.Int64,
|
||||
modTime: c.Modified.Time,
|
||||
collection: c.Dir,
|
||||
etag: c.Etag.String,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f filesystem) CreateMemberResource(r Resource, id uuid.UUID, name string, dir bool) (Resource, error) {
|
||||
if r.Permission() < PermissionReadWrite {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
if !r.IsDir() {
|
||||
return resource{}, ErrResourceNotCollection
|
||||
}
|
||||
var result sql.Resource
|
||||
err := f.db.RunInTx(f.ctx, func(q *sql.Queries) error {
|
||||
var err error
|
||||
if result, err = q.CreateResource(f.ctx, sql.CreateResourceParams{ID: id, Parent: r.ID(), Name: name, Dir: dir}); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.UpdateResourceModified(f.ctx, r.ID())
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource{
|
||||
id: id,
|
||||
parentID: r.ID(),
|
||||
permission: r.Permission(),
|
||||
name: result.Name,
|
||||
size: 0,
|
||||
modTime: result.Modified.Time,
|
||||
collection: dir,
|
||||
etag: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f filesystem) DeleteRecursive(r Resource, hardDelete bool) error {
|
||||
if r.Permission() < PermissionReadWrite {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
// TODO: versioning
|
||||
return f.db.RunInTx(f.ctx, func(q *sql.Queries) error {
|
||||
if hardDelete {
|
||||
deleted, err := q.HardDeleteRecursive(f.ctx, r.ID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
errors := r.storage.Delete(deleted)
|
||||
for err := range errors {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
} else {
|
||||
if err := q.DeleteRecursive(f.ctx, r.ID()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
emptyUUID := uuid.UUID{}
|
||||
if r.ParentID() != emptyUUID {
|
||||
return q.UpdateResourceModified(f.ctx, r.ParentID())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (f filesystem) UpdateName(r Resource, name string) error {
|
||||
return f.db.Queries().UpdateResourceName(f.ctx, sql.UpdateResourceNameParams{ID: r.ID(), Name: name})
|
||||
}
|
||||
|
||||
func (f filesystem) UpdateParent(r Resource, parent uuid.UUID) error {
|
||||
emptyUUID := uuid.UUID{}
|
||||
if r.ParentID() == emptyUUID {
|
||||
return ErrCannotReparentRootResource
|
||||
}
|
||||
return f.db.Queries().UpdateResourceParent(f.ctx, sql.UpdateResourceParentParams{ID: r.ID(), Parent: parent})
|
||||
}
|
||||
|
||||
func (f filesystem) UpdatePermissions(r Resource, userID int32, permission int32) error {
|
||||
if r.Permission() < PermissionAdmin {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
if permission > PermissionAdmin {
|
||||
return ErrCannotGrantOwnerPermission
|
||||
}
|
||||
|
||||
return f.db.Queries().UpdatePermissionsForResource(f.ctx, sql.UpdatePermissionsForResourceParams{
|
||||
ResourceID: r.ID(),
|
||||
UserID: userID,
|
||||
Permission: permission,
|
||||
})
|
||||
}
|
||||
@@ -1,19 +1,13 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/shroff/phylum/server/internal/db"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,9 +25,9 @@ var (
|
||||
ErrCannotReparentToRoot = errors.New("cannot reparent resource to root")
|
||||
)
|
||||
|
||||
type ResourceInfo interface {
|
||||
type Resource interface {
|
||||
ID() uuid.UUID
|
||||
ParentID() *uuid.UUID
|
||||
ParentID() uuid.UUID
|
||||
Name() string
|
||||
Size() int64
|
||||
Permission() int32
|
||||
@@ -43,27 +37,12 @@ type ResourceInfo interface {
|
||||
ContentType() string
|
||||
}
|
||||
|
||||
type Resource interface {
|
||||
ResourceInfo
|
||||
OpenRead(start, length int64) (io.ReadCloser, error)
|
||||
OpenWrite(ctx context.Context) (io.WriteCloser, error)
|
||||
ReadDir(ctx context.Context) ([]Resource, error)
|
||||
CreateMemberResource(ctx context.Context, id uuid.UUID, name string, dir bool) (Resource, error)
|
||||
DeleteRecursive(ctx context.Context, hardDelete bool) error
|
||||
UpdateName(ctx context.Context, name string) error
|
||||
UpdateParent(ctx context.Context, parent uuid.UUID) error
|
||||
UpdatePermissions(ctx context.Context, userID int32, permission int32) error
|
||||
}
|
||||
|
||||
type resource struct {
|
||||
db *db.DbHandler
|
||||
storage Storage
|
||||
id uuid.UUID
|
||||
parentID *uuid.UUID
|
||||
permission int32
|
||||
parentID uuid.UUID
|
||||
name string
|
||||
size int64
|
||||
userID int32
|
||||
permission int32
|
||||
collection bool
|
||||
modTime time.Time
|
||||
etag string
|
||||
@@ -73,7 +52,7 @@ func (r resource) ID() uuid.UUID { return r.id }
|
||||
|
||||
func (r resource) Permission() int32 { return r.permission }
|
||||
|
||||
func (r resource) ParentID() *uuid.UUID { return r.parentID }
|
||||
func (r resource) ParentID() uuid.UUID { return r.parentID }
|
||||
|
||||
func (r resource) Name() string { return r.name }
|
||||
|
||||
@@ -99,143 +78,3 @@ func (r resource) ContentType() string {
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
func (r resource) OpenRead(start, length int64) (io.ReadCloser, error) {
|
||||
if r.permission < PermissionReadOnly {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
return r.storage.OpenRead(r.id, start, length)
|
||||
}
|
||||
|
||||
func (r resource) OpenWrite(ctx context.Context) (io.WriteCloser, error) {
|
||||
if r.permission < PermissionReadWrite {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
return r.storage.OpenWrite(r.id, func(len int, etag string) error {
|
||||
return r.db.Queries().UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{
|
||||
ID: r.id,
|
||||
Size: pgtype.Int8{Int64: int64(len), Valid: true},
|
||||
Etag: pgtype.Text{String: etag, Valid: true},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (r resource) ReadDir(ctx context.Context) ([]Resource, error) {
|
||||
if r.permission < PermissionReadOnly {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
children, err := r.db.Queries().ReadDir(ctx, sql.ReadDirParams{ID: r.id, UserID: r.userID, IncludeRoot: false, Recursive: false})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]Resource, len(children))
|
||||
for i, c := range children {
|
||||
permission := r.Permission()
|
||||
if c.Permission.Valid && c.Permission.Int32 > permission {
|
||||
permission = c.Permission.Int32
|
||||
|
||||
}
|
||||
result[i] = resource{
|
||||
db: r.db,
|
||||
storage: r.storage,
|
||||
id: c.ID,
|
||||
userID: r.userID,
|
||||
permission: permission,
|
||||
parentID: &r.id,
|
||||
name: c.Name,
|
||||
size: c.Size.Int64,
|
||||
modTime: c.Modified.Time,
|
||||
collection: c.Dir,
|
||||
etag: c.Etag.String,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r resource) CreateMemberResource(ctx context.Context, id uuid.UUID, name string, dir bool) (Resource, error) {
|
||||
if r.permission < PermissionReadWrite {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
if !r.collection {
|
||||
return resource{}, ErrResourceNotCollection
|
||||
}
|
||||
var result sql.Resource
|
||||
err := r.db.RunInTx(ctx, func(q *sql.Queries) error {
|
||||
var err error
|
||||
if result, err = q.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &r.id, Name: name, Dir: dir}); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.UpdateResourceModified(ctx, r.id)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource{
|
||||
db: r.db,
|
||||
storage: r.storage,
|
||||
id: id,
|
||||
parentID: &r.id,
|
||||
userID: r.userID,
|
||||
permission: r.permission,
|
||||
name: result.Name,
|
||||
size: 0,
|
||||
modTime: result.Modified.Time,
|
||||
collection: dir,
|
||||
etag: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r resource) DeleteRecursive(ctx context.Context, hardDelete bool) error {
|
||||
if r.permission < PermissionReadWrite {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
// TODO: versioning
|
||||
return r.db.RunInTx(ctx, func(q *sql.Queries) error {
|
||||
if hardDelete {
|
||||
deleted, err := q.HardDeleteRecursive(ctx, r.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
errors := r.storage.Delete(deleted)
|
||||
for err := range errors {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
} else {
|
||||
if err := q.DeleteRecursive(ctx, r.id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if r.parentID != nil {
|
||||
return q.UpdateResourceModified(ctx, *r.parentID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r resource) UpdateName(ctx context.Context, name string) error {
|
||||
return r.db.Queries().UpdateResourceName(ctx, sql.UpdateResourceNameParams{ID: r.id, Name: name})
|
||||
}
|
||||
|
||||
func (r resource) UpdateParent(ctx context.Context, parent uuid.UUID) error {
|
||||
if r.parentID == nil {
|
||||
return ErrCannotReparentRootResource
|
||||
}
|
||||
return r.db.Queries().UpdateResourceParent(ctx, sql.UpdateResourceParentParams{ID: r.id, Parent: parent})
|
||||
}
|
||||
|
||||
func (r resource) UpdatePermissions(ctx context.Context, userID int32, permission int32) error {
|
||||
if r.permission < PermissionAdmin {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
if permission > PermissionAdmin {
|
||||
return ErrCannotGrantOwnerPermission
|
||||
}
|
||||
|
||||
return r.db.Queries().UpdatePermissionsForResource(ctx, sql.UpdatePermissionsForResourceParams{
|
||||
ResourceID: r.id,
|
||||
UserID: userID,
|
||||
Permission: permission,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shroff/phylum/server/internal/db"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
)
|
||||
|
||||
type Silo interface {
|
||||
@@ -15,7 +10,6 @@ type Silo interface {
|
||||
Name() string
|
||||
Owner() int32
|
||||
StorageName() string
|
||||
ResourceByPath(ctx context.Context, path string, userID int32) (Resource, error)
|
||||
}
|
||||
|
||||
type silo struct {
|
||||
@@ -51,35 +45,3 @@ func (s *silo) Owner() int32 {
|
||||
func (s *silo) StorageName() string {
|
||||
return s.storage.Name()
|
||||
}
|
||||
|
||||
func (s *silo) ResourceByPath(ctx context.Context, path string, userID int32) (Resource, error) {
|
||||
path = strings.Trim(path, "/")
|
||||
segments := strings.Split(path, "/")
|
||||
if path == "" {
|
||||
// Calling strings.Split on an empty string returns a slice of length 1. That breaks the query
|
||||
segments = []string{}
|
||||
}
|
||||
|
||||
res, err := s.db.Queries().ResourceByPath(ctx, sql.ResourceByPathParams{Root: s.root, Search: segments, UserID: userID})
|
||||
if err != nil {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
|
||||
if !res.Permission.Valid || res.Permission.Int32 == 0 {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
|
||||
return resource{
|
||||
db: s.db,
|
||||
storage: s.storage,
|
||||
id: res.ID,
|
||||
userID: userID,
|
||||
permission: res.Permission.Int32,
|
||||
parentID: res.Parent,
|
||||
name: res.Name,
|
||||
size: res.Size.Int64,
|
||||
collection: res.Dir,
|
||||
modTime: res.Modified.Time,
|
||||
etag: res.Etag.String,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,24 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/shroff/phylum/server/internal/sql"
|
||||
)
|
||||
|
||||
var ErrResourceNotFound = errors.New("resource not found")
|
||||
|
||||
func (a *App) LocateResource(id uuid.UUID, userID int32) (uuid.UUID, int, error) {
|
||||
result, err := a.Db.Queries().PermissionsForResource(context.Background(), sql.PermissionsForResourceParams{ResourceID: id, UserID: userID})
|
||||
if err == pgx.ErrNoRows {
|
||||
err = ErrResourceNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return uuid.UUID{}, 0, err
|
||||
}
|
||||
|
||||
return result.ID, int(result.Permission.Int32), nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func (a App) CreateSilo(ctx context.Context, id uuid.UUID, owner int32, storage,
|
||||
}
|
||||
if _, err := q.CreateResource(ctx, sql.CreateResourceParams{
|
||||
ID: id,
|
||||
Parent: nil,
|
||||
Parent: uuid.UUID{},
|
||||
Name: id.String(),
|
||||
Dir: true,
|
||||
}); err != nil {
|
||||
@@ -86,25 +86,26 @@ func (a App) FindSilo(ctx context.Context, idOrName string) (core.Silo, error) {
|
||||
}
|
||||
|
||||
func (a App) DeleteSilo(ctx context.Context, id uuid.UUID) error {
|
||||
return a.Db.RunInTx(ctx, func(q *sql.Queries) error {
|
||||
result, err := q.SiloById(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage := a.FindStorageBackend(result.Storage)
|
||||
if storage == nil {
|
||||
return errors.New("storage backend not found for " + id.String())
|
||||
}
|
||||
return nil
|
||||
// return a.Db.RunInTx(ctx, func(q *sql.Queries) error {
|
||||
// result, err := q.SiloById(ctx, id)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// storage := a.FindStorageBackend(result.Storage)
|
||||
// if storage == nil {
|
||||
// return errors.New("storage backend not found for " + id.String())
|
||||
// }
|
||||
|
||||
silo := core.NewSilo(a.Db, result.Name, result.Owner, result.ID, storage)
|
||||
resource, err := silo.ResourceByPath(ctx, "/", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := resource.DeleteRecursive(ctx, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// silo := core.NewSilo(a.Db, result.Name, result.Owner, result.ID, storage)
|
||||
// resource, err := silo.ResourceByPath(ctx, "/", -1)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err := resource.DeleteRecursive(ctx, true); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return q.DeleteSilo(ctx, id)
|
||||
})
|
||||
// return q.DeleteSilo(ctx, id)
|
||||
// })
|
||||
}
|
||||
|
||||
@@ -42,46 +42,52 @@ func SetupHandler(r *gin.RouterGroup, app *app.App) {
|
||||
}
|
||||
|
||||
func (h *handler) HandleRequest(c *gin.Context) {
|
||||
path := c.Params.ByName("path")
|
||||
identifier := strings.TrimLeft(path, "/")
|
||||
if identifier == "" {
|
||||
// No path specified
|
||||
c.JSON(404, gin.H{
|
||||
"ERR_CODE": "err_silo_path_not_specified",
|
||||
})
|
||||
return
|
||||
}
|
||||
index := strings.Index(identifier, "/")
|
||||
if index != -1 {
|
||||
identifier = identifier[0:index]
|
||||
}
|
||||
silo, err := h.app.FindSilo(context.Background(), identifier)
|
||||
if err != nil {
|
||||
c.Writer.WriteHeader(404)
|
||||
c.JSON(404, gin.H{
|
||||
"ERR_CODE": "err_silo_not_found",
|
||||
"ERR_DETAILS": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// path := c.Params.ByName("path")
|
||||
// identifier := strings.TrimLeft(path, "/")
|
||||
// if identifier == "" {
|
||||
// // No path specified
|
||||
// c.JSON(404, gin.H{
|
||||
// "ERR_CODE": "err_silo_path_not_specified",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// index := strings.Index(identifier, "/")
|
||||
// if index != -1 {
|
||||
// identifier = identifier[0:index]
|
||||
// }
|
||||
|
||||
userID := auth.GetUserID(c)
|
||||
fs, err := h.app.OpenFileSystem(c.Request.Context(), auth.GetUserID(c))
|
||||
|
||||
// silo, err := h.app.FindSilo(context.Background(), identifier)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(404, gin.H{
|
||||
"ERR_CODE": "err_silo_not_found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
webdavHandler := webdav.Handler{
|
||||
Prefix: h.prefix + "/" + identifier,
|
||||
FileSystem: adapter{silo: silo, userID: userID},
|
||||
Prefix: h.prefix,
|
||||
FileSystem: adapter{fs: fs},
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
webdavHandler.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
type adapter struct {
|
||||
silo core.Silo
|
||||
userID int32
|
||||
fs core.FileSystem
|
||||
}
|
||||
|
||||
func (a adapter) Stat(ctx context.Context, name string) (core.Resource, error) {
|
||||
return a.silo.ResourceByPath(ctx, name, a.userID)
|
||||
return a.fs.ResourceByPath(name)
|
||||
}
|
||||
|
||||
func (a adapter) OpenRead(r core.Resource, start, len int64) (io.ReadCloser, error) {
|
||||
return a.fs.OpenRead(r, start, len)
|
||||
}
|
||||
|
||||
func (a adapter) ReadDir(r core.Resource) ([]core.Resource, error) {
|
||||
return a.fs.ReadDir(r)
|
||||
}
|
||||
|
||||
func (a adapter) OpenWrite(ctx context.Context, name string) (io.WriteCloser, error) {
|
||||
@@ -99,18 +105,18 @@ func (a adapter) OpenWrite(ctx context.Context, name string) (io.WriteCloser, er
|
||||
}
|
||||
resourceName := name[index+1:]
|
||||
resourceId := uuid.New()
|
||||
resource, err = parent.CreateMemberResource(ctx, resourceId, resourceName, false)
|
||||
resource, err = a.fs.CreateMemberResource(parent, resourceId, resourceName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource.OpenWrite(ctx)
|
||||
return a.fs.OpenWrite(resource)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if resource.IsDir() {
|
||||
return nil, errors.New("cannot open collection for write")
|
||||
}
|
||||
return resource.OpenWrite(ctx)
|
||||
return a.fs.OpenWrite(resource)
|
||||
}
|
||||
|
||||
func (a adapter) RemoveAll(ctx context.Context, name string) error {
|
||||
@@ -118,7 +124,7 @@ func (a adapter) RemoveAll(ctx context.Context, name string) error {
|
||||
if err != nil {
|
||||
return fs.ErrNotExist
|
||||
}
|
||||
return resource.DeleteRecursive(ctx, false)
|
||||
return a.fs.DeleteRecursive(resource, false)
|
||||
}
|
||||
|
||||
func (a adapter) Mkdir(ctx context.Context, name string) error {
|
||||
@@ -133,7 +139,7 @@ func (a adapter) Mkdir(ctx context.Context, name string) error {
|
||||
return fs.ErrNotExist
|
||||
}
|
||||
dirName := name[index+1:]
|
||||
_, err = parent.CreateMemberResource(ctx, uuid.New(), dirName, true)
|
||||
_, err = a.fs.CreateMemberResource(parent, uuid.New(), dirName, true)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -158,13 +164,13 @@ func (a adapter) Rename(ctx context.Context, oldName, newName string) error {
|
||||
return fs.ErrNotExist
|
||||
}
|
||||
|
||||
if src.ParentID() != nil && *src.ParentID() != parent.ID() {
|
||||
if err = src.UpdateParent(ctx, parent.ID()); err != nil {
|
||||
if src.ParentID() != parent.ID() {
|
||||
if err = a.fs.UpdateParent(src, parent.ID()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if src.Name() != newName && newName != "" && newName != "/" {
|
||||
if err = src.UpdateName(ctx, newName); err != nil {
|
||||
if err = a.fs.UpdateName(src, newName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
CREATE TABLE resources (
|
||||
id uuid PRIMARY KEY,
|
||||
parent uuid REFERENCES resources(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
parent uuid NOT NULL REFERENCES resources(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
dir BOOLEAN NOT NULL,
|
||||
created TIMESTAMP NOT NULL,
|
||||
|
||||
@@ -24,7 +24,7 @@ type Permission struct {
|
||||
|
||||
type Resource struct {
|
||||
ID uuid.UUID
|
||||
Parent *uuid.UUID
|
||||
Parent uuid.UUID
|
||||
Name string
|
||||
Dir bool
|
||||
Created pgtype.Timestamp
|
||||
|
||||
@@ -12,32 +12,68 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const permissionsForResource = `-- name: PermissionsForResource :one
|
||||
const resourceByIdWithPermissions = `-- name: ResourceByIdWithPermissions :one
|
||||
WITH RECURSIVE nodes(id, parent, permission) AS (
|
||||
SELECT r.id, r.parent, p.permission
|
||||
FROM resources r LEFT JOIN permissions p on r.id = p.resource_id WHERE r.id = $1::uuid AND p.user_id = $2::int
|
||||
FROM resources r
|
||||
LEFT JOIN permissions p
|
||||
on r.id = p.resource_id
|
||||
AND p.user_id = $2::int
|
||||
WHERE r.id = $3::uuid
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, CASE WHEN (n.permission IS NULL OR p.permission > n.permission) THEN p.permission ELSE n.permission END
|
||||
FROM resources r JOIN nodes n ON r.id = n.parent LEFT JOIN permissions p ON r.id = p.resource_id AND p.user_id = $2::int
|
||||
FROM resources r
|
||||
JOIN nodes n
|
||||
ON r.id = n.parent
|
||||
LEFT JOIN permissions p
|
||||
ON r.id = p.resource_id AND p.user_id = $2::int
|
||||
WHERE r.parent = $1::uuid
|
||||
OR r.parent = '00000000-0000-0000-0000-000000000000'
|
||||
)
|
||||
SELECT id, parent, permission FROM nodes WHERE parent IS NULL
|
||||
SELECT n.id, n.parent, permission, r.id, r.parent, name, dir, created, modified, deleted, size, etag FROM nodes n
|
||||
JOIN resources r
|
||||
ON n.id = r.id
|
||||
WHERE n.parent = $1::uuid
|
||||
`
|
||||
|
||||
type PermissionsForResourceParams struct {
|
||||
ResourceID uuid.UUID
|
||||
type ResourceByIdWithPermissionsParams struct {
|
||||
Root uuid.UUID
|
||||
UserID int32
|
||||
ResourceID uuid.UUID
|
||||
}
|
||||
|
||||
type PermissionsForResourceRow struct {
|
||||
type ResourceByIdWithPermissionsRow struct {
|
||||
ID uuid.UUID
|
||||
Parent *uuid.UUID
|
||||
Parent uuid.UUID
|
||||
Permission pgtype.Int4
|
||||
ID_2 uuid.UUID
|
||||
Parent_2 uuid.UUID
|
||||
Name string
|
||||
Dir bool
|
||||
Created pgtype.Timestamp
|
||||
Modified pgtype.Timestamp
|
||||
Deleted pgtype.Timestamp
|
||||
Size pgtype.Int8
|
||||
Etag pgtype.Text
|
||||
}
|
||||
|
||||
func (q *Queries) PermissionsForResource(ctx context.Context, arg PermissionsForResourceParams) (PermissionsForResourceRow, error) {
|
||||
row := q.db.QueryRow(ctx, permissionsForResource, arg.ResourceID, arg.UserID)
|
||||
var i PermissionsForResourceRow
|
||||
err := row.Scan(&i.ID, &i.Parent, &i.Permission)
|
||||
func (q *Queries) ResourceByIdWithPermissions(ctx context.Context, arg ResourceByIdWithPermissionsParams) (ResourceByIdWithPermissionsRow, error) {
|
||||
row := q.db.QueryRow(ctx, resourceByIdWithPermissions, arg.Root, arg.UserID, arg.ResourceID)
|
||||
var i ResourceByIdWithPermissionsRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Parent,
|
||||
&i.Permission,
|
||||
&i.ID_2,
|
||||
&i.Parent_2,
|
||||
&i.Name,
|
||||
&i.Dir,
|
||||
&i.Created,
|
||||
&i.Modified,
|
||||
&i.Deleted,
|
||||
&i.Size,
|
||||
&i.Etag,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ INSERT INTO resources(
|
||||
|
||||
type CreateResourceParams struct {
|
||||
ID uuid.UUID
|
||||
Parent *uuid.UUID
|
||||
Parent uuid.UUID
|
||||
Name string
|
||||
Dir bool
|
||||
}
|
||||
@@ -137,7 +137,7 @@ type ReadDirParams struct {
|
||||
|
||||
type ReadDirRow struct {
|
||||
ID uuid.UUID
|
||||
Parent *uuid.UUID
|
||||
Parent uuid.UUID
|
||||
Name string
|
||||
Dir bool
|
||||
Created pgtype.Timestamp
|
||||
@@ -236,7 +236,7 @@ type ResourceByPathParams struct {
|
||||
|
||||
type ResourceByPathRow struct {
|
||||
ID uuid.UUID
|
||||
Parent *uuid.UUID
|
||||
Parent uuid.UUID
|
||||
Name string
|
||||
Dir bool
|
||||
Created pgtype.Timestamp
|
||||
|
||||
@@ -23,6 +23,8 @@ type FileSystem interface {
|
||||
Mkdir(ctx context.Context, name string) error
|
||||
RemoveAll(ctx context.Context, name string) error
|
||||
Rename(ctx context.Context, oldName, newName string) error
|
||||
ReadDir(r core.Resource) ([]core.Resource, error)
|
||||
OpenRead(r core.Resource, start, length int64) (io.ReadCloser, error)
|
||||
OpenWrite(ctx context.Context, name string) (io.WriteCloser, error)
|
||||
}
|
||||
|
||||
@@ -97,7 +99,7 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
if depth == infiniteDepth {
|
||||
children, err := srcStat.ReadDir(ctx)
|
||||
children, err := fs.ReadDir(srcStat)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
@@ -114,7 +116,7 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo
|
||||
}
|
||||
|
||||
} else {
|
||||
srcFile, err := srcStat.OpenRead(0, -1)
|
||||
srcFile, err := fs.OpenRead(srcStat, 0, -1)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return http.StatusNotFound, err
|
||||
@@ -174,7 +176,7 @@ func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info cor
|
||||
if err != nil {
|
||||
return walkFn(name, info, err)
|
||||
}
|
||||
fileInfos, err := f.ReadDir(ctx)
|
||||
fileInfos, err := fs.ReadDir(f)
|
||||
if err != nil {
|
||||
return walkFn(name, info, err)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func makePropstats(x, y Propstat) []Propstat {
|
||||
var liveProps = map[xml.Name]struct {
|
||||
// findFn implements the propfind function of this property. If nil,
|
||||
// it indicates a hidden property.
|
||||
findFn func(core.ResourceInfo) string
|
||||
findFn func(core.Resource) string
|
||||
// dir is true if the property applies to directories.
|
||||
dir bool
|
||||
}{
|
||||
@@ -265,34 +265,34 @@ func escapeXML(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func findResourceType(fi core.ResourceInfo) string {
|
||||
func findResourceType(fi core.Resource) string {
|
||||
if fi.IsDir() {
|
||||
return `<D:collection xmlns:D="DAV:"/>`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findDisplayName(fi core.ResourceInfo) string {
|
||||
func findDisplayName(fi core.Resource) string {
|
||||
return escapeXML(fi.Name())
|
||||
}
|
||||
|
||||
func findContentLength(fi core.ResourceInfo) string {
|
||||
func findContentLength(fi core.Resource) string {
|
||||
return strconv.FormatInt(fi.Size(), 10)
|
||||
}
|
||||
|
||||
func findLastModified(fi core.ResourceInfo) string {
|
||||
func findLastModified(fi core.Resource) string {
|
||||
return fi.ModTime().UTC().Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
func findContentType(fi core.ResourceInfo) string {
|
||||
func findContentType(fi core.Resource) string {
|
||||
return fi.ContentType()
|
||||
}
|
||||
|
||||
func findETag(fi core.ResourceInfo) string {
|
||||
func findETag(fi core.Resource) string {
|
||||
return fi.ETag()
|
||||
}
|
||||
|
||||
func findSupportedLock(fi core.ResourceInfo) string {
|
||||
func findSupportedLock(fi core.Resource) string {
|
||||
return `` +
|
||||
`<D:lockentry xmlns:D="DAV:">` +
|
||||
`<D:lockscope><D:exclusive/></D:lockscope>` +
|
||||
|
||||
@@ -25,7 +25,7 @@ var htmlReplacer = strings.NewReplacer(
|
||||
"'", "'",
|
||||
)
|
||||
|
||||
func serveCollection(w http.ResponseWriter, r *http.Request, file core.Resource) {
|
||||
func serveCollection(w http.ResponseWriter, r *http.Request, fs FileSystem, file core.Resource) {
|
||||
if !strings.HasSuffix(r.URL.Path, "/") {
|
||||
http.Redirect(w, r, r.URL.String()+"/", http.StatusMovedPermanently)
|
||||
return
|
||||
@@ -36,7 +36,7 @@ func serveCollection(w http.ResponseWriter, r *http.Request, file core.Resource)
|
||||
}
|
||||
w.Header().Set("Last-Modified", file.ModTime().Format(http.TimeFormat))
|
||||
|
||||
files, err := file.ReadDir(r.Context())
|
||||
files, err := fs.ReadDir(file)
|
||||
if err != nil {
|
||||
http.Error(w, "Error reading directory", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -59,7 +59,7 @@ func serveCollection(w http.ResponseWriter, r *http.Request, file core.Resource)
|
||||
fmt.Fprintf(w, "</pre>\n")
|
||||
}
|
||||
|
||||
func serveResource(w http.ResponseWriter, r *http.Request, file core.Resource) {
|
||||
func serveResource(w http.ResponseWriter, r *http.Request, fs FileSystem, file core.Resource) {
|
||||
w.Header().Set("Etag", file.ETag())
|
||||
w.Header().Set("Last-Modified", file.ModTime().Format(http.TimeFormat))
|
||||
w.Header().Set("Content-Type", file.ContentType())
|
||||
@@ -95,9 +95,9 @@ func serveResource(w http.ResponseWriter, r *http.Request, file core.Resource) {
|
||||
sendSize = ra.length
|
||||
code = http.StatusPartialContent
|
||||
w.Header().Set("Content-Range", ra.contentRange(file.Size()))
|
||||
reader, err = file.OpenRead(ra.start, ra.length)
|
||||
reader, err = fs.OpenRead(file, ra.start, ra.length)
|
||||
} else {
|
||||
reader, err = file.OpenRead(0, -1)
|
||||
reader, err = fs.OpenRead(file, 0, -1)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -206,7 +206,7 @@ func parseRange(s string, size int64) ([]httpRange, error) {
|
||||
|
||||
// checkPreconditions evaluates request preconditions and reports whether a precondition
|
||||
// resulted in sending StatusNotModified or StatusPreconditionFailed.
|
||||
func checkPreconditions(w http.ResponseWriter, r *http.Request, ri core.ResourceInfo) (done bool, rangeHeader string) {
|
||||
func checkPreconditions(w http.ResponseWriter, r *http.Request, ri core.Resource) (done bool, rangeHeader string) {
|
||||
// This function carefully follows RFC 7232 section 6.
|
||||
ch := checkIfMatch(r, ri)
|
||||
if ch == condNone {
|
||||
@@ -289,7 +289,7 @@ const (
|
||||
condFalse
|
||||
)
|
||||
|
||||
func checkIfMatch(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
func checkIfMatch(r *http.Request, ri core.Resource) condResult {
|
||||
im := r.Header.Get("If-Match")
|
||||
if im == "" {
|
||||
return condNone
|
||||
@@ -319,7 +319,7 @@ func checkIfMatch(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
return condFalse
|
||||
}
|
||||
|
||||
func checkIfUnmodifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
func checkIfUnmodifiedSince(r *http.Request, ri core.Resource) condResult {
|
||||
ius := r.Header.Get("If-Unmodified-Since")
|
||||
if ius == "" || isZeroTime(ri.ModTime()) {
|
||||
return condNone
|
||||
@@ -338,7 +338,7 @@ func checkIfUnmodifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
return condFalse
|
||||
}
|
||||
|
||||
func checkIfNoneMatch(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
func checkIfNoneMatch(r *http.Request, ri core.Resource) condResult {
|
||||
inm := r.Header.Get("If-None-Match")
|
||||
if inm == "" {
|
||||
return condNone
|
||||
@@ -368,7 +368,7 @@ func checkIfNoneMatch(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
return condTrue
|
||||
}
|
||||
|
||||
func checkIfModifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
func checkIfModifiedSince(r *http.Request, ri core.Resource) condResult {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
return condNone
|
||||
}
|
||||
@@ -389,7 +389,7 @@ func checkIfModifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
return condTrue
|
||||
}
|
||||
|
||||
func checkIfRange(r *http.Request, ri core.ResourceInfo) condResult {
|
||||
func checkIfRange(r *http.Request, ri core.Resource) condResult {
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
return condNone
|
||||
}
|
||||
|
||||
@@ -206,9 +206,9 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
if file.IsDir() {
|
||||
serveCollection(w, r, file)
|
||||
serveCollection(w, r, h.FileSystem, file)
|
||||
} else {
|
||||
serveResource(w, r, file)
|
||||
serveResource(w, r, h.FileSystem, file)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
@@ -625,7 +625,7 @@ func makePropstatResponse(href string, pstats []Propstat) *response {
|
||||
return &resp
|
||||
}
|
||||
|
||||
func handlePropfindError(err error, info core.ResourceInfo) error {
|
||||
func handlePropfindError(err error, info core.Resource) error {
|
||||
var skipResp error = nil
|
||||
if info.IsDir() {
|
||||
skipResp = filepath.SkipDir
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
|
||||
-- name: PermissionsForResource :one
|
||||
-- name: ResourceByIdWithPermissions :one
|
||||
WITH RECURSIVE nodes(id, parent, permission) AS (
|
||||
SELECT r.id, r.parent, p.permission
|
||||
FROM resources r LEFT JOIN permissions p on r.id = p.resource_id WHERE r.id = @resource_id::uuid AND p.user_id = @user_id::int
|
||||
FROM resources r
|
||||
LEFT JOIN permissions p
|
||||
on r.id = p.resource_id
|
||||
AND p.user_id = @user_id::int
|
||||
WHERE r.id = @resource_id::uuid
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, CASE WHEN (n.permission IS NULL OR p.permission > n.permission) THEN p.permission ELSE n.permission END
|
||||
FROM resources r JOIN nodes n ON r.id = n.parent LEFT JOIN permissions p ON r.id = p.resource_id AND p.user_id = @user_id::int
|
||||
FROM resources r
|
||||
JOIN nodes n
|
||||
ON r.id = n.parent
|
||||
LEFT JOIN permissions p
|
||||
ON r.id = p.resource_id AND p.user_id = @user_id::int
|
||||
WHERE r.parent = @root::uuid
|
||||
OR r.parent = '00000000-0000-0000-0000-000000000000'
|
||||
)
|
||||
SELECT * FROM nodes WHERE parent IS NULL;
|
||||
SELECT * FROM nodes n
|
||||
JOIN resources r
|
||||
ON n.id = r.id
|
||||
WHERE n.parent = @root::uuid;
|
||||
|
||||
-- name: UpdatePermissionsForResource :exec
|
||||
INSERT INTO permissions(resource_id, user_id, permission)
|
||||
|
||||
Reference in New Issue
Block a user