Merge remote-tracking branch 'origin/master' into feat/storage-policy-adv

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3587/head
Tomas Dvorak 2 weeks ago
commit d04e7a0783

@ -171,7 +171,7 @@ Order = user-visible value first; each ships with backend + UI + tests.
1. **Share collaboration** — write/upload/delete via share link, anonymous upload, share ACL (users/groups), preview-only mode (fixes #3555, #3390, #3340, #3517, #3578; uses `NavigatorCapability` placeholder slots + `ShareProps` extension + `share` entity fields). See §1.3a for the extracted UI spec. 1. **Share collaboration** — write/upload/delete via share link, anonymous upload, share ACL (users/groups), preview-only mode (fixes #3555, #3390, #3340, #3517, #3578; uses `NavigatorCapability` placeholder slots + `ShareProps` extension + `share` entity fields). See §1.3a for the extracted UI spec.
- [x] PR #140 — `allow_upload`/`allow_edit`/`preview_only`/`upload_only` props, props-derived capability sets enforced server-side (`writePermitted`), same-share move/copy, anonymous upload, drop-box listing suppression, download denial via `IsDownloadCtxKey` hooks (fixes #3555 preview-only, #3340 drop-box) - [x] PR #140 — `allow_upload`/`allow_edit`/`preview_only`/`upload_only` props, props-derived capability sets enforced server-side (`writePermitted`), same-share move/copy, anonymous upload, drop-box listing suppression, download denial via `IsDownloadCtxKey` hooks (fixes #3555 preview-only, #3340 drop-box)
- [ ] File/dir ACL entity — `(subject_type ∈ user|group, subject_id)` → R/C/U/D bitmask + anonymous/everyone tiers; Permissions dialog under More actions; enforced in navigator capability checks (#3517) - [x] File/dir ACL entity — `(subject_type ∈ user|group|anonymous|everyone, subject_id)` → R/C/U/D bitmask; Permissions dialog under More actions; enforced in share-navigator capability checks; group bit 15 gate (#181, fixes #3517)
- [x] Default shares — `setting.default_symbolics` + group `default_pinned` chip-input of share IDs; materialize as share-shortcut entries on fs init (#180) - [x] Default shares — `setting.default_symbolics` + group `default_pinned` chip-input of share IDs; materialize as share-shortcut entries on fs init (#180)
- [ ] Paid shares — `share.price_points` + gate page + purchase/resume-ticket flow; needs B.4 credits first - [ ] Paid shares — `share.price_points` + gate page + purchase/resume-ticket flow; needs B.4 credits first
2. **Storage policy advanced** — multiple policies per group, per-directory binding, load-balancer policy, file migration (fixes #3518, #2961, #2262). See §1.3a. 2. **Storage policy advanced** — multiple policies per group, per-directory binding, load-balancer policy, file migration (fixes #3518, #2961, #2262). See §1.3a.

@ -106,6 +106,8 @@ type Dep interface {
RequestClient(opts ...request.Option) request.Client RequestClient(opts ...request.Option) request.Client
// ShareClient Get a singleton inventory.ShareClient instance for access DB share store. // ShareClient Get a singleton inventory.ShareClient instance for access DB share store.
ShareClient() inventory.ShareClient ShareClient() inventory.ShareClient
// AclClient Get a singleton inventory.AclClient instance for access DB ACL entry store.
AclClient() inventory.AclClient
// TaskClient Get a singleton inventory.TaskClient instance for access DB task store. // TaskClient Get a singleton inventory.TaskClient instance for access DB task store.
TaskClient() inventory.TaskClient TaskClient() inventory.TaskClient
// ForkWithLogger create a shallow copy of dependency with a new correlated logger, used as per-request dep. // ForkWithLogger create a shallow copy of dependency with a new correlated logger, used as per-request dep.
@ -162,6 +164,7 @@ type dependency struct {
settingClient inventory.SettingClient settingClient inventory.SettingClient
fileClient inventory.FileClient fileClient inventory.FileClient
shareClient inventory.ShareClient shareClient inventory.ShareClient
aclClient inventory.AclClient
settingProvider setting.Provider settingProvider setting.Provider
userClient inventory.UserClient userClient inventory.UserClient
groupClient inventory.GroupClient groupClient inventory.GroupClient
@ -822,6 +825,15 @@ func (d *dependency) ShareClient() inventory.ShareClient {
return d.shareClient return d.shareClient
} }
func (d *dependency) AclClient() inventory.AclClient {
if d.aclClient != nil {
return d.aclClient
}
d.aclClient = inventory.NewAclClient(d.DBClient(), d.ConfigProvider().Database().Type)
return d.aclClient
}
func (d *dependency) TaskClient() inventory.TaskClient { func (d *dependency) TaskClient() inventory.TaskClient {
if d.taskClient != nil { if d.taskClient != nil {
return d.taskClient return d.taskClient

@ -0,0 +1,215 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
)
// AclEntry is the model entity for the AclEntry schema.
type AclEntry struct {
config `json:"-"`
// ID of the ent.
ID int `json:"id,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// DeletedAt holds the value of the "deleted_at" field.
DeletedAt *time.Time `json:"deleted_at,omitempty"`
// FileID holds the value of the "file_id" field.
FileID int `json:"file_id,omitempty"`
// SubjectType holds the value of the "subject_type" field.
SubjectType aclentry.SubjectType `json:"subject_type,omitempty"`
// SubjectID holds the value of the "subject_id" field.
SubjectID int `json:"subject_id,omitempty"`
// Permissions holds the value of the "permissions" field.
Permissions *boolset.BooleanSet `json:"permissions,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AclEntryQuery when eager-loading is set.
Edges AclEntryEdges `json:"edges"`
selectValues sql.SelectValues
}
// AclEntryEdges holds the relations/edges for other nodes in the graph.
type AclEntryEdges struct {
// File holds the value of the file edge.
File *File `json:"file,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// FileOrErr returns the File value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AclEntryEdges) FileOrErr() (*File, error) {
if e.loadedTypes[0] {
if e.File == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: file.Label}
}
return e.File, nil
}
return nil, &NotLoadedError{edge: "file"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*AclEntry) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case aclentry.FieldPermissions:
values[i] = new(boolset.BooleanSet)
case aclentry.FieldID, aclentry.FieldFileID, aclentry.FieldSubjectID:
values[i] = new(sql.NullInt64)
case aclentry.FieldSubjectType:
values[i] = new(sql.NullString)
case aclentry.FieldCreatedAt, aclentry.FieldUpdatedAt, aclentry.FieldDeletedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the AclEntry fields.
func (ae *AclEntry) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case aclentry.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
ae.ID = int(value.Int64)
case aclentry.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
ae.CreatedAt = value.Time
}
case aclentry.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
ae.UpdatedAt = value.Time
}
case aclentry.FieldDeletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field deleted_at", values[i])
} else if value.Valid {
ae.DeletedAt = new(time.Time)
*ae.DeletedAt = value.Time
}
case aclentry.FieldFileID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field file_id", values[i])
} else if value.Valid {
ae.FileID = int(value.Int64)
}
case aclentry.FieldSubjectType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field subject_type", values[i])
} else if value.Valid {
ae.SubjectType = aclentry.SubjectType(value.String)
}
case aclentry.FieldSubjectID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field subject_id", values[i])
} else if value.Valid {
ae.SubjectID = int(value.Int64)
}
case aclentry.FieldPermissions:
if value, ok := values[i].(*boolset.BooleanSet); !ok {
return fmt.Errorf("unexpected type %T for field permissions", values[i])
} else if value != nil {
ae.Permissions = value
}
default:
ae.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the AclEntry.
// This includes values selected through modifiers, order, etc.
func (ae *AclEntry) Value(name string) (ent.Value, error) {
return ae.selectValues.Get(name)
}
// QueryFile queries the "file" edge of the AclEntry entity.
func (ae *AclEntry) QueryFile() *FileQuery {
return NewAclEntryClient(ae.config).QueryFile(ae)
}
// Update returns a builder for updating this AclEntry.
// Note that you need to call AclEntry.Unwrap() before calling this method if this AclEntry
// was returned from a transaction, and the transaction was committed or rolled back.
func (ae *AclEntry) Update() *AclEntryUpdateOne {
return NewAclEntryClient(ae.config).UpdateOne(ae)
}
// Unwrap unwraps the AclEntry entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (ae *AclEntry) Unwrap() *AclEntry {
_tx, ok := ae.config.driver.(*txDriver)
if !ok {
panic("ent: AclEntry is not a transactional entity")
}
ae.config.driver = _tx.drv
return ae
}
// String implements the fmt.Stringer.
func (ae *AclEntry) String() string {
var builder strings.Builder
builder.WriteString("AclEntry(")
builder.WriteString(fmt.Sprintf("id=%v, ", ae.ID))
builder.WriteString("created_at=")
builder.WriteString(ae.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(ae.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", ")
if v := ae.DeletedAt; v != nil {
builder.WriteString("deleted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("file_id=")
builder.WriteString(fmt.Sprintf("%v", ae.FileID))
builder.WriteString(", ")
builder.WriteString("subject_type=")
builder.WriteString(fmt.Sprintf("%v", ae.SubjectType))
builder.WriteString(", ")
builder.WriteString("subject_id=")
builder.WriteString(fmt.Sprintf("%v", ae.SubjectID))
builder.WriteString(", ")
builder.WriteString("permissions=")
builder.WriteString(fmt.Sprintf("%v", ae.Permissions))
builder.WriteByte(')')
return builder.String()
}
// SetFile manually set the edge as loaded state.
func (e *AclEntry) SetFile(v *File) {
e.Edges.File = v
e.Edges.loadedTypes[0] = true
}
// AclEntries is a parsable slice of AclEntry.
type AclEntries []*AclEntry

@ -0,0 +1,159 @@
// Code generated by ent, DO NOT EDIT.
package aclentry
import (
"fmt"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the aclentry type in the database.
Label = "acl_entry"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldDeletedAt holds the string denoting the deleted_at field in the database.
FieldDeletedAt = "deleted_at"
// FieldFileID holds the string denoting the file_id field in the database.
FieldFileID = "file_id"
// FieldSubjectType holds the string denoting the subject_type field in the database.
FieldSubjectType = "subject_type"
// FieldSubjectID holds the string denoting the subject_id field in the database.
FieldSubjectID = "subject_id"
// FieldPermissions holds the string denoting the permissions field in the database.
FieldPermissions = "permissions"
// EdgeFile holds the string denoting the file edge name in mutations.
EdgeFile = "file"
// Table holds the table name of the aclentry in the database.
Table = "acl_entries"
// FileTable is the table that holds the file relation/edge.
FileTable = "acl_entries"
// FileInverseTable is the table name for the File entity.
// It exists in this package in order to avoid circular dependency with the "file" package.
FileInverseTable = "files"
// FileColumn is the table column denoting the file relation/edge.
FileColumn = "file_id"
)
// Columns holds all SQL columns for aclentry fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldDeletedAt,
FieldFileID,
FieldSubjectType,
FieldSubjectID,
FieldPermissions,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
// Note that the variables below are initialized by the runtime
// package on the initialization of the application. Therefore,
// it should be imported in the main as follows:
//
// import _ "github.com/cloudreve/Cloudreve/v4/ent/runtime"
var (
Hooks [1]ent.Hook
Interceptors [1]ent.Interceptor
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
)
// SubjectType defines the type for the "subject_type" enum field.
type SubjectType string
// SubjectType values.
const (
SubjectTypeUser SubjectType = "user"
SubjectTypeGroup SubjectType = "group"
SubjectTypeAnonymous SubjectType = "anonymous"
SubjectTypeEveryone SubjectType = "everyone"
)
func (st SubjectType) String() string {
return string(st)
}
// SubjectTypeValidator is a validator for the "subject_type" field enum values. It is called by the builders before save.
func SubjectTypeValidator(st SubjectType) error {
switch st {
case SubjectTypeUser, SubjectTypeGroup, SubjectTypeAnonymous, SubjectTypeEveryone:
return nil
default:
return fmt.Errorf("aclentry: invalid enum value for subject_type field: %q", st)
}
}
// OrderOption defines the ordering options for the AclEntry queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByDeletedAt orders the results by the deleted_at field.
func ByDeletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDeletedAt, opts...).ToFunc()
}
// ByFileID orders the results by the file_id field.
func ByFileID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFileID, opts...).ToFunc()
}
// BySubjectType orders the results by the subject_type field.
func BySubjectType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubjectType, opts...).ToFunc()
}
// BySubjectID orders the results by the subject_id field.
func BySubjectID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubjectID, opts...).ToFunc()
}
// ByFileField orders the results by file field.
func ByFileField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newFileStep(), sql.OrderByField(field, opts...))
}
}
func newFileStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(FileInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, FileTable, FileColumn),
)
}

@ -0,0 +1,385 @@
// Code generated by ent, DO NOT EDIT.
package aclentry
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
)
// ID filters vertices based on their ID field.
func ID(id int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLTE(FieldID, id))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldUpdatedAt, v))
}
// DeletedAt applies equality check predicate on the "deleted_at" field. It's identical to DeletedAtEQ.
func DeletedAt(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldDeletedAt, v))
}
// FileID applies equality check predicate on the "file_id" field. It's identical to FileIDEQ.
func FileID(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldFileID, v))
}
// SubjectID applies equality check predicate on the "subject_id" field. It's identical to SubjectIDEQ.
func SubjectID(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldSubjectID, v))
}
// Permissions applies equality check predicate on the "permissions" field. It's identical to PermissionsEQ.
func Permissions(v *boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldPermissions, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLTE(FieldUpdatedAt, v))
}
// DeletedAtEQ applies the EQ predicate on the "deleted_at" field.
func DeletedAtEQ(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldDeletedAt, v))
}
// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field.
func DeletedAtNEQ(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldDeletedAt, v))
}
// DeletedAtIn applies the In predicate on the "deleted_at" field.
func DeletedAtIn(vs ...time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldDeletedAt, vs...))
}
// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field.
func DeletedAtNotIn(vs ...time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldDeletedAt, vs...))
}
// DeletedAtGT applies the GT predicate on the "deleted_at" field.
func DeletedAtGT(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGT(FieldDeletedAt, v))
}
// DeletedAtGTE applies the GTE predicate on the "deleted_at" field.
func DeletedAtGTE(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGTE(FieldDeletedAt, v))
}
// DeletedAtLT applies the LT predicate on the "deleted_at" field.
func DeletedAtLT(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLT(FieldDeletedAt, v))
}
// DeletedAtLTE applies the LTE predicate on the "deleted_at" field.
func DeletedAtLTE(v time.Time) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLTE(FieldDeletedAt, v))
}
// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field.
func DeletedAtIsNil() predicate.AclEntry {
return predicate.AclEntry(sql.FieldIsNull(FieldDeletedAt))
}
// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field.
func DeletedAtNotNil() predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotNull(FieldDeletedAt))
}
// FileIDEQ applies the EQ predicate on the "file_id" field.
func FileIDEQ(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldFileID, v))
}
// FileIDNEQ applies the NEQ predicate on the "file_id" field.
func FileIDNEQ(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldFileID, v))
}
// FileIDIn applies the In predicate on the "file_id" field.
func FileIDIn(vs ...int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldFileID, vs...))
}
// FileIDNotIn applies the NotIn predicate on the "file_id" field.
func FileIDNotIn(vs ...int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldFileID, vs...))
}
// SubjectTypeEQ applies the EQ predicate on the "subject_type" field.
func SubjectTypeEQ(v SubjectType) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldSubjectType, v))
}
// SubjectTypeNEQ applies the NEQ predicate on the "subject_type" field.
func SubjectTypeNEQ(v SubjectType) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldSubjectType, v))
}
// SubjectTypeIn applies the In predicate on the "subject_type" field.
func SubjectTypeIn(vs ...SubjectType) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldSubjectType, vs...))
}
// SubjectTypeNotIn applies the NotIn predicate on the "subject_type" field.
func SubjectTypeNotIn(vs ...SubjectType) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldSubjectType, vs...))
}
// SubjectIDEQ applies the EQ predicate on the "subject_id" field.
func SubjectIDEQ(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldSubjectID, v))
}
// SubjectIDNEQ applies the NEQ predicate on the "subject_id" field.
func SubjectIDNEQ(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldSubjectID, v))
}
// SubjectIDIn applies the In predicate on the "subject_id" field.
func SubjectIDIn(vs ...int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldSubjectID, vs...))
}
// SubjectIDNotIn applies the NotIn predicate on the "subject_id" field.
func SubjectIDNotIn(vs ...int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldSubjectID, vs...))
}
// SubjectIDGT applies the GT predicate on the "subject_id" field.
func SubjectIDGT(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGT(FieldSubjectID, v))
}
// SubjectIDGTE applies the GTE predicate on the "subject_id" field.
func SubjectIDGTE(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGTE(FieldSubjectID, v))
}
// SubjectIDLT applies the LT predicate on the "subject_id" field.
func SubjectIDLT(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLT(FieldSubjectID, v))
}
// SubjectIDLTE applies the LTE predicate on the "subject_id" field.
func SubjectIDLTE(v int) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLTE(FieldSubjectID, v))
}
// SubjectIDIsNil applies the IsNil predicate on the "subject_id" field.
func SubjectIDIsNil() predicate.AclEntry {
return predicate.AclEntry(sql.FieldIsNull(FieldSubjectID))
}
// SubjectIDNotNil applies the NotNil predicate on the "subject_id" field.
func SubjectIDNotNil() predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotNull(FieldSubjectID))
}
// PermissionsEQ applies the EQ predicate on the "permissions" field.
func PermissionsEQ(v *boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldEQ(FieldPermissions, v))
}
// PermissionsNEQ applies the NEQ predicate on the "permissions" field.
func PermissionsNEQ(v *boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNEQ(FieldPermissions, v))
}
// PermissionsIn applies the In predicate on the "permissions" field.
func PermissionsIn(vs ...*boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldIn(FieldPermissions, vs...))
}
// PermissionsNotIn applies the NotIn predicate on the "permissions" field.
func PermissionsNotIn(vs ...*boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldNotIn(FieldPermissions, vs...))
}
// PermissionsGT applies the GT predicate on the "permissions" field.
func PermissionsGT(v *boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGT(FieldPermissions, v))
}
// PermissionsGTE applies the GTE predicate on the "permissions" field.
func PermissionsGTE(v *boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldGTE(FieldPermissions, v))
}
// PermissionsLT applies the LT predicate on the "permissions" field.
func PermissionsLT(v *boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLT(FieldPermissions, v))
}
// PermissionsLTE applies the LTE predicate on the "permissions" field.
func PermissionsLTE(v *boolset.BooleanSet) predicate.AclEntry {
return predicate.AclEntry(sql.FieldLTE(FieldPermissions, v))
}
// HasFile applies the HasEdge predicate on the "file" edge.
func HasFile() predicate.AclEntry {
return predicate.AclEntry(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, FileTable, FileColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasFileWith applies the HasEdge predicate on the "file" edge with a given conditions (other predicates).
func HasFileWith(preds ...predicate.File) predicate.AclEntry {
return predicate.AclEntry(func(s *sql.Selector) {
step := newFileStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.AclEntry) predicate.AclEntry {
return predicate.AclEntry(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.AclEntry) predicate.AclEntry {
return predicate.AclEntry(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.AclEntry) predicate.AclEntry {
return predicate.AclEntry(sql.NotPredicates(p))
}

@ -0,0 +1,894 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
)
// AclEntryCreate is the builder for creating a AclEntry entity.
type AclEntryCreate struct {
config
mutation *AclEntryMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetCreatedAt sets the "created_at" field.
func (aec *AclEntryCreate) SetCreatedAt(t time.Time) *AclEntryCreate {
aec.mutation.SetCreatedAt(t)
return aec
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (aec *AclEntryCreate) SetNillableCreatedAt(t *time.Time) *AclEntryCreate {
if t != nil {
aec.SetCreatedAt(*t)
}
return aec
}
// SetUpdatedAt sets the "updated_at" field.
func (aec *AclEntryCreate) SetUpdatedAt(t time.Time) *AclEntryCreate {
aec.mutation.SetUpdatedAt(t)
return aec
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (aec *AclEntryCreate) SetNillableUpdatedAt(t *time.Time) *AclEntryCreate {
if t != nil {
aec.SetUpdatedAt(*t)
}
return aec
}
// SetDeletedAt sets the "deleted_at" field.
func (aec *AclEntryCreate) SetDeletedAt(t time.Time) *AclEntryCreate {
aec.mutation.SetDeletedAt(t)
return aec
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (aec *AclEntryCreate) SetNillableDeletedAt(t *time.Time) *AclEntryCreate {
if t != nil {
aec.SetDeletedAt(*t)
}
return aec
}
// SetFileID sets the "file_id" field.
func (aec *AclEntryCreate) SetFileID(i int) *AclEntryCreate {
aec.mutation.SetFileID(i)
return aec
}
// SetSubjectType sets the "subject_type" field.
func (aec *AclEntryCreate) SetSubjectType(at aclentry.SubjectType) *AclEntryCreate {
aec.mutation.SetSubjectType(at)
return aec
}
// SetSubjectID sets the "subject_id" field.
func (aec *AclEntryCreate) SetSubjectID(i int) *AclEntryCreate {
aec.mutation.SetSubjectID(i)
return aec
}
// SetNillableSubjectID sets the "subject_id" field if the given value is not nil.
func (aec *AclEntryCreate) SetNillableSubjectID(i *int) *AclEntryCreate {
if i != nil {
aec.SetSubjectID(*i)
}
return aec
}
// SetPermissions sets the "permissions" field.
func (aec *AclEntryCreate) SetPermissions(bs *boolset.BooleanSet) *AclEntryCreate {
aec.mutation.SetPermissions(bs)
return aec
}
// SetFile sets the "file" edge to the File entity.
func (aec *AclEntryCreate) SetFile(f *File) *AclEntryCreate {
return aec.SetFileID(f.ID)
}
// Mutation returns the AclEntryMutation object of the builder.
func (aec *AclEntryCreate) Mutation() *AclEntryMutation {
return aec.mutation
}
// Save creates the AclEntry in the database.
func (aec *AclEntryCreate) Save(ctx context.Context) (*AclEntry, error) {
if err := aec.defaults(); err != nil {
return nil, err
}
return withHooks(ctx, aec.sqlSave, aec.mutation, aec.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (aec *AclEntryCreate) SaveX(ctx context.Context) *AclEntry {
v, err := aec.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (aec *AclEntryCreate) Exec(ctx context.Context) error {
_, err := aec.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (aec *AclEntryCreate) ExecX(ctx context.Context) {
if err := aec.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (aec *AclEntryCreate) defaults() error {
if _, ok := aec.mutation.CreatedAt(); !ok {
if aclentry.DefaultCreatedAt == nil {
return fmt.Errorf("ent: uninitialized aclentry.DefaultCreatedAt (forgotten import ent/runtime?)")
}
v := aclentry.DefaultCreatedAt()
aec.mutation.SetCreatedAt(v)
}
if _, ok := aec.mutation.UpdatedAt(); !ok {
if aclentry.DefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized aclentry.DefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := aclentry.DefaultUpdatedAt()
aec.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (aec *AclEntryCreate) check() error {
if _, ok := aec.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "AclEntry.created_at"`)}
}
if _, ok := aec.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "AclEntry.updated_at"`)}
}
if _, ok := aec.mutation.FileID(); !ok {
return &ValidationError{Name: "file_id", err: errors.New(`ent: missing required field "AclEntry.file_id"`)}
}
if _, ok := aec.mutation.SubjectType(); !ok {
return &ValidationError{Name: "subject_type", err: errors.New(`ent: missing required field "AclEntry.subject_type"`)}
}
if v, ok := aec.mutation.SubjectType(); ok {
if err := aclentry.SubjectTypeValidator(v); err != nil {
return &ValidationError{Name: "subject_type", err: fmt.Errorf(`ent: validator failed for field "AclEntry.subject_type": %w`, err)}
}
}
if _, ok := aec.mutation.Permissions(); !ok {
return &ValidationError{Name: "permissions", err: errors.New(`ent: missing required field "AclEntry.permissions"`)}
}
if _, ok := aec.mutation.FileID(); !ok {
return &ValidationError{Name: "file", err: errors.New(`ent: missing required edge "AclEntry.file"`)}
}
return nil
}
func (aec *AclEntryCreate) sqlSave(ctx context.Context) (*AclEntry, error) {
if err := aec.check(); err != nil {
return nil, err
}
_node, _spec := aec.createSpec()
if err := sqlgraph.CreateNode(ctx, aec.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
id := _spec.ID.Value.(int64)
_node.ID = int(id)
aec.mutation.id = &_node.ID
aec.mutation.done = true
return _node, nil
}
func (aec *AclEntryCreate) createSpec() (*AclEntry, *sqlgraph.CreateSpec) {
var (
_node = &AclEntry{config: aec.config}
_spec = sqlgraph.NewCreateSpec(aclentry.Table, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt))
)
if id, ok := aec.mutation.ID(); ok {
_node.ID = id
id64 := int64(id)
_spec.ID.Value = id64
}
_spec.OnConflict = aec.conflict
if value, ok := aec.mutation.CreatedAt(); ok {
_spec.SetField(aclentry.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if value, ok := aec.mutation.UpdatedAt(); ok {
_spec.SetField(aclentry.FieldUpdatedAt, field.TypeTime, value)
_node.UpdatedAt = value
}
if value, ok := aec.mutation.DeletedAt(); ok {
_spec.SetField(aclentry.FieldDeletedAt, field.TypeTime, value)
_node.DeletedAt = &value
}
if value, ok := aec.mutation.SubjectType(); ok {
_spec.SetField(aclentry.FieldSubjectType, field.TypeEnum, value)
_node.SubjectType = value
}
if value, ok := aec.mutation.SubjectID(); ok {
_spec.SetField(aclentry.FieldSubjectID, field.TypeInt, value)
_node.SubjectID = value
}
if value, ok := aec.mutation.Permissions(); ok {
_spec.SetField(aclentry.FieldPermissions, field.TypeBytes, value)
_node.Permissions = value
}
if nodes := aec.mutation.FileIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: aclentry.FileTable,
Columns: []string{aclentry.FileColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.FileID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.AclEntry.Create().
// SetCreatedAt(v).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.AclEntryUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (aec *AclEntryCreate) OnConflict(opts ...sql.ConflictOption) *AclEntryUpsertOne {
aec.conflict = opts
return &AclEntryUpsertOne{
create: aec,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.AclEntry.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (aec *AclEntryCreate) OnConflictColumns(columns ...string) *AclEntryUpsertOne {
aec.conflict = append(aec.conflict, sql.ConflictColumns(columns...))
return &AclEntryUpsertOne{
create: aec,
}
}
type (
// AclEntryUpsertOne is the builder for "upsert"-ing
// one AclEntry node.
AclEntryUpsertOne struct {
create *AclEntryCreate
}
// AclEntryUpsert is the "OnConflict" setter.
AclEntryUpsert struct {
*sql.UpdateSet
}
)
// SetUpdatedAt sets the "updated_at" field.
func (u *AclEntryUpsert) SetUpdatedAt(v time.Time) *AclEntryUpsert {
u.Set(aclentry.FieldUpdatedAt, v)
return u
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *AclEntryUpsert) UpdateUpdatedAt() *AclEntryUpsert {
u.SetExcluded(aclentry.FieldUpdatedAt)
return u
}
// SetDeletedAt sets the "deleted_at" field.
func (u *AclEntryUpsert) SetDeletedAt(v time.Time) *AclEntryUpsert {
u.Set(aclentry.FieldDeletedAt, v)
return u
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *AclEntryUpsert) UpdateDeletedAt() *AclEntryUpsert {
u.SetExcluded(aclentry.FieldDeletedAt)
return u
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *AclEntryUpsert) ClearDeletedAt() *AclEntryUpsert {
u.SetNull(aclentry.FieldDeletedAt)
return u
}
// SetFileID sets the "file_id" field.
func (u *AclEntryUpsert) SetFileID(v int) *AclEntryUpsert {
u.Set(aclentry.FieldFileID, v)
return u
}
// UpdateFileID sets the "file_id" field to the value that was provided on create.
func (u *AclEntryUpsert) UpdateFileID() *AclEntryUpsert {
u.SetExcluded(aclentry.FieldFileID)
return u
}
// SetSubjectType sets the "subject_type" field.
func (u *AclEntryUpsert) SetSubjectType(v aclentry.SubjectType) *AclEntryUpsert {
u.Set(aclentry.FieldSubjectType, v)
return u
}
// UpdateSubjectType sets the "subject_type" field to the value that was provided on create.
func (u *AclEntryUpsert) UpdateSubjectType() *AclEntryUpsert {
u.SetExcluded(aclentry.FieldSubjectType)
return u
}
// SetSubjectID sets the "subject_id" field.
func (u *AclEntryUpsert) SetSubjectID(v int) *AclEntryUpsert {
u.Set(aclentry.FieldSubjectID, v)
return u
}
// UpdateSubjectID sets the "subject_id" field to the value that was provided on create.
func (u *AclEntryUpsert) UpdateSubjectID() *AclEntryUpsert {
u.SetExcluded(aclentry.FieldSubjectID)
return u
}
// AddSubjectID adds v to the "subject_id" field.
func (u *AclEntryUpsert) AddSubjectID(v int) *AclEntryUpsert {
u.Add(aclentry.FieldSubjectID, v)
return u
}
// ClearSubjectID clears the value of the "subject_id" field.
func (u *AclEntryUpsert) ClearSubjectID() *AclEntryUpsert {
u.SetNull(aclentry.FieldSubjectID)
return u
}
// SetPermissions sets the "permissions" field.
func (u *AclEntryUpsert) SetPermissions(v *boolset.BooleanSet) *AclEntryUpsert {
u.Set(aclentry.FieldPermissions, v)
return u
}
// UpdatePermissions sets the "permissions" field to the value that was provided on create.
func (u *AclEntryUpsert) UpdatePermissions() *AclEntryUpsert {
u.SetExcluded(aclentry.FieldPermissions)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.AclEntry.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *AclEntryUpsertOne) UpdateNewValues() *AclEntryUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
if _, exists := u.create.mutation.CreatedAt(); exists {
s.SetIgnore(aclentry.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.AclEntry.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *AclEntryUpsertOne) Ignore() *AclEntryUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *AclEntryUpsertOne) DoNothing() *AclEntryUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the AclEntryCreate.OnConflict
// documentation for more info.
func (u *AclEntryUpsertOne) Update(set func(*AclEntryUpsert)) *AclEntryUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&AclEntryUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *AclEntryUpsertOne) SetUpdatedAt(v time.Time) *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *AclEntryUpsertOne) UpdateUpdatedAt() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateUpdatedAt()
})
}
// SetDeletedAt sets the "deleted_at" field.
func (u *AclEntryUpsertOne) SetDeletedAt(v time.Time) *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.SetDeletedAt(v)
})
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *AclEntryUpsertOne) UpdateDeletedAt() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateDeletedAt()
})
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *AclEntryUpsertOne) ClearDeletedAt() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.ClearDeletedAt()
})
}
// SetFileID sets the "file_id" field.
func (u *AclEntryUpsertOne) SetFileID(v int) *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.SetFileID(v)
})
}
// UpdateFileID sets the "file_id" field to the value that was provided on create.
func (u *AclEntryUpsertOne) UpdateFileID() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateFileID()
})
}
// SetSubjectType sets the "subject_type" field.
func (u *AclEntryUpsertOne) SetSubjectType(v aclentry.SubjectType) *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.SetSubjectType(v)
})
}
// UpdateSubjectType sets the "subject_type" field to the value that was provided on create.
func (u *AclEntryUpsertOne) UpdateSubjectType() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateSubjectType()
})
}
// SetSubjectID sets the "subject_id" field.
func (u *AclEntryUpsertOne) SetSubjectID(v int) *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.SetSubjectID(v)
})
}
// AddSubjectID adds v to the "subject_id" field.
func (u *AclEntryUpsertOne) AddSubjectID(v int) *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.AddSubjectID(v)
})
}
// UpdateSubjectID sets the "subject_id" field to the value that was provided on create.
func (u *AclEntryUpsertOne) UpdateSubjectID() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateSubjectID()
})
}
// ClearSubjectID clears the value of the "subject_id" field.
func (u *AclEntryUpsertOne) ClearSubjectID() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.ClearSubjectID()
})
}
// SetPermissions sets the "permissions" field.
func (u *AclEntryUpsertOne) SetPermissions(v *boolset.BooleanSet) *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.SetPermissions(v)
})
}
// UpdatePermissions sets the "permissions" field to the value that was provided on create.
func (u *AclEntryUpsertOne) UpdatePermissions() *AclEntryUpsertOne {
return u.Update(func(s *AclEntryUpsert) {
s.UpdatePermissions()
})
}
// Exec executes the query.
func (u *AclEntryUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for AclEntryCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *AclEntryUpsertOne) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}
// Exec executes the UPSERT query and returns the inserted/updated ID.
func (u *AclEntryUpsertOne) ID(ctx context.Context) (id int, err error) {
node, err := u.create.Save(ctx)
if err != nil {
return id, err
}
return node.ID, nil
}
// IDX is like ID, but panics if an error occurs.
func (u *AclEntryUpsertOne) IDX(ctx context.Context) int {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
func (m *AclEntryCreate) SetRawID(t int) *AclEntryCreate {
m.mutation.SetRawID(t)
return m
}
// AclEntryCreateBulk is the builder for creating many AclEntry entities in bulk.
type AclEntryCreateBulk struct {
config
err error
builders []*AclEntryCreate
conflict []sql.ConflictOption
}
// Save creates the AclEntry entities in the database.
func (aecb *AclEntryCreateBulk) Save(ctx context.Context) ([]*AclEntry, error) {
if aecb.err != nil {
return nil, aecb.err
}
specs := make([]*sqlgraph.CreateSpec, len(aecb.builders))
nodes := make([]*AclEntry, len(aecb.builders))
mutators := make([]Mutator, len(aecb.builders))
for i := range aecb.builders {
func(i int, root context.Context) {
builder := aecb.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*AclEntryMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, aecb.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
spec.OnConflict = aecb.conflict
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, aecb.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
if specs[i].ID.Value != nil {
id := specs[i].ID.Value.(int64)
nodes[i].ID = int(id)
}
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, aecb.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (aecb *AclEntryCreateBulk) SaveX(ctx context.Context) []*AclEntry {
v, err := aecb.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (aecb *AclEntryCreateBulk) Exec(ctx context.Context) error {
_, err := aecb.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (aecb *AclEntryCreateBulk) ExecX(ctx context.Context) {
if err := aecb.Exec(ctx); err != nil {
panic(err)
}
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.AclEntry.CreateBulk(builders...).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.AclEntryUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (aecb *AclEntryCreateBulk) OnConflict(opts ...sql.ConflictOption) *AclEntryUpsertBulk {
aecb.conflict = opts
return &AclEntryUpsertBulk{
create: aecb,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.AclEntry.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (aecb *AclEntryCreateBulk) OnConflictColumns(columns ...string) *AclEntryUpsertBulk {
aecb.conflict = append(aecb.conflict, sql.ConflictColumns(columns...))
return &AclEntryUpsertBulk{
create: aecb,
}
}
// AclEntryUpsertBulk is the builder for "upsert"-ing
// a bulk of AclEntry nodes.
type AclEntryUpsertBulk struct {
create *AclEntryCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.AclEntry.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *AclEntryUpsertBulk) UpdateNewValues() *AclEntryUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
for _, b := range u.create.builders {
if _, exists := b.mutation.CreatedAt(); exists {
s.SetIgnore(aclentry.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.AclEntry.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *AclEntryUpsertBulk) Ignore() *AclEntryUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *AclEntryUpsertBulk) DoNothing() *AclEntryUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the AclEntryCreateBulk.OnConflict
// documentation for more info.
func (u *AclEntryUpsertBulk) Update(set func(*AclEntryUpsert)) *AclEntryUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&AclEntryUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *AclEntryUpsertBulk) SetUpdatedAt(v time.Time) *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *AclEntryUpsertBulk) UpdateUpdatedAt() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateUpdatedAt()
})
}
// SetDeletedAt sets the "deleted_at" field.
func (u *AclEntryUpsertBulk) SetDeletedAt(v time.Time) *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.SetDeletedAt(v)
})
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *AclEntryUpsertBulk) UpdateDeletedAt() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateDeletedAt()
})
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *AclEntryUpsertBulk) ClearDeletedAt() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.ClearDeletedAt()
})
}
// SetFileID sets the "file_id" field.
func (u *AclEntryUpsertBulk) SetFileID(v int) *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.SetFileID(v)
})
}
// UpdateFileID sets the "file_id" field to the value that was provided on create.
func (u *AclEntryUpsertBulk) UpdateFileID() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateFileID()
})
}
// SetSubjectType sets the "subject_type" field.
func (u *AclEntryUpsertBulk) SetSubjectType(v aclentry.SubjectType) *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.SetSubjectType(v)
})
}
// UpdateSubjectType sets the "subject_type" field to the value that was provided on create.
func (u *AclEntryUpsertBulk) UpdateSubjectType() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateSubjectType()
})
}
// SetSubjectID sets the "subject_id" field.
func (u *AclEntryUpsertBulk) SetSubjectID(v int) *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.SetSubjectID(v)
})
}
// AddSubjectID adds v to the "subject_id" field.
func (u *AclEntryUpsertBulk) AddSubjectID(v int) *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.AddSubjectID(v)
})
}
// UpdateSubjectID sets the "subject_id" field to the value that was provided on create.
func (u *AclEntryUpsertBulk) UpdateSubjectID() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.UpdateSubjectID()
})
}
// ClearSubjectID clears the value of the "subject_id" field.
func (u *AclEntryUpsertBulk) ClearSubjectID() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.ClearSubjectID()
})
}
// SetPermissions sets the "permissions" field.
func (u *AclEntryUpsertBulk) SetPermissions(v *boolset.BooleanSet) *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.SetPermissions(v)
})
}
// UpdatePermissions sets the "permissions" field to the value that was provided on create.
func (u *AclEntryUpsertBulk) UpdatePermissions() *AclEntryUpsertBulk {
return u.Update(func(s *AclEntryUpsert) {
s.UpdatePermissions()
})
}
// Exec executes the query.
func (u *AclEntryUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {
return u.create.err
}
for i, b := range u.create.builders {
if len(b.conflict) != 0 {
return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the AclEntryCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for AclEntryCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *AclEntryUpsertBulk) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}

@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
)
// AclEntryDelete is the builder for deleting a AclEntry entity.
type AclEntryDelete struct {
config
hooks []Hook
mutation *AclEntryMutation
}
// Where appends a list predicates to the AclEntryDelete builder.
func (aed *AclEntryDelete) Where(ps ...predicate.AclEntry) *AclEntryDelete {
aed.mutation.Where(ps...)
return aed
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (aed *AclEntryDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, aed.sqlExec, aed.mutation, aed.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (aed *AclEntryDelete) ExecX(ctx context.Context) int {
n, err := aed.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (aed *AclEntryDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(aclentry.Table, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt))
if ps := aed.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, aed.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
aed.mutation.done = true
return affected, err
}
// AclEntryDeleteOne is the builder for deleting a single AclEntry entity.
type AclEntryDeleteOne struct {
aed *AclEntryDelete
}
// Where appends a list predicates to the AclEntryDelete builder.
func (aedo *AclEntryDeleteOne) Where(ps ...predicate.AclEntry) *AclEntryDeleteOne {
aedo.aed.mutation.Where(ps...)
return aedo
}
// Exec executes the deletion query.
func (aedo *AclEntryDeleteOne) Exec(ctx context.Context) error {
n, err := aedo.aed.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{aclentry.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (aedo *AclEntryDeleteOne) ExecX(ctx context.Context) {
if err := aedo.Exec(ctx); err != nil {
panic(err)
}
}

@ -0,0 +1,605 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
)
// AclEntryQuery is the builder for querying AclEntry entities.
type AclEntryQuery struct {
config
ctx *QueryContext
order []aclentry.OrderOption
inters []Interceptor
predicates []predicate.AclEntry
withFile *FileQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the AclEntryQuery builder.
func (aeq *AclEntryQuery) Where(ps ...predicate.AclEntry) *AclEntryQuery {
aeq.predicates = append(aeq.predicates, ps...)
return aeq
}
// Limit the number of records to be returned by this query.
func (aeq *AclEntryQuery) Limit(limit int) *AclEntryQuery {
aeq.ctx.Limit = &limit
return aeq
}
// Offset to start from.
func (aeq *AclEntryQuery) Offset(offset int) *AclEntryQuery {
aeq.ctx.Offset = &offset
return aeq
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (aeq *AclEntryQuery) Unique(unique bool) *AclEntryQuery {
aeq.ctx.Unique = &unique
return aeq
}
// Order specifies how the records should be ordered.
func (aeq *AclEntryQuery) Order(o ...aclentry.OrderOption) *AclEntryQuery {
aeq.order = append(aeq.order, o...)
return aeq
}
// QueryFile chains the current query on the "file" edge.
func (aeq *AclEntryQuery) QueryFile() *FileQuery {
query := (&FileClient{config: aeq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := aeq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := aeq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(aclentry.Table, aclentry.FieldID, selector),
sqlgraph.To(file.Table, file.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, aclentry.FileTable, aclentry.FileColumn),
)
fromU = sqlgraph.SetNeighbors(aeq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first AclEntry entity from the query.
// Returns a *NotFoundError when no AclEntry was found.
func (aeq *AclEntryQuery) First(ctx context.Context) (*AclEntry, error) {
nodes, err := aeq.Limit(1).All(setContextOp(ctx, aeq.ctx, "First"))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{aclentry.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (aeq *AclEntryQuery) FirstX(ctx context.Context) *AclEntry {
node, err := aeq.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first AclEntry ID from the query.
// Returns a *NotFoundError when no AclEntry ID was found.
func (aeq *AclEntryQuery) FirstID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = aeq.Limit(1).IDs(setContextOp(ctx, aeq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{aclentry.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (aeq *AclEntryQuery) FirstIDX(ctx context.Context) int {
id, err := aeq.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single AclEntry entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one AclEntry entity is found.
// Returns a *NotFoundError when no AclEntry entities are found.
func (aeq *AclEntryQuery) Only(ctx context.Context) (*AclEntry, error) {
nodes, err := aeq.Limit(2).All(setContextOp(ctx, aeq.ctx, "Only"))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{aclentry.Label}
default:
return nil, &NotSingularError{aclentry.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (aeq *AclEntryQuery) OnlyX(ctx context.Context) *AclEntry {
node, err := aeq.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only AclEntry ID in the query.
// Returns a *NotSingularError when more than one AclEntry ID is found.
// Returns a *NotFoundError when no entities are found.
func (aeq *AclEntryQuery) OnlyID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = aeq.Limit(2).IDs(setContextOp(ctx, aeq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{aclentry.Label}
default:
err = &NotSingularError{aclentry.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (aeq *AclEntryQuery) OnlyIDX(ctx context.Context) int {
id, err := aeq.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of AclEntries.
func (aeq *AclEntryQuery) All(ctx context.Context) ([]*AclEntry, error) {
ctx = setContextOp(ctx, aeq.ctx, "All")
if err := aeq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*AclEntry, *AclEntryQuery]()
return withInterceptors[[]*AclEntry](ctx, aeq, qr, aeq.inters)
}
// AllX is like All, but panics if an error occurs.
func (aeq *AclEntryQuery) AllX(ctx context.Context) []*AclEntry {
nodes, err := aeq.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of AclEntry IDs.
func (aeq *AclEntryQuery) IDs(ctx context.Context) (ids []int, err error) {
if aeq.ctx.Unique == nil && aeq.path != nil {
aeq.Unique(true)
}
ctx = setContextOp(ctx, aeq.ctx, "IDs")
if err = aeq.Select(aclentry.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (aeq *AclEntryQuery) IDsX(ctx context.Context) []int {
ids, err := aeq.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (aeq *AclEntryQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, aeq.ctx, "Count")
if err := aeq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, aeq, querierCount[*AclEntryQuery](), aeq.inters)
}
// CountX is like Count, but panics if an error occurs.
func (aeq *AclEntryQuery) CountX(ctx context.Context) int {
count, err := aeq.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (aeq *AclEntryQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, aeq.ctx, "Exist")
switch _, err := aeq.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (aeq *AclEntryQuery) ExistX(ctx context.Context) bool {
exist, err := aeq.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the AclEntryQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (aeq *AclEntryQuery) Clone() *AclEntryQuery {
if aeq == nil {
return nil
}
return &AclEntryQuery{
config: aeq.config,
ctx: aeq.ctx.Clone(),
order: append([]aclentry.OrderOption{}, aeq.order...),
inters: append([]Interceptor{}, aeq.inters...),
predicates: append([]predicate.AclEntry{}, aeq.predicates...),
withFile: aeq.withFile.Clone(),
// clone intermediate query.
sql: aeq.sql.Clone(),
path: aeq.path,
}
}
// WithFile tells the query-builder to eager-load the nodes that are connected to
// the "file" edge. The optional arguments are used to configure the query builder of the edge.
func (aeq *AclEntryQuery) WithFile(opts ...func(*FileQuery)) *AclEntryQuery {
query := (&FileClient{config: aeq.config}).Query()
for _, opt := range opts {
opt(query)
}
aeq.withFile = query
return aeq
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.AclEntry.Query().
// GroupBy(aclentry.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (aeq *AclEntryQuery) GroupBy(field string, fields ...string) *AclEntryGroupBy {
aeq.ctx.Fields = append([]string{field}, fields...)
grbuild := &AclEntryGroupBy{build: aeq}
grbuild.flds = &aeq.ctx.Fields
grbuild.label = aclentry.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// CreatedAt time.Time `json:"created_at,omitempty"`
// }
//
// client.AclEntry.Query().
// Select(aclentry.FieldCreatedAt).
// Scan(ctx, &v)
func (aeq *AclEntryQuery) Select(fields ...string) *AclEntrySelect {
aeq.ctx.Fields = append(aeq.ctx.Fields, fields...)
sbuild := &AclEntrySelect{AclEntryQuery: aeq}
sbuild.label = aclentry.Label
sbuild.flds, sbuild.scan = &aeq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AclEntrySelect configured with the given aggregations.
func (aeq *AclEntryQuery) Aggregate(fns ...AggregateFunc) *AclEntrySelect {
return aeq.Select().Aggregate(fns...)
}
func (aeq *AclEntryQuery) prepareQuery(ctx context.Context) error {
for _, inter := range aeq.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, aeq); err != nil {
return err
}
}
}
for _, f := range aeq.ctx.Fields {
if !aclentry.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if aeq.path != nil {
prev, err := aeq.path(ctx)
if err != nil {
return err
}
aeq.sql = prev
}
return nil
}
func (aeq *AclEntryQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AclEntry, error) {
var (
nodes = []*AclEntry{}
_spec = aeq.querySpec()
loadedTypes = [1]bool{
aeq.withFile != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*AclEntry).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &AclEntry{config: aeq.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, aeq.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := aeq.withFile; query != nil {
if err := aeq.loadFile(ctx, query, nodes, nil,
func(n *AclEntry, e *File) { n.Edges.File = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (aeq *AclEntryQuery) loadFile(ctx context.Context, query *FileQuery, nodes []*AclEntry, init func(*AclEntry), assign func(*AclEntry, *File)) error {
ids := make([]int, 0, len(nodes))
nodeids := make(map[int][]*AclEntry)
for i := range nodes {
fk := nodes[i].FileID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(file.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "file_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (aeq *AclEntryQuery) sqlCount(ctx context.Context) (int, error) {
_spec := aeq.querySpec()
_spec.Node.Columns = aeq.ctx.Fields
if len(aeq.ctx.Fields) > 0 {
_spec.Unique = aeq.ctx.Unique != nil && *aeq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, aeq.driver, _spec)
}
func (aeq *AclEntryQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(aclentry.Table, aclentry.Columns, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt))
_spec.From = aeq.sql
if unique := aeq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if aeq.path != nil {
_spec.Unique = true
}
if fields := aeq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, aclentry.FieldID)
for i := range fields {
if fields[i] != aclentry.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if aeq.withFile != nil {
_spec.Node.AddColumnOnce(aclentry.FieldFileID)
}
}
if ps := aeq.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := aeq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := aeq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := aeq.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (aeq *AclEntryQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(aeq.driver.Dialect())
t1 := builder.Table(aclentry.Table)
columns := aeq.ctx.Fields
if len(columns) == 0 {
columns = aclentry.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if aeq.sql != nil {
selector = aeq.sql
selector.Select(selector.Columns(columns...)...)
}
if aeq.ctx.Unique != nil && *aeq.ctx.Unique {
selector.Distinct()
}
for _, p := range aeq.predicates {
p(selector)
}
for _, p := range aeq.order {
p(selector)
}
if offset := aeq.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := aeq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// AclEntryGroupBy is the group-by builder for AclEntry entities.
type AclEntryGroupBy struct {
selector
build *AclEntryQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (aegb *AclEntryGroupBy) Aggregate(fns ...AggregateFunc) *AclEntryGroupBy {
aegb.fns = append(aegb.fns, fns...)
return aegb
}
// Scan applies the selector query and scans the result into the given value.
func (aegb *AclEntryGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, aegb.build.ctx, "GroupBy")
if err := aegb.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*AclEntryQuery, *AclEntryGroupBy](ctx, aegb.build, aegb, aegb.build.inters, v)
}
func (aegb *AclEntryGroupBy) sqlScan(ctx context.Context, root *AclEntryQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(aegb.fns))
for _, fn := range aegb.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*aegb.flds)+len(aegb.fns))
for _, f := range *aegb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*aegb.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := aegb.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// AclEntrySelect is the builder for selecting fields of AclEntry entities.
type AclEntrySelect struct {
*AclEntryQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (aes *AclEntrySelect) Aggregate(fns ...AggregateFunc) *AclEntrySelect {
aes.fns = append(aes.fns, fns...)
return aes
}
// Scan applies the selector query and scans the result into the given value.
func (aes *AclEntrySelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, aes.ctx, "Select")
if err := aes.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*AclEntryQuery, *AclEntrySelect](ctx, aes.AclEntryQuery, aes, aes.inters, v)
}
func (aes *AclEntrySelect) sqlScan(ctx context.Context, root *AclEntryQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(aes.fns))
for _, fn := range aes.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*aes.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := aes.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

@ -0,0 +1,542 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/file"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
)
// AclEntryUpdate is the builder for updating AclEntry entities.
type AclEntryUpdate struct {
config
hooks []Hook
mutation *AclEntryMutation
}
// Where appends a list predicates to the AclEntryUpdate builder.
func (aeu *AclEntryUpdate) Where(ps ...predicate.AclEntry) *AclEntryUpdate {
aeu.mutation.Where(ps...)
return aeu
}
// SetUpdatedAt sets the "updated_at" field.
func (aeu *AclEntryUpdate) SetUpdatedAt(t time.Time) *AclEntryUpdate {
aeu.mutation.SetUpdatedAt(t)
return aeu
}
// SetDeletedAt sets the "deleted_at" field.
func (aeu *AclEntryUpdate) SetDeletedAt(t time.Time) *AclEntryUpdate {
aeu.mutation.SetDeletedAt(t)
return aeu
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (aeu *AclEntryUpdate) SetNillableDeletedAt(t *time.Time) *AclEntryUpdate {
if t != nil {
aeu.SetDeletedAt(*t)
}
return aeu
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (aeu *AclEntryUpdate) ClearDeletedAt() *AclEntryUpdate {
aeu.mutation.ClearDeletedAt()
return aeu
}
// SetFileID sets the "file_id" field.
func (aeu *AclEntryUpdate) SetFileID(i int) *AclEntryUpdate {
aeu.mutation.SetFileID(i)
return aeu
}
// SetNillableFileID sets the "file_id" field if the given value is not nil.
func (aeu *AclEntryUpdate) SetNillableFileID(i *int) *AclEntryUpdate {
if i != nil {
aeu.SetFileID(*i)
}
return aeu
}
// SetSubjectType sets the "subject_type" field.
func (aeu *AclEntryUpdate) SetSubjectType(at aclentry.SubjectType) *AclEntryUpdate {
aeu.mutation.SetSubjectType(at)
return aeu
}
// SetNillableSubjectType sets the "subject_type" field if the given value is not nil.
func (aeu *AclEntryUpdate) SetNillableSubjectType(at *aclentry.SubjectType) *AclEntryUpdate {
if at != nil {
aeu.SetSubjectType(*at)
}
return aeu
}
// SetSubjectID sets the "subject_id" field.
func (aeu *AclEntryUpdate) SetSubjectID(i int) *AclEntryUpdate {
aeu.mutation.ResetSubjectID()
aeu.mutation.SetSubjectID(i)
return aeu
}
// SetNillableSubjectID sets the "subject_id" field if the given value is not nil.
func (aeu *AclEntryUpdate) SetNillableSubjectID(i *int) *AclEntryUpdate {
if i != nil {
aeu.SetSubjectID(*i)
}
return aeu
}
// AddSubjectID adds i to the "subject_id" field.
func (aeu *AclEntryUpdate) AddSubjectID(i int) *AclEntryUpdate {
aeu.mutation.AddSubjectID(i)
return aeu
}
// ClearSubjectID clears the value of the "subject_id" field.
func (aeu *AclEntryUpdate) ClearSubjectID() *AclEntryUpdate {
aeu.mutation.ClearSubjectID()
return aeu
}
// SetPermissions sets the "permissions" field.
func (aeu *AclEntryUpdate) SetPermissions(bs *boolset.BooleanSet) *AclEntryUpdate {
aeu.mutation.SetPermissions(bs)
return aeu
}
// SetFile sets the "file" edge to the File entity.
func (aeu *AclEntryUpdate) SetFile(f *File) *AclEntryUpdate {
return aeu.SetFileID(f.ID)
}
// Mutation returns the AclEntryMutation object of the builder.
func (aeu *AclEntryUpdate) Mutation() *AclEntryMutation {
return aeu.mutation
}
// ClearFile clears the "file" edge to the File entity.
func (aeu *AclEntryUpdate) ClearFile() *AclEntryUpdate {
aeu.mutation.ClearFile()
return aeu
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (aeu *AclEntryUpdate) Save(ctx context.Context) (int, error) {
if err := aeu.defaults(); err != nil {
return 0, err
}
return withHooks(ctx, aeu.sqlSave, aeu.mutation, aeu.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (aeu *AclEntryUpdate) SaveX(ctx context.Context) int {
affected, err := aeu.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (aeu *AclEntryUpdate) Exec(ctx context.Context) error {
_, err := aeu.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (aeu *AclEntryUpdate) ExecX(ctx context.Context) {
if err := aeu.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (aeu *AclEntryUpdate) defaults() error {
if _, ok := aeu.mutation.UpdatedAt(); !ok {
if aclentry.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized aclentry.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := aclentry.UpdateDefaultUpdatedAt()
aeu.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (aeu *AclEntryUpdate) check() error {
if v, ok := aeu.mutation.SubjectType(); ok {
if err := aclentry.SubjectTypeValidator(v); err != nil {
return &ValidationError{Name: "subject_type", err: fmt.Errorf(`ent: validator failed for field "AclEntry.subject_type": %w`, err)}
}
}
if _, ok := aeu.mutation.FileID(); aeu.mutation.FileCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "AclEntry.file"`)
}
return nil
}
func (aeu *AclEntryUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := aeu.check(); err != nil {
return n, err
}
_spec := sqlgraph.NewUpdateSpec(aclentry.Table, aclentry.Columns, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt))
if ps := aeu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := aeu.mutation.UpdatedAt(); ok {
_spec.SetField(aclentry.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := aeu.mutation.DeletedAt(); ok {
_spec.SetField(aclentry.FieldDeletedAt, field.TypeTime, value)
}
if aeu.mutation.DeletedAtCleared() {
_spec.ClearField(aclentry.FieldDeletedAt, field.TypeTime)
}
if value, ok := aeu.mutation.SubjectType(); ok {
_spec.SetField(aclentry.FieldSubjectType, field.TypeEnum, value)
}
if value, ok := aeu.mutation.SubjectID(); ok {
_spec.SetField(aclentry.FieldSubjectID, field.TypeInt, value)
}
if value, ok := aeu.mutation.AddedSubjectID(); ok {
_spec.AddField(aclentry.FieldSubjectID, field.TypeInt, value)
}
if aeu.mutation.SubjectIDCleared() {
_spec.ClearField(aclentry.FieldSubjectID, field.TypeInt)
}
if value, ok := aeu.mutation.Permissions(); ok {
_spec.SetField(aclentry.FieldPermissions, field.TypeBytes, value)
}
if aeu.mutation.FileCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: aclentry.FileTable,
Columns: []string{aclentry.FileColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := aeu.mutation.FileIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: aclentry.FileTable,
Columns: []string{aclentry.FileColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if n, err = sqlgraph.UpdateNodes(ctx, aeu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{aclentry.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
aeu.mutation.done = true
return n, nil
}
// AclEntryUpdateOne is the builder for updating a single AclEntry entity.
type AclEntryUpdateOne struct {
config
fields []string
hooks []Hook
mutation *AclEntryMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (aeuo *AclEntryUpdateOne) SetUpdatedAt(t time.Time) *AclEntryUpdateOne {
aeuo.mutation.SetUpdatedAt(t)
return aeuo
}
// SetDeletedAt sets the "deleted_at" field.
func (aeuo *AclEntryUpdateOne) SetDeletedAt(t time.Time) *AclEntryUpdateOne {
aeuo.mutation.SetDeletedAt(t)
return aeuo
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (aeuo *AclEntryUpdateOne) SetNillableDeletedAt(t *time.Time) *AclEntryUpdateOne {
if t != nil {
aeuo.SetDeletedAt(*t)
}
return aeuo
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (aeuo *AclEntryUpdateOne) ClearDeletedAt() *AclEntryUpdateOne {
aeuo.mutation.ClearDeletedAt()
return aeuo
}
// SetFileID sets the "file_id" field.
func (aeuo *AclEntryUpdateOne) SetFileID(i int) *AclEntryUpdateOne {
aeuo.mutation.SetFileID(i)
return aeuo
}
// SetNillableFileID sets the "file_id" field if the given value is not nil.
func (aeuo *AclEntryUpdateOne) SetNillableFileID(i *int) *AclEntryUpdateOne {
if i != nil {
aeuo.SetFileID(*i)
}
return aeuo
}
// SetSubjectType sets the "subject_type" field.
func (aeuo *AclEntryUpdateOne) SetSubjectType(at aclentry.SubjectType) *AclEntryUpdateOne {
aeuo.mutation.SetSubjectType(at)
return aeuo
}
// SetNillableSubjectType sets the "subject_type" field if the given value is not nil.
func (aeuo *AclEntryUpdateOne) SetNillableSubjectType(at *aclentry.SubjectType) *AclEntryUpdateOne {
if at != nil {
aeuo.SetSubjectType(*at)
}
return aeuo
}
// SetSubjectID sets the "subject_id" field.
func (aeuo *AclEntryUpdateOne) SetSubjectID(i int) *AclEntryUpdateOne {
aeuo.mutation.ResetSubjectID()
aeuo.mutation.SetSubjectID(i)
return aeuo
}
// SetNillableSubjectID sets the "subject_id" field if the given value is not nil.
func (aeuo *AclEntryUpdateOne) SetNillableSubjectID(i *int) *AclEntryUpdateOne {
if i != nil {
aeuo.SetSubjectID(*i)
}
return aeuo
}
// AddSubjectID adds i to the "subject_id" field.
func (aeuo *AclEntryUpdateOne) AddSubjectID(i int) *AclEntryUpdateOne {
aeuo.mutation.AddSubjectID(i)
return aeuo
}
// ClearSubjectID clears the value of the "subject_id" field.
func (aeuo *AclEntryUpdateOne) ClearSubjectID() *AclEntryUpdateOne {
aeuo.mutation.ClearSubjectID()
return aeuo
}
// SetPermissions sets the "permissions" field.
func (aeuo *AclEntryUpdateOne) SetPermissions(bs *boolset.BooleanSet) *AclEntryUpdateOne {
aeuo.mutation.SetPermissions(bs)
return aeuo
}
// SetFile sets the "file" edge to the File entity.
func (aeuo *AclEntryUpdateOne) SetFile(f *File) *AclEntryUpdateOne {
return aeuo.SetFileID(f.ID)
}
// Mutation returns the AclEntryMutation object of the builder.
func (aeuo *AclEntryUpdateOne) Mutation() *AclEntryMutation {
return aeuo.mutation
}
// ClearFile clears the "file" edge to the File entity.
func (aeuo *AclEntryUpdateOne) ClearFile() *AclEntryUpdateOne {
aeuo.mutation.ClearFile()
return aeuo
}
// Where appends a list predicates to the AclEntryUpdate builder.
func (aeuo *AclEntryUpdateOne) Where(ps ...predicate.AclEntry) *AclEntryUpdateOne {
aeuo.mutation.Where(ps...)
return aeuo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (aeuo *AclEntryUpdateOne) Select(field string, fields ...string) *AclEntryUpdateOne {
aeuo.fields = append([]string{field}, fields...)
return aeuo
}
// Save executes the query and returns the updated AclEntry entity.
func (aeuo *AclEntryUpdateOne) Save(ctx context.Context) (*AclEntry, error) {
if err := aeuo.defaults(); err != nil {
return nil, err
}
return withHooks(ctx, aeuo.sqlSave, aeuo.mutation, aeuo.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (aeuo *AclEntryUpdateOne) SaveX(ctx context.Context) *AclEntry {
node, err := aeuo.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (aeuo *AclEntryUpdateOne) Exec(ctx context.Context) error {
_, err := aeuo.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (aeuo *AclEntryUpdateOne) ExecX(ctx context.Context) {
if err := aeuo.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (aeuo *AclEntryUpdateOne) defaults() error {
if _, ok := aeuo.mutation.UpdatedAt(); !ok {
if aclentry.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized aclentry.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := aclentry.UpdateDefaultUpdatedAt()
aeuo.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (aeuo *AclEntryUpdateOne) check() error {
if v, ok := aeuo.mutation.SubjectType(); ok {
if err := aclentry.SubjectTypeValidator(v); err != nil {
return &ValidationError{Name: "subject_type", err: fmt.Errorf(`ent: validator failed for field "AclEntry.subject_type": %w`, err)}
}
}
if _, ok := aeuo.mutation.FileID(); aeuo.mutation.FileCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "AclEntry.file"`)
}
return nil
}
func (aeuo *AclEntryUpdateOne) sqlSave(ctx context.Context) (_node *AclEntry, err error) {
if err := aeuo.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(aclentry.Table, aclentry.Columns, sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt))
id, ok := aeuo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AclEntry.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := aeuo.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, aclentry.FieldID)
for _, f := range fields {
if !aclentry.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != aclentry.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := aeuo.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := aeuo.mutation.UpdatedAt(); ok {
_spec.SetField(aclentry.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := aeuo.mutation.DeletedAt(); ok {
_spec.SetField(aclentry.FieldDeletedAt, field.TypeTime, value)
}
if aeuo.mutation.DeletedAtCleared() {
_spec.ClearField(aclentry.FieldDeletedAt, field.TypeTime)
}
if value, ok := aeuo.mutation.SubjectType(); ok {
_spec.SetField(aclentry.FieldSubjectType, field.TypeEnum, value)
}
if value, ok := aeuo.mutation.SubjectID(); ok {
_spec.SetField(aclentry.FieldSubjectID, field.TypeInt, value)
}
if value, ok := aeuo.mutation.AddedSubjectID(); ok {
_spec.AddField(aclentry.FieldSubjectID, field.TypeInt, value)
}
if aeuo.mutation.SubjectIDCleared() {
_spec.ClearField(aclentry.FieldSubjectID, field.TypeInt)
}
if value, ok := aeuo.mutation.Permissions(); ok {
_spec.SetField(aclentry.FieldPermissions, field.TypeBytes, value)
}
if aeuo.mutation.FileCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: aclentry.FileTable,
Columns: []string{aclentry.FileColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := aeuo.mutation.FileIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: aclentry.FileTable,
Columns: []string{aclentry.FileColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(file.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &AclEntry{config: aeuo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, aeuo.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{aclentry.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
aeuo.mutation.done = true
return _node, nil
}

@ -15,6 +15,7 @@ import (
"entgo.io/ent/dialect" "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/davaccount"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
@ -41,6 +42,8 @@ type Client struct {
config config
// Schema is the client for creating, migrating and dropping schema. // Schema is the client for creating, migrating and dropping schema.
Schema *migrate.Schema Schema *migrate.Schema
// AclEntry is the client for interacting with the AclEntry builders.
AclEntry *AclEntryClient
// DavAccount is the client for interacting with the DavAccount builders. // DavAccount is the client for interacting with the DavAccount builders.
DavAccount *DavAccountClient DavAccount *DavAccountClient
// DirectLink is the client for interacting with the DirectLink builders. // DirectLink is the client for interacting with the DirectLink builders.
@ -86,6 +89,7 @@ func NewClient(opts ...Option) *Client {
func (c *Client) init() { func (c *Client) init() {
c.Schema = migrate.NewSchema(c.driver) c.Schema = migrate.NewSchema(c.driver)
c.AclEntry = NewAclEntryClient(c.config)
c.DavAccount = NewDavAccountClient(c.config) c.DavAccount = NewDavAccountClient(c.config)
c.DirectLink = NewDirectLinkClient(c.config) c.DirectLink = NewDirectLinkClient(c.config)
c.Entity = NewEntityClient(c.config) c.Entity = NewEntityClient(c.config)
@ -195,6 +199,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
return &Tx{ return &Tx{
ctx: ctx, ctx: ctx,
config: cfg, config: cfg,
AclEntry: NewAclEntryClient(cfg),
DavAccount: NewDavAccountClient(cfg), DavAccount: NewDavAccountClient(cfg),
DirectLink: NewDirectLinkClient(cfg), DirectLink: NewDirectLinkClient(cfg),
Entity: NewEntityClient(cfg), Entity: NewEntityClient(cfg),
@ -231,6 +236,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
return &Tx{ return &Tx{
ctx: ctx, ctx: ctx,
config: cfg, config: cfg,
AclEntry: NewAclEntryClient(cfg),
DavAccount: NewDavAccountClient(cfg), DavAccount: NewDavAccountClient(cfg),
DirectLink: NewDirectLinkClient(cfg), DirectLink: NewDirectLinkClient(cfg),
Entity: NewEntityClient(cfg), Entity: NewEntityClient(cfg),
@ -254,7 +260,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
// Debug returns a new debug-client. It's used to get verbose logging on specific operations. // Debug returns a new debug-client. It's used to get verbose logging on specific operations.
// //
// client.Debug(). // client.Debug().
// DavAccount. // AclEntry.
// Query(). // Query().
// Count(ctx) // Count(ctx)
func (c *Client) Debug() *Client { func (c *Client) Debug() *Client {
@ -277,7 +283,7 @@ func (c *Client) Close() error {
// In order to add hooks to a specific client, call: `client.Node.Use(...)`. // In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) { func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{ for _, n := range []interface{ Use(...Hook) }{
c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group, c.AclEntry, c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group,
c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey, c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey,
c.Setting, c.Share, c.StoragePolicy, c.Task, c.User, c.Setting, c.Share, c.StoragePolicy, c.Task, c.User,
} { } {
@ -289,7 +295,7 @@ func (c *Client) Use(hooks ...Hook) {
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) { func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{ for _, n := range []interface{ Intercept(...Interceptor) }{
c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group, c.AclEntry, c.DavAccount, c.DirectLink, c.Entity, c.File, c.FsEvent, c.Group,
c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey, c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey,
c.Setting, c.Share, c.StoragePolicy, c.Task, c.User, c.Setting, c.Share, c.StoragePolicy, c.Task, c.User,
} { } {
@ -300,6 +306,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
// Mutate implements the ent.Mutator interface. // Mutate implements the ent.Mutator interface.
func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
switch m := m.(type) { switch m := m.(type) {
case *AclEntryMutation:
return c.AclEntry.mutate(ctx, m)
case *DavAccountMutation: case *DavAccountMutation:
return c.DavAccount.mutate(ctx, m) return c.DavAccount.mutate(ctx, m)
case *DirectLinkMutation: case *DirectLinkMutation:
@ -339,6 +347,157 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
} }
} }
// AclEntryClient is a client for the AclEntry schema.
type AclEntryClient struct {
config
}
// NewAclEntryClient returns a client for the AclEntry from the given config.
func NewAclEntryClient(c config) *AclEntryClient {
return &AclEntryClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `aclentry.Hooks(f(g(h())))`.
func (c *AclEntryClient) Use(hooks ...Hook) {
c.hooks.AclEntry = append(c.hooks.AclEntry, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `aclentry.Intercept(f(g(h())))`.
func (c *AclEntryClient) Intercept(interceptors ...Interceptor) {
c.inters.AclEntry = append(c.inters.AclEntry, interceptors...)
}
// Create returns a builder for creating a AclEntry entity.
func (c *AclEntryClient) Create() *AclEntryCreate {
mutation := newAclEntryMutation(c.config, OpCreate)
return &AclEntryCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of AclEntry entities.
func (c *AclEntryClient) CreateBulk(builders ...*AclEntryCreate) *AclEntryCreateBulk {
return &AclEntryCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *AclEntryClient) MapCreateBulk(slice any, setFunc func(*AclEntryCreate, int)) *AclEntryCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &AclEntryCreateBulk{err: fmt.Errorf("calling to AclEntryClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*AclEntryCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &AclEntryCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for AclEntry.
func (c *AclEntryClient) Update() *AclEntryUpdate {
mutation := newAclEntryMutation(c.config, OpUpdate)
return &AclEntryUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *AclEntryClient) UpdateOne(ae *AclEntry) *AclEntryUpdateOne {
mutation := newAclEntryMutation(c.config, OpUpdateOne, withAclEntry(ae))
return &AclEntryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *AclEntryClient) UpdateOneID(id int) *AclEntryUpdateOne {
mutation := newAclEntryMutation(c.config, OpUpdateOne, withAclEntryID(id))
return &AclEntryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for AclEntry.
func (c *AclEntryClient) Delete() *AclEntryDelete {
mutation := newAclEntryMutation(c.config, OpDelete)
return &AclEntryDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *AclEntryClient) DeleteOne(ae *AclEntry) *AclEntryDeleteOne {
return c.DeleteOneID(ae.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *AclEntryClient) DeleteOneID(id int) *AclEntryDeleteOne {
builder := c.Delete().Where(aclentry.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &AclEntryDeleteOne{builder}
}
// Query returns a query builder for AclEntry.
func (c *AclEntryClient) Query() *AclEntryQuery {
return &AclEntryQuery{
config: c.config,
ctx: &QueryContext{Type: TypeAclEntry},
inters: c.Interceptors(),
}
}
// Get returns a AclEntry entity by its id.
func (c *AclEntryClient) Get(ctx context.Context, id int) (*AclEntry, error) {
return c.Query().Where(aclentry.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *AclEntryClient) GetX(ctx context.Context, id int) *AclEntry {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryFile queries the file edge of a AclEntry.
func (c *AclEntryClient) QueryFile(ae *AclEntry) *FileQuery {
query := (&FileClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := ae.ID
step := sqlgraph.NewStep(
sqlgraph.From(aclentry.Table, aclentry.FieldID, id),
sqlgraph.To(file.Table, file.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, aclentry.FileTable, aclentry.FileColumn),
)
fromV = sqlgraph.Neighbors(ae.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *AclEntryClient) Hooks() []Hook {
hooks := c.hooks.AclEntry
return append(hooks[:len(hooks):len(hooks)], aclentry.Hooks[:]...)
}
// Interceptors returns the client interceptors.
func (c *AclEntryClient) Interceptors() []Interceptor {
inters := c.inters.AclEntry
return append(inters[:len(inters):len(inters)], aclentry.Interceptors[:]...)
}
func (c *AclEntryClient) mutate(ctx context.Context, m *AclEntryMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&AclEntryCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&AclEntryUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&AclEntryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&AclEntryDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown AclEntry mutation op: %q", m.Op())
}
}
// DavAccountClient is a client for the DavAccount schema. // DavAccountClient is a client for the DavAccount schema.
type DavAccountClient struct { type DavAccountClient struct {
config config
@ -1044,6 +1203,22 @@ func (c *FileClient) QueryShares(f *File) *ShareQuery {
return query return query
} }
// QueryACLEntries queries the acl_entries edge of a File.
func (c *FileClient) QueryACLEntries(f *File) *AclEntryQuery {
query := (&AclEntryClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := f.ID
step := sqlgraph.NewStep(
sqlgraph.From(file.Table, file.FieldID, id),
sqlgraph.To(aclentry.Table, aclentry.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, file.ACLEntriesTable, file.ACLEntriesColumn),
)
fromV = sqlgraph.Neighbors(f.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryDirectLinks queries the direct_links edge of a File. // QueryDirectLinks queries the direct_links edge of a File.
func (c *FileClient) QueryDirectLinks(f *File) *DirectLinkQuery { func (c *FileClient) QueryDirectLinks(f *File) *DirectLinkQuery {
query := (&DirectLinkClient{config: c.config}).Query() query := (&DirectLinkClient{config: c.config}).Query()
@ -3276,14 +3451,14 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error)
// hooks and interceptors per client, for fast access. // hooks and interceptors per client, for fast access.
type ( type (
hooks struct { hooks struct {
DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode, Metadata, AclEntry, DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode,
Node, OAuthClient, OAuthGrant, Passkey, Setting, Share, StoragePolicy, Task, Metadata, Node, OAuthClient, OAuthGrant, Passkey, Setting, Share,
User []ent.Hook StoragePolicy, Task, User []ent.Hook
} }
inters struct { inters struct {
DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode, Metadata, AclEntry, DavAccount, DirectLink, Entity, File, FsEvent, Group, InvitationCode,
Node, OAuthClient, OAuthGrant, Passkey, Setting, Share, StoragePolicy, Task, Metadata, Node, OAuthClient, OAuthGrant, Passkey, Setting, Share,
User []ent.Interceptor StoragePolicy, Task, User []ent.Interceptor
} }
) )

@ -12,6 +12,7 @@ import (
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/davaccount"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
@ -89,6 +90,7 @@ var (
func checkColumn(table, column string) error { func checkColumn(table, column string) error {
initCheck.Do(func() { initCheck.Do(func() {
columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ columnCheck = sql.NewColumnCheck(map[string]func(string) bool{
aclentry.Table: aclentry.ValidColumn,
davaccount.Table: davaccount.ValidColumn, davaccount.Table: davaccount.ValidColumn,
directlink.Table: directlink.ValidColumn, directlink.Table: directlink.ValidColumn,
entity.Table: entity.ValidColumn, entity.Table: entity.ValidColumn,

@ -65,11 +65,13 @@ type FileEdges struct {
Entities []*Entity `json:"entities,omitempty"` Entities []*Entity `json:"entities,omitempty"`
// Shares holds the value of the shares edge. // Shares holds the value of the shares edge.
Shares []*Share `json:"shares,omitempty"` Shares []*Share `json:"shares,omitempty"`
// ACLEntries holds the value of the acl_entries edge.
ACLEntries []*AclEntry `json:"acl_entries,omitempty"`
// DirectLinks holds the value of the direct_links edge. // DirectLinks holds the value of the direct_links edge.
DirectLinks []*DirectLink `json:"direct_links,omitempty"` DirectLinks []*DirectLink `json:"direct_links,omitempty"`
// loadedTypes holds the information for reporting if a // loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not. // type was loaded (or requested) in eager-loading or not.
loadedTypes [8]bool loadedTypes [9]bool
} }
// OwnerOrErr returns the Owner value or an error if the edge // OwnerOrErr returns the Owner value or an error if the edge
@ -147,10 +149,19 @@ func (e FileEdges) SharesOrErr() ([]*Share, error) {
return nil, &NotLoadedError{edge: "shares"} return nil, &NotLoadedError{edge: "shares"}
} }
// ACLEntriesOrErr returns the ACLEntries value or an error if the edge
// was not loaded in eager-loading.
func (e FileEdges) ACLEntriesOrErr() ([]*AclEntry, error) {
if e.loadedTypes[7] {
return e.ACLEntries, nil
}
return nil, &NotLoadedError{edge: "acl_entries"}
}
// DirectLinksOrErr returns the DirectLinks value or an error if the edge // DirectLinksOrErr returns the DirectLinks value or an error if the edge
// was not loaded in eager-loading. // was not loaded in eager-loading.
func (e FileEdges) DirectLinksOrErr() ([]*DirectLink, error) { func (e FileEdges) DirectLinksOrErr() ([]*DirectLink, error) {
if e.loadedTypes[7] { if e.loadedTypes[8] {
return e.DirectLinks, nil return e.DirectLinks, nil
} }
return nil, &NotLoadedError{edge: "direct_links"} return nil, &NotLoadedError{edge: "direct_links"}
@ -308,6 +319,11 @@ func (f *File) QueryShares() *ShareQuery {
return NewFileClient(f.config).QueryShares(f) return NewFileClient(f.config).QueryShares(f)
} }
// QueryACLEntries queries the "acl_entries" edge of the File entity.
func (f *File) QueryACLEntries() *AclEntryQuery {
return NewFileClient(f.config).QueryACLEntries(f)
}
// QueryDirectLinks queries the "direct_links" edge of the File entity. // QueryDirectLinks queries the "direct_links" edge of the File entity.
func (f *File) QueryDirectLinks() *DirectLinkQuery { func (f *File) QueryDirectLinks() *DirectLinkQuery {
return NewFileClient(f.config).QueryDirectLinks(f) return NewFileClient(f.config).QueryDirectLinks(f)
@ -414,10 +430,16 @@ func (e *File) SetShares(v []*Share) {
e.Edges.loadedTypes[6] = true e.Edges.loadedTypes[6] = true
} }
// SetACLEntries manually set the edge as loaded state.
func (e *File) SetACLEntries(v []*AclEntry) {
e.Edges.ACLEntries = v
e.Edges.loadedTypes[7] = true
}
// SetDirectLinks manually set the edge as loaded state. // SetDirectLinks manually set the edge as loaded state.
func (e *File) SetDirectLinks(v []*DirectLink) { func (e *File) SetDirectLinks(v []*DirectLink) {
e.Edges.DirectLinks = v e.Edges.DirectLinks = v
e.Edges.loadedTypes[7] = true e.Edges.loadedTypes[8] = true
} }
// Files is a parsable slice of File. // Files is a parsable slice of File.

@ -51,6 +51,8 @@ const (
EdgeEntities = "entities" EdgeEntities = "entities"
// EdgeShares holds the string denoting the shares edge name in mutations. // EdgeShares holds the string denoting the shares edge name in mutations.
EdgeShares = "shares" EdgeShares = "shares"
// EdgeACLEntries holds the string denoting the acl_entries edge name in mutations.
EdgeACLEntries = "acl_entries"
// EdgeDirectLinks holds the string denoting the direct_links edge name in mutations. // EdgeDirectLinks holds the string denoting the direct_links edge name in mutations.
EdgeDirectLinks = "direct_links" EdgeDirectLinks = "direct_links"
// Table holds the table name of the file in the database. // Table holds the table name of the file in the database.
@ -96,6 +98,13 @@ const (
SharesInverseTable = "shares" SharesInverseTable = "shares"
// SharesColumn is the table column denoting the shares relation/edge. // SharesColumn is the table column denoting the shares relation/edge.
SharesColumn = "file_shares" SharesColumn = "file_shares"
// ACLEntriesTable is the table that holds the acl_entries relation/edge.
ACLEntriesTable = "acl_entries"
// ACLEntriesInverseTable is the table name for the AclEntry entity.
// It exists in this package in order to avoid circular dependency with the "aclentry" package.
ACLEntriesInverseTable = "acl_entries"
// ACLEntriesColumn is the table column denoting the acl_entries relation/edge.
ACLEntriesColumn = "file_id"
// DirectLinksTable is the table that holds the direct_links relation/edge. // DirectLinksTable is the table that holds the direct_links relation/edge.
DirectLinksTable = "direct_links" DirectLinksTable = "direct_links"
// DirectLinksInverseTable is the table name for the DirectLink entity. // DirectLinksInverseTable is the table name for the DirectLink entity.
@ -289,6 +298,20 @@ func ByShares(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
} }
} }
// ByACLEntriesCount orders the results by acl_entries count.
func ByACLEntriesCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newACLEntriesStep(), opts...)
}
}
// ByACLEntries orders the results by acl_entries terms.
func ByACLEntries(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newACLEntriesStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByDirectLinksCount orders the results by direct_links count. // ByDirectLinksCount orders the results by direct_links count.
func ByDirectLinksCount(opts ...sql.OrderTermOption) OrderOption { func ByDirectLinksCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) { return func(s *sql.Selector) {
@ -351,6 +374,13 @@ func newSharesStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, SharesTable, SharesColumn), sqlgraph.Edge(sqlgraph.O2M, false, SharesTable, SharesColumn),
) )
} }
func newACLEntriesStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ACLEntriesInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ACLEntriesTable, ACLEntriesColumn),
)
}
func newDirectLinksStep() *sqlgraph.Step { func newDirectLinksStep() *sqlgraph.Step {
return sqlgraph.NewStep( return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID), sqlgraph.From(Table, FieldID),

@ -641,6 +641,29 @@ func HasSharesWith(preds ...predicate.Share) predicate.File {
}) })
} }
// HasACLEntries applies the HasEdge predicate on the "acl_entries" edge.
func HasACLEntries() predicate.File {
return predicate.File(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ACLEntriesTable, ACLEntriesColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasACLEntriesWith applies the HasEdge predicate on the "acl_entries" edge with a given conditions (other predicates).
func HasACLEntriesWith(preds ...predicate.AclEntry) predicate.File {
return predicate.File(func(s *sql.Selector) {
step := newACLEntriesStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasDirectLinks applies the HasEdge predicate on the "direct_links" edge. // HasDirectLinks applies the HasEdge predicate on the "direct_links" edge.
func HasDirectLinks() predicate.File { func HasDirectLinks() predicate.File {
return predicate.File(func(s *sql.Selector) { return predicate.File(func(s *sql.Selector) {

@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
"github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/file"
@ -254,6 +255,21 @@ func (fc *FileCreate) AddShares(s ...*Share) *FileCreate {
return fc.AddShareIDs(ids...) return fc.AddShareIDs(ids...)
} }
// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by IDs.
func (fc *FileCreate) AddACLEntryIDs(ids ...int) *FileCreate {
fc.mutation.AddACLEntryIDs(ids...)
return fc
}
// AddACLEntries adds the "acl_entries" edges to the AclEntry entity.
func (fc *FileCreate) AddACLEntries(a ...*AclEntry) *FileCreate {
ids := make([]int, len(a))
for i := range a {
ids[i] = a[i].ID
}
return fc.AddACLEntryIDs(ids...)
}
// AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs. // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs.
func (fc *FileCreate) AddDirectLinkIDs(ids ...int) *FileCreate { func (fc *FileCreate) AddDirectLinkIDs(ids ...int) *FileCreate {
fc.mutation.AddDirectLinkIDs(ids...) fc.mutation.AddDirectLinkIDs(ids...)
@ -538,6 +554,22 @@ func (fc *FileCreate) createSpec() (*File, *sqlgraph.CreateSpec) {
} }
_spec.Edges = append(_spec.Edges, edge) _spec.Edges = append(_spec.Edges, edge)
} }
if nodes := fc.mutation.ACLEntriesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: file.ACLEntriesTable,
Columns: []string{file.ACLEntriesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := fc.mutation.DirectLinksIDs(); len(nodes) > 0 { if nodes := fc.mutation.DirectLinksIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M, Rel: sqlgraph.O2M,

@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
"github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/file"
@ -35,6 +36,7 @@ type FileQuery struct {
withMetadata *MetadataQuery withMetadata *MetadataQuery
withEntities *EntityQuery withEntities *EntityQuery
withShares *ShareQuery withShares *ShareQuery
withACLEntries *AclEntryQuery
withDirectLinks *DirectLinkQuery withDirectLinks *DirectLinkQuery
// intermediate query (i.e. traversal path). // intermediate query (i.e. traversal path).
sql *sql.Selector sql *sql.Selector
@ -226,6 +228,28 @@ func (fq *FileQuery) QueryShares() *ShareQuery {
return query return query
} }
// QueryACLEntries chains the current query on the "acl_entries" edge.
func (fq *FileQuery) QueryACLEntries() *AclEntryQuery {
query := (&AclEntryClient{config: fq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := fq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := fq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(file.Table, file.FieldID, selector),
sqlgraph.To(aclentry.Table, aclentry.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, file.ACLEntriesTable, file.ACLEntriesColumn),
)
fromU = sqlgraph.SetNeighbors(fq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryDirectLinks chains the current query on the "direct_links" edge. // QueryDirectLinks chains the current query on the "direct_links" edge.
func (fq *FileQuery) QueryDirectLinks() *DirectLinkQuery { func (fq *FileQuery) QueryDirectLinks() *DirectLinkQuery {
query := (&DirectLinkClient{config: fq.config}).Query() query := (&DirectLinkClient{config: fq.config}).Query()
@ -447,6 +471,7 @@ func (fq *FileQuery) Clone() *FileQuery {
withMetadata: fq.withMetadata.Clone(), withMetadata: fq.withMetadata.Clone(),
withEntities: fq.withEntities.Clone(), withEntities: fq.withEntities.Clone(),
withShares: fq.withShares.Clone(), withShares: fq.withShares.Clone(),
withACLEntries: fq.withACLEntries.Clone(),
withDirectLinks: fq.withDirectLinks.Clone(), withDirectLinks: fq.withDirectLinks.Clone(),
// clone intermediate query. // clone intermediate query.
sql: fq.sql.Clone(), sql: fq.sql.Clone(),
@ -531,6 +556,17 @@ func (fq *FileQuery) WithShares(opts ...func(*ShareQuery)) *FileQuery {
return fq return fq
} }
// WithACLEntries tells the query-builder to eager-load the nodes that are connected to
// the "acl_entries" edge. The optional arguments are used to configure the query builder of the edge.
func (fq *FileQuery) WithACLEntries(opts ...func(*AclEntryQuery)) *FileQuery {
query := (&AclEntryClient{config: fq.config}).Query()
for _, opt := range opts {
opt(query)
}
fq.withACLEntries = query
return fq
}
// WithDirectLinks tells the query-builder to eager-load the nodes that are connected to // WithDirectLinks tells the query-builder to eager-load the nodes that are connected to
// the "direct_links" edge. The optional arguments are used to configure the query builder of the edge. // the "direct_links" edge. The optional arguments are used to configure the query builder of the edge.
func (fq *FileQuery) WithDirectLinks(opts ...func(*DirectLinkQuery)) *FileQuery { func (fq *FileQuery) WithDirectLinks(opts ...func(*DirectLinkQuery)) *FileQuery {
@ -620,7 +656,7 @@ func (fq *FileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*File, e
var ( var (
nodes = []*File{} nodes = []*File{}
_spec = fq.querySpec() _spec = fq.querySpec()
loadedTypes = [8]bool{ loadedTypes = [9]bool{
fq.withOwner != nil, fq.withOwner != nil,
fq.withStoragePolicies != nil, fq.withStoragePolicies != nil,
fq.withParent != nil, fq.withParent != nil,
@ -628,6 +664,7 @@ func (fq *FileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*File, e
fq.withMetadata != nil, fq.withMetadata != nil,
fq.withEntities != nil, fq.withEntities != nil,
fq.withShares != nil, fq.withShares != nil,
fq.withACLEntries != nil,
fq.withDirectLinks != nil, fq.withDirectLinks != nil,
} }
) )
@ -695,6 +732,13 @@ func (fq *FileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*File, e
return nil, err return nil, err
} }
} }
if query := fq.withACLEntries; query != nil {
if err := fq.loadACLEntries(ctx, query, nodes,
func(n *File) { n.Edges.ACLEntries = []*AclEntry{} },
func(n *File, e *AclEntry) { n.Edges.ACLEntries = append(n.Edges.ACLEntries, e) }); err != nil {
return nil, err
}
}
if query := fq.withDirectLinks; query != nil { if query := fq.withDirectLinks; query != nil {
if err := fq.loadDirectLinks(ctx, query, nodes, if err := fq.loadDirectLinks(ctx, query, nodes,
func(n *File) { n.Edges.DirectLinks = []*DirectLink{} }, func(n *File) { n.Edges.DirectLinks = []*DirectLink{} },
@ -944,6 +988,36 @@ func (fq *FileQuery) loadShares(ctx context.Context, query *ShareQuery, nodes []
} }
return nil return nil
} }
func (fq *FileQuery) loadACLEntries(ctx context.Context, query *AclEntryQuery, nodes []*File, init func(*File), assign func(*File, *AclEntry)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int]*File)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(aclentry.FieldFileID)
}
query.Where(predicate.AclEntry(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(file.ACLEntriesColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.FileID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "file_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (fq *FileQuery) loadDirectLinks(ctx context.Context, query *DirectLinkQuery, nodes []*File, init func(*File), assign func(*File, *DirectLink)) error { func (fq *FileQuery) loadDirectLinks(ctx context.Context, query *DirectLinkQuery, nodes []*File, init func(*File), assign func(*File, *DirectLink)) error {
fks := make([]driver.Value, 0, len(nodes)) fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int]*File) nodeids := make(map[int]*File)

@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
"github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/file"
@ -315,6 +316,21 @@ func (fu *FileUpdate) AddShares(s ...*Share) *FileUpdate {
return fu.AddShareIDs(ids...) return fu.AddShareIDs(ids...)
} }
// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by IDs.
func (fu *FileUpdate) AddACLEntryIDs(ids ...int) *FileUpdate {
fu.mutation.AddACLEntryIDs(ids...)
return fu
}
// AddACLEntries adds the "acl_entries" edges to the AclEntry entity.
func (fu *FileUpdate) AddACLEntries(a ...*AclEntry) *FileUpdate {
ids := make([]int, len(a))
for i := range a {
ids[i] = a[i].ID
}
return fu.AddACLEntryIDs(ids...)
}
// AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs. // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs.
func (fu *FileUpdate) AddDirectLinkIDs(ids ...int) *FileUpdate { func (fu *FileUpdate) AddDirectLinkIDs(ids ...int) *FileUpdate {
fu.mutation.AddDirectLinkIDs(ids...) fu.mutation.AddDirectLinkIDs(ids...)
@ -437,6 +453,27 @@ func (fu *FileUpdate) RemoveShares(s ...*Share) *FileUpdate {
return fu.RemoveShareIDs(ids...) return fu.RemoveShareIDs(ids...)
} }
// ClearACLEntries clears all "acl_entries" edges to the AclEntry entity.
func (fu *FileUpdate) ClearACLEntries() *FileUpdate {
fu.mutation.ClearACLEntries()
return fu
}
// RemoveACLEntryIDs removes the "acl_entries" edge to AclEntry entities by IDs.
func (fu *FileUpdate) RemoveACLEntryIDs(ids ...int) *FileUpdate {
fu.mutation.RemoveACLEntryIDs(ids...)
return fu
}
// RemoveACLEntries removes "acl_entries" edges to AclEntry entities.
func (fu *FileUpdate) RemoveACLEntries(a ...*AclEntry) *FileUpdate {
ids := make([]int, len(a))
for i := range a {
ids[i] = a[i].ID
}
return fu.RemoveACLEntryIDs(ids...)
}
// ClearDirectLinks clears all "direct_links" edges to the DirectLink entity. // ClearDirectLinks clears all "direct_links" edges to the DirectLink entity.
func (fu *FileUpdate) ClearDirectLinks() *FileUpdate { func (fu *FileUpdate) ClearDirectLinks() *FileUpdate {
fu.mutation.ClearDirectLinks() fu.mutation.ClearDirectLinks()
@ -808,6 +845,51 @@ func (fu *FileUpdate) sqlSave(ctx context.Context) (n int, err error) {
} }
_spec.Edges.Add = append(_spec.Edges.Add, edge) _spec.Edges.Add = append(_spec.Edges.Add, edge)
} }
if fu.mutation.ACLEntriesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: file.ACLEntriesTable,
Columns: []string{file.ACLEntriesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := fu.mutation.RemovedACLEntriesIDs(); len(nodes) > 0 && !fu.mutation.ACLEntriesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: file.ACLEntriesTable,
Columns: []string{file.ACLEntriesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := fu.mutation.ACLEntriesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: file.ACLEntriesTable,
Columns: []string{file.ACLEntriesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if fu.mutation.DirectLinksCleared() { if fu.mutation.DirectLinksCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M, Rel: sqlgraph.O2M,
@ -1153,6 +1235,21 @@ func (fuo *FileUpdateOne) AddShares(s ...*Share) *FileUpdateOne {
return fuo.AddShareIDs(ids...) return fuo.AddShareIDs(ids...)
} }
// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by IDs.
func (fuo *FileUpdateOne) AddACLEntryIDs(ids ...int) *FileUpdateOne {
fuo.mutation.AddACLEntryIDs(ids...)
return fuo
}
// AddACLEntries adds the "acl_entries" edges to the AclEntry entity.
func (fuo *FileUpdateOne) AddACLEntries(a ...*AclEntry) *FileUpdateOne {
ids := make([]int, len(a))
for i := range a {
ids[i] = a[i].ID
}
return fuo.AddACLEntryIDs(ids...)
}
// AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs. // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by IDs.
func (fuo *FileUpdateOne) AddDirectLinkIDs(ids ...int) *FileUpdateOne { func (fuo *FileUpdateOne) AddDirectLinkIDs(ids ...int) *FileUpdateOne {
fuo.mutation.AddDirectLinkIDs(ids...) fuo.mutation.AddDirectLinkIDs(ids...)
@ -1275,6 +1372,27 @@ func (fuo *FileUpdateOne) RemoveShares(s ...*Share) *FileUpdateOne {
return fuo.RemoveShareIDs(ids...) return fuo.RemoveShareIDs(ids...)
} }
// ClearACLEntries clears all "acl_entries" edges to the AclEntry entity.
func (fuo *FileUpdateOne) ClearACLEntries() *FileUpdateOne {
fuo.mutation.ClearACLEntries()
return fuo
}
// RemoveACLEntryIDs removes the "acl_entries" edge to AclEntry entities by IDs.
func (fuo *FileUpdateOne) RemoveACLEntryIDs(ids ...int) *FileUpdateOne {
fuo.mutation.RemoveACLEntryIDs(ids...)
return fuo
}
// RemoveACLEntries removes "acl_entries" edges to AclEntry entities.
func (fuo *FileUpdateOne) RemoveACLEntries(a ...*AclEntry) *FileUpdateOne {
ids := make([]int, len(a))
for i := range a {
ids[i] = a[i].ID
}
return fuo.RemoveACLEntryIDs(ids...)
}
// ClearDirectLinks clears all "direct_links" edges to the DirectLink entity. // ClearDirectLinks clears all "direct_links" edges to the DirectLink entity.
func (fuo *FileUpdateOne) ClearDirectLinks() *FileUpdateOne { func (fuo *FileUpdateOne) ClearDirectLinks() *FileUpdateOne {
fuo.mutation.ClearDirectLinks() fuo.mutation.ClearDirectLinks()
@ -1676,6 +1794,51 @@ func (fuo *FileUpdateOne) sqlSave(ctx context.Context) (_node *File, err error)
} }
_spec.Edges.Add = append(_spec.Edges.Add, edge) _spec.Edges.Add = append(_spec.Edges.Add, edge)
} }
if fuo.mutation.ACLEntriesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: file.ACLEntriesTable,
Columns: []string{file.ACLEntriesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := fuo.mutation.RemovedACLEntriesIDs(); len(nodes) > 0 && !fuo.mutation.ACLEntriesCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: file.ACLEntriesTable,
Columns: []string{file.ACLEntriesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := fuo.mutation.ACLEntriesIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: file.ACLEntriesTable,
Columns: []string{file.ACLEntriesColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(aclentry.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if fuo.mutation.DirectLinksCleared() { if fuo.mutation.DirectLinksCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M, Rel: sqlgraph.O2M,

@ -9,6 +9,18 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent"
) )
// The AclEntryFunc type is an adapter to allow the use of ordinary
// function as AclEntry mutator.
type AclEntryFunc func(context.Context, *ent.AclEntryMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f AclEntryFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.AclEntryMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AclEntryMutation", m)
}
// The DavAccountFunc type is an adapter to allow the use of ordinary // The DavAccountFunc type is an adapter to allow the use of ordinary
// function as DavAccount mutator. // function as DavAccount mutator.
type DavAccountFunc func(context.Context, *ent.DavAccountMutation) (ent.Value, error) type DavAccountFunc func(context.Context, *ent.DavAccountMutation) (ent.Value, error)

@ -8,6 +8,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/davaccount"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
@ -84,6 +85,33 @@ func (f TraverseFunc) Traverse(ctx context.Context, q ent.Query) error {
return f(ctx, query) return f(ctx, query)
} }
// The AclEntryFunc type is an adapter to allow the use of ordinary function as a Querier.
type AclEntryFunc func(context.Context, *ent.AclEntryQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f AclEntryFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.AclEntryQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.AclEntryQuery", q)
}
// The TraverseAclEntry type is an adapter to allow the use of ordinary function as Traverser.
type TraverseAclEntry func(context.Context, *ent.AclEntryQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseAclEntry) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseAclEntry) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.AclEntryQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.AclEntryQuery", q)
}
// The DavAccountFunc type is an adapter to allow the use of ordinary function as a Querier. // The DavAccountFunc type is an adapter to allow the use of ordinary function as a Querier.
type DavAccountFunc func(context.Context, *ent.DavAccountQuery) (ent.Value, error) type DavAccountFunc func(context.Context, *ent.DavAccountQuery) (ent.Value, error)
@ -546,6 +574,8 @@ func (f TraverseUser) Traverse(ctx context.Context, q ent.Query) error {
// NewQuery returns the generic Query interface for the given typed query. // NewQuery returns the generic Query interface for the given typed query.
func NewQuery(q ent.Query) (Query, error) { func NewQuery(q ent.Query) (Query, error) {
switch q := q.(type) { switch q := q.(type) {
case *ent.AclEntryQuery:
return &query[*ent.AclEntryQuery, predicate.AclEntry, aclentry.OrderOption]{typ: ent.TypeAclEntry, tq: q}, nil
case *ent.DavAccountQuery: case *ent.DavAccountQuery:
return &query[*ent.DavAccountQuery, predicate.DavAccount, davaccount.OrderOption]{typ: ent.TypeDavAccount, tq: q}, nil return &query[*ent.DavAccountQuery, predicate.DavAccount, davaccount.OrderOption]{typ: ent.TypeDavAccount, tq: q}, nil
case *ent.DirectLinkQuery: case *ent.DirectLinkQuery:

File diff suppressed because one or more lines are too long

@ -8,6 +8,38 @@ import (
) )
var ( var (
// ACLEntriesColumns holds the columns for the "acl_entries" table.
ACLEntriesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime"}},
{Name: "subject_type", Type: field.TypeEnum, Enums: []string{"user", "group", "anonymous", "everyone"}},
{Name: "subject_id", Type: field.TypeInt, Nullable: true},
{Name: "permissions", Type: field.TypeBytes},
{Name: "file_id", Type: field.TypeInt},
}
// ACLEntriesTable holds the schema information for the "acl_entries" table.
ACLEntriesTable = &schema.Table{
Name: "acl_entries",
Columns: ACLEntriesColumns,
PrimaryKey: []*schema.Column{ACLEntriesColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "acl_entries_files_acl_entries",
Columns: []*schema.Column{ACLEntriesColumns[7]},
RefColumns: []*schema.Column{FilesColumns[0]},
OnDelete: schema.NoAction,
},
},
Indexes: []*schema.Index{
{
Name: "aclentry_file_id_subject_type_subject_id",
Unique: true,
Columns: []*schema.Column{ACLEntriesColumns[7], ACLEntriesColumns[4], ACLEntriesColumns[5]},
},
},
}
// DavAccountsColumns holds the columns for the "dav_accounts" table. // DavAccountsColumns holds the columns for the "dav_accounts" table.
DavAccountsColumns = []*schema.Column{ DavAccountsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true}, {Name: "id", Type: field.TypeInt, Increment: true},
@ -577,6 +609,7 @@ var (
} }
// Tables holds all the tables in the schema. // Tables holds all the tables in the schema.
Tables = []*schema.Table{ Tables = []*schema.Table{
ACLEntriesTable,
DavAccountsTable, DavAccountsTable,
DirectLinksTable, DirectLinksTable,
EntitiesTable, EntitiesTable,
@ -600,6 +633,7 @@ var (
) )
func init() { func init() {
ACLEntriesTable.ForeignKeys[0].RefTable = FilesTable
DavAccountsTable.ForeignKeys[0].RefTable = UsersTable DavAccountsTable.ForeignKeys[0].RefTable = UsersTable
DirectLinksTable.ForeignKeys[0].RefTable = FilesTable DirectLinksTable.ForeignKeys[0].RefTable = FilesTable
EntitiesTable.ForeignKeys[0].RefTable = StoragePoliciesTable EntitiesTable.ForeignKeys[0].RefTable = StoragePoliciesTable

@ -11,6 +11,7 @@ import (
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/davaccount"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
@ -44,6 +45,7 @@ const (
OpUpdateOne = ent.OpUpdateOne OpUpdateOne = ent.OpUpdateOne
// Node types. // Node types.
TypeAclEntry = "AclEntry"
TypeDavAccount = "DavAccount" TypeDavAccount = "DavAccount"
TypeDirectLink = "DirectLink" TypeDirectLink = "DirectLink"
TypeEntity = "Entity" TypeEntity = "Entity"
@ -63,6 +65,788 @@ const (
TypeUser = "User" TypeUser = "User"
) )
// AclEntryMutation represents an operation that mutates the AclEntry nodes in the graph.
type AclEntryMutation struct {
config
op Op
typ string
id *int
created_at *time.Time
updated_at *time.Time
deleted_at *time.Time
subject_type *aclentry.SubjectType
subject_id *int
addsubject_id *int
permissions **boolset.BooleanSet
clearedFields map[string]struct{}
file *int
clearedfile bool
done bool
oldValue func(context.Context) (*AclEntry, error)
predicates []predicate.AclEntry
}
var _ ent.Mutation = (*AclEntryMutation)(nil)
// aclentryOption allows management of the mutation configuration using functional options.
type aclentryOption func(*AclEntryMutation)
// newAclEntryMutation creates new mutation for the AclEntry entity.
func newAclEntryMutation(c config, op Op, opts ...aclentryOption) *AclEntryMutation {
m := &AclEntryMutation{
config: c,
op: op,
typ: TypeAclEntry,
clearedFields: make(map[string]struct{}),
}
for _, opt := range opts {
opt(m)
}
return m
}
// withAclEntryID sets the ID field of the mutation.
func withAclEntryID(id int) aclentryOption {
return func(m *AclEntryMutation) {
var (
err error
once sync.Once
value *AclEntry
)
m.oldValue = func(ctx context.Context) (*AclEntry, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
value, err = m.Client().AclEntry.Get(ctx, id)
}
})
return value, err
}
m.id = &id
}
}
// withAclEntry sets the old AclEntry of the mutation.
func withAclEntry(node *AclEntry) aclentryOption {
return func(m *AclEntryMutation) {
m.oldValue = func(context.Context) (*AclEntry, error) {
return node, nil
}
m.id = &node.ID
}
}
// Client returns a new `ent.Client` from the mutation. If the mutation was
// executed in a transaction (ent.Tx), a transactional client is returned.
func (m AclEntryMutation) Client() *Client {
client := &Client{config: m.config}
client.init()
return client
}
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
// it returns an error otherwise.
func (m AclEntryMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
tx := &Tx{config: m.config}
tx.init()
return tx, nil
}
// ID returns the ID value in the mutation. Note that the ID is only available
// if it was provided to the builder or after it was returned from the database.
func (m *AclEntryMutation) ID() (id int, exists bool) {
if m.id == nil {
return
}
return *m.id, true
}
// IDs queries the database and returns the entity ids that match the mutation's predicate.
// That means, if the mutation is applied within a transaction with an isolation level such
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
// or updated by the mutation.
func (m *AclEntryMutation) IDs(ctx context.Context) ([]int, error) {
switch {
case m.op.Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
if exists {
return []int{id}, nil
}
fallthrough
case m.op.Is(OpUpdate | OpDelete):
return m.Client().AclEntry.Query().Where(m.predicates...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
}
}
// SetCreatedAt sets the "created_at" field.
func (m *AclEntryMutation) SetCreatedAt(t time.Time) {
m.created_at = &t
}
// CreatedAt returns the value of the "created_at" field in the mutation.
func (m *AclEntryMutation) CreatedAt() (r time.Time, exists bool) {
v := m.created_at
if v == nil {
return
}
return *v, true
}
// OldCreatedAt returns the old "created_at" field's value of the AclEntry entity.
// If the AclEntry object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *AclEntryMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldCreatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
}
return oldValue.CreatedAt, nil
}
// ResetCreatedAt resets all changes to the "created_at" field.
func (m *AclEntryMutation) ResetCreatedAt() {
m.created_at = nil
}
// SetUpdatedAt sets the "updated_at" field.
func (m *AclEntryMutation) SetUpdatedAt(t time.Time) {
m.updated_at = &t
}
// UpdatedAt returns the value of the "updated_at" field in the mutation.
func (m *AclEntryMutation) UpdatedAt() (r time.Time, exists bool) {
v := m.updated_at
if v == nil {
return
}
return *v, true
}
// OldUpdatedAt returns the old "updated_at" field's value of the AclEntry entity.
// If the AclEntry object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *AclEntryMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
}
return oldValue.UpdatedAt, nil
}
// ResetUpdatedAt resets all changes to the "updated_at" field.
func (m *AclEntryMutation) ResetUpdatedAt() {
m.updated_at = nil
}
// SetDeletedAt sets the "deleted_at" field.
func (m *AclEntryMutation) SetDeletedAt(t time.Time) {
m.deleted_at = &t
}
// DeletedAt returns the value of the "deleted_at" field in the mutation.
func (m *AclEntryMutation) DeletedAt() (r time.Time, exists bool) {
v := m.deleted_at
if v == nil {
return
}
return *v, true
}
// OldDeletedAt returns the old "deleted_at" field's value of the AclEntry entity.
// If the AclEntry object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *AclEntryMutation) OldDeletedAt(ctx context.Context) (v *time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldDeletedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err)
}
return oldValue.DeletedAt, nil
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (m *AclEntryMutation) ClearDeletedAt() {
m.deleted_at = nil
m.clearedFields[aclentry.FieldDeletedAt] = struct{}{}
}
// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation.
func (m *AclEntryMutation) DeletedAtCleared() bool {
_, ok := m.clearedFields[aclentry.FieldDeletedAt]
return ok
}
// ResetDeletedAt resets all changes to the "deleted_at" field.
func (m *AclEntryMutation) ResetDeletedAt() {
m.deleted_at = nil
delete(m.clearedFields, aclentry.FieldDeletedAt)
}
// SetFileID sets the "file_id" field.
func (m *AclEntryMutation) SetFileID(i int) {
m.file = &i
}
// FileID returns the value of the "file_id" field in the mutation.
func (m *AclEntryMutation) FileID() (r int, exists bool) {
v := m.file
if v == nil {
return
}
return *v, true
}
// OldFileID returns the old "file_id" field's value of the AclEntry entity.
// If the AclEntry object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *AclEntryMutation) OldFileID(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldFileID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldFileID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldFileID: %w", err)
}
return oldValue.FileID, nil
}
// ResetFileID resets all changes to the "file_id" field.
func (m *AclEntryMutation) ResetFileID() {
m.file = nil
}
// SetSubjectType sets the "subject_type" field.
func (m *AclEntryMutation) SetSubjectType(at aclentry.SubjectType) {
m.subject_type = &at
}
// SubjectType returns the value of the "subject_type" field in the mutation.
func (m *AclEntryMutation) SubjectType() (r aclentry.SubjectType, exists bool) {
v := m.subject_type
if v == nil {
return
}
return *v, true
}
// OldSubjectType returns the old "subject_type" field's value of the AclEntry entity.
// If the AclEntry object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *AclEntryMutation) OldSubjectType(ctx context.Context) (v aclentry.SubjectType, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSubjectType is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSubjectType requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSubjectType: %w", err)
}
return oldValue.SubjectType, nil
}
// ResetSubjectType resets all changes to the "subject_type" field.
func (m *AclEntryMutation) ResetSubjectType() {
m.subject_type = nil
}
// SetSubjectID sets the "subject_id" field.
func (m *AclEntryMutation) SetSubjectID(i int) {
m.subject_id = &i
m.addsubject_id = nil
}
// SubjectID returns the value of the "subject_id" field in the mutation.
func (m *AclEntryMutation) SubjectID() (r int, exists bool) {
v := m.subject_id
if v == nil {
return
}
return *v, true
}
// OldSubjectID returns the old "subject_id" field's value of the AclEntry entity.
// If the AclEntry object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *AclEntryMutation) OldSubjectID(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSubjectID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSubjectID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSubjectID: %w", err)
}
return oldValue.SubjectID, nil
}
// AddSubjectID adds i to the "subject_id" field.
func (m *AclEntryMutation) AddSubjectID(i int) {
if m.addsubject_id != nil {
*m.addsubject_id += i
} else {
m.addsubject_id = &i
}
}
// AddedSubjectID returns the value that was added to the "subject_id" field in this mutation.
func (m *AclEntryMutation) AddedSubjectID() (r int, exists bool) {
v := m.addsubject_id
if v == nil {
return
}
return *v, true
}
// ClearSubjectID clears the value of the "subject_id" field.
func (m *AclEntryMutation) ClearSubjectID() {
m.subject_id = nil
m.addsubject_id = nil
m.clearedFields[aclentry.FieldSubjectID] = struct{}{}
}
// SubjectIDCleared returns if the "subject_id" field was cleared in this mutation.
func (m *AclEntryMutation) SubjectIDCleared() bool {
_, ok := m.clearedFields[aclentry.FieldSubjectID]
return ok
}
// ResetSubjectID resets all changes to the "subject_id" field.
func (m *AclEntryMutation) ResetSubjectID() {
m.subject_id = nil
m.addsubject_id = nil
delete(m.clearedFields, aclentry.FieldSubjectID)
}
// SetPermissions sets the "permissions" field.
func (m *AclEntryMutation) SetPermissions(bs *boolset.BooleanSet) {
m.permissions = &bs
}
// Permissions returns the value of the "permissions" field in the mutation.
func (m *AclEntryMutation) Permissions() (r *boolset.BooleanSet, exists bool) {
v := m.permissions
if v == nil {
return
}
return *v, true
}
// OldPermissions returns the old "permissions" field's value of the AclEntry entity.
// If the AclEntry object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *AclEntryMutation) OldPermissions(ctx context.Context) (v *boolset.BooleanSet, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldPermissions is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldPermissions requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldPermissions: %w", err)
}
return oldValue.Permissions, nil
}
// ResetPermissions resets all changes to the "permissions" field.
func (m *AclEntryMutation) ResetPermissions() {
m.permissions = nil
}
// ClearFile clears the "file" edge to the File entity.
func (m *AclEntryMutation) ClearFile() {
m.clearedfile = true
m.clearedFields[aclentry.FieldFileID] = struct{}{}
}
// FileCleared reports if the "file" edge to the File entity was cleared.
func (m *AclEntryMutation) FileCleared() bool {
return m.clearedfile
}
// FileIDs returns the "file" edge IDs in the mutation.
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
// FileID instead. It exists only for internal usage by the builders.
func (m *AclEntryMutation) FileIDs() (ids []int) {
if id := m.file; id != nil {
ids = append(ids, *id)
}
return
}
// ResetFile resets all changes to the "file" edge.
func (m *AclEntryMutation) ResetFile() {
m.file = nil
m.clearedfile = false
}
// Where appends a list predicates to the AclEntryMutation builder.
func (m *AclEntryMutation) Where(ps ...predicate.AclEntry) {
m.predicates = append(m.predicates, ps...)
}
// WhereP appends storage-level predicates to the AclEntryMutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
func (m *AclEntryMutation) WhereP(ps ...func(*sql.Selector)) {
p := make([]predicate.AclEntry, len(ps))
for i := range ps {
p[i] = ps[i]
}
m.Where(p...)
}
// Op returns the operation name.
func (m *AclEntryMutation) Op() Op {
return m.op
}
// SetOp allows setting the mutation operation.
func (m *AclEntryMutation) SetOp(op Op) {
m.op = op
}
// Type returns the node type of this mutation (AclEntry).
func (m *AclEntryMutation) Type() string {
return m.typ
}
// Fields returns all fields that were changed during this mutation. Note that in
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *AclEntryMutation) Fields() []string {
fields := make([]string, 0, 7)
if m.created_at != nil {
fields = append(fields, aclentry.FieldCreatedAt)
}
if m.updated_at != nil {
fields = append(fields, aclentry.FieldUpdatedAt)
}
if m.deleted_at != nil {
fields = append(fields, aclentry.FieldDeletedAt)
}
if m.file != nil {
fields = append(fields, aclentry.FieldFileID)
}
if m.subject_type != nil {
fields = append(fields, aclentry.FieldSubjectType)
}
if m.subject_id != nil {
fields = append(fields, aclentry.FieldSubjectID)
}
if m.permissions != nil {
fields = append(fields, aclentry.FieldPermissions)
}
return fields
}
// Field returns the value of a field with the given name. The second boolean
// return value indicates that this field was not set, or was not defined in the
// schema.
func (m *AclEntryMutation) Field(name string) (ent.Value, bool) {
switch name {
case aclentry.FieldCreatedAt:
return m.CreatedAt()
case aclentry.FieldUpdatedAt:
return m.UpdatedAt()
case aclentry.FieldDeletedAt:
return m.DeletedAt()
case aclentry.FieldFileID:
return m.FileID()
case aclentry.FieldSubjectType:
return m.SubjectType()
case aclentry.FieldSubjectID:
return m.SubjectID()
case aclentry.FieldPermissions:
return m.Permissions()
}
return nil, false
}
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
func (m *AclEntryMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
case aclentry.FieldCreatedAt:
return m.OldCreatedAt(ctx)
case aclentry.FieldUpdatedAt:
return m.OldUpdatedAt(ctx)
case aclentry.FieldDeletedAt:
return m.OldDeletedAt(ctx)
case aclentry.FieldFileID:
return m.OldFileID(ctx)
case aclentry.FieldSubjectType:
return m.OldSubjectType(ctx)
case aclentry.FieldSubjectID:
return m.OldSubjectID(ctx)
case aclentry.FieldPermissions:
return m.OldPermissions(ctx)
}
return nil, fmt.Errorf("unknown AclEntry field %s", name)
}
// SetField sets the value of a field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
func (m *AclEntryMutation) SetField(name string, value ent.Value) error {
switch name {
case aclentry.FieldCreatedAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetCreatedAt(v)
return nil
case aclentry.FieldUpdatedAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetUpdatedAt(v)
return nil
case aclentry.FieldDeletedAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetDeletedAt(v)
return nil
case aclentry.FieldFileID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetFileID(v)
return nil
case aclentry.FieldSubjectType:
v, ok := value.(aclentry.SubjectType)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSubjectType(v)
return nil
case aclentry.FieldSubjectID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSubjectID(v)
return nil
case aclentry.FieldPermissions:
v, ok := value.(*boolset.BooleanSet)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetPermissions(v)
return nil
}
return fmt.Errorf("unknown AclEntry field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
func (m *AclEntryMutation) AddedFields() []string {
var fields []string
if m.addsubject_id != nil {
fields = append(fields, aclentry.FieldSubjectID)
}
return fields
}
// AddedField returns the numeric value that was incremented/decremented on a field
// with the given name. The second boolean return value indicates that this field
// was not set, or was not defined in the schema.
func (m *AclEntryMutation) AddedField(name string) (ent.Value, bool) {
switch name {
case aclentry.FieldSubjectID:
return m.AddedSubjectID()
}
return nil, false
}
// AddField adds the value to the field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
func (m *AclEntryMutation) AddField(name string, value ent.Value) error {
switch name {
case aclentry.FieldSubjectID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddSubjectID(v)
return nil
}
return fmt.Errorf("unknown AclEntry numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *AclEntryMutation) ClearedFields() []string {
var fields []string
if m.FieldCleared(aclentry.FieldDeletedAt) {
fields = append(fields, aclentry.FieldDeletedAt)
}
if m.FieldCleared(aclentry.FieldSubjectID) {
fields = append(fields, aclentry.FieldSubjectID)
}
return fields
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
func (m *AclEntryMutation) FieldCleared(name string) bool {
_, ok := m.clearedFields[name]
return ok
}
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
func (m *AclEntryMutation) ClearField(name string) error {
switch name {
case aclentry.FieldDeletedAt:
m.ClearDeletedAt()
return nil
case aclentry.FieldSubjectID:
m.ClearSubjectID()
return nil
}
return fmt.Errorf("unknown AclEntry nullable field %s", name)
}
// ResetField resets all changes in the mutation for the field with the given name.
// It returns an error if the field is not defined in the schema.
func (m *AclEntryMutation) ResetField(name string) error {
switch name {
case aclentry.FieldCreatedAt:
m.ResetCreatedAt()
return nil
case aclentry.FieldUpdatedAt:
m.ResetUpdatedAt()
return nil
case aclentry.FieldDeletedAt:
m.ResetDeletedAt()
return nil
case aclentry.FieldFileID:
m.ResetFileID()
return nil
case aclentry.FieldSubjectType:
m.ResetSubjectType()
return nil
case aclentry.FieldSubjectID:
m.ResetSubjectID()
return nil
case aclentry.FieldPermissions:
m.ResetPermissions()
return nil
}
return fmt.Errorf("unknown AclEntry field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *AclEntryMutation) AddedEdges() []string {
edges := make([]string, 0, 1)
if m.file != nil {
edges = append(edges, aclentry.EdgeFile)
}
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
func (m *AclEntryMutation) AddedIDs(name string) []ent.Value {
switch name {
case aclentry.EdgeFile:
if id := m.file; id != nil {
return []ent.Value{*id}
}
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *AclEntryMutation) RemovedEdges() []string {
edges := make([]string, 0, 1)
return edges
}
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
// the given name in this mutation.
func (m *AclEntryMutation) RemovedIDs(name string) []ent.Value {
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *AclEntryMutation) ClearedEdges() []string {
edges := make([]string, 0, 1)
if m.clearedfile {
edges = append(edges, aclentry.EdgeFile)
}
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
func (m *AclEntryMutation) EdgeCleared(name string) bool {
switch name {
case aclentry.EdgeFile:
return m.clearedfile
}
return false
}
// ClearEdge clears the value of the edge with the given name. It returns an error
// if that edge is not defined in the schema.
func (m *AclEntryMutation) ClearEdge(name string) error {
switch name {
case aclentry.EdgeFile:
m.ClearFile()
return nil
}
return fmt.Errorf("unknown AclEntry unique edge %s", name)
}
// ResetEdge resets all changes to the edge with the given name in this mutation.
// It returns an error if the edge is not defined in the schema.
func (m *AclEntryMutation) ResetEdge(name string) error {
switch name {
case aclentry.EdgeFile:
m.ResetFile()
return nil
}
return fmt.Errorf("unknown AclEntry edge %s", name)
}
// DavAccountMutation represents an operation that mutates the DavAccount nodes in the graph. // DavAccountMutation represents an operation that mutates the DavAccount nodes in the graph.
type DavAccountMutation struct { type DavAccountMutation struct {
config config
@ -3008,6 +3792,9 @@ type FileMutation struct {
shares map[int]struct{} shares map[int]struct{}
removedshares map[int]struct{} removedshares map[int]struct{}
clearedshares bool clearedshares bool
acl_entries map[int]struct{}
removedacl_entries map[int]struct{}
clearedacl_entries bool
direct_links map[int]struct{} direct_links map[int]struct{}
removeddirect_links map[int]struct{} removeddirect_links map[int]struct{}
cleareddirect_links bool cleareddirect_links bool
@ -3946,6 +4733,60 @@ func (m *FileMutation) ResetShares() {
m.removedshares = nil m.removedshares = nil
} }
// AddACLEntryIDs adds the "acl_entries" edge to the AclEntry entity by ids.
func (m *FileMutation) AddACLEntryIDs(ids ...int) {
if m.acl_entries == nil {
m.acl_entries = make(map[int]struct{})
}
for i := range ids {
m.acl_entries[ids[i]] = struct{}{}
}
}
// ClearACLEntries clears the "acl_entries" edge to the AclEntry entity.
func (m *FileMutation) ClearACLEntries() {
m.clearedacl_entries = true
}
// ACLEntriesCleared reports if the "acl_entries" edge to the AclEntry entity was cleared.
func (m *FileMutation) ACLEntriesCleared() bool {
return m.clearedacl_entries
}
// RemoveACLEntryIDs removes the "acl_entries" edge to the AclEntry entity by IDs.
func (m *FileMutation) RemoveACLEntryIDs(ids ...int) {
if m.removedacl_entries == nil {
m.removedacl_entries = make(map[int]struct{})
}
for i := range ids {
delete(m.acl_entries, ids[i])
m.removedacl_entries[ids[i]] = struct{}{}
}
}
// RemovedACLEntries returns the removed IDs of the "acl_entries" edge to the AclEntry entity.
func (m *FileMutation) RemovedACLEntriesIDs() (ids []int) {
for id := range m.removedacl_entries {
ids = append(ids, id)
}
return
}
// ACLEntriesIDs returns the "acl_entries" edge IDs in the mutation.
func (m *FileMutation) ACLEntriesIDs() (ids []int) {
for id := range m.acl_entries {
ids = append(ids, id)
}
return
}
// ResetACLEntries resets all changes to the "acl_entries" edge.
func (m *FileMutation) ResetACLEntries() {
m.acl_entries = nil
m.clearedacl_entries = false
m.removedacl_entries = nil
}
// AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by ids. // AddDirectLinkIDs adds the "direct_links" edge to the DirectLink entity by ids.
func (m *FileMutation) AddDirectLinkIDs(ids ...int) { func (m *FileMutation) AddDirectLinkIDs(ids ...int) {
if m.direct_links == nil { if m.direct_links == nil {
@ -4369,7 +5210,7 @@ func (m *FileMutation) ResetField(name string) error {
// AddedEdges returns all edge names that were set/added in this mutation. // AddedEdges returns all edge names that were set/added in this mutation.
func (m *FileMutation) AddedEdges() []string { func (m *FileMutation) AddedEdges() []string {
edges := make([]string, 0, 8) edges := make([]string, 0, 9)
if m.owner != nil { if m.owner != nil {
edges = append(edges, file.EdgeOwner) edges = append(edges, file.EdgeOwner)
} }
@ -4391,6 +5232,9 @@ func (m *FileMutation) AddedEdges() []string {
if m.shares != nil { if m.shares != nil {
edges = append(edges, file.EdgeShares) edges = append(edges, file.EdgeShares)
} }
if m.acl_entries != nil {
edges = append(edges, file.EdgeACLEntries)
}
if m.direct_links != nil { if m.direct_links != nil {
edges = append(edges, file.EdgeDirectLinks) edges = append(edges, file.EdgeDirectLinks)
} }
@ -4437,6 +5281,12 @@ func (m *FileMutation) AddedIDs(name string) []ent.Value {
ids = append(ids, id) ids = append(ids, id)
} }
return ids return ids
case file.EdgeACLEntries:
ids := make([]ent.Value, 0, len(m.acl_entries))
for id := range m.acl_entries {
ids = append(ids, id)
}
return ids
case file.EdgeDirectLinks: case file.EdgeDirectLinks:
ids := make([]ent.Value, 0, len(m.direct_links)) ids := make([]ent.Value, 0, len(m.direct_links))
for id := range m.direct_links { for id := range m.direct_links {
@ -4449,7 +5299,7 @@ func (m *FileMutation) AddedIDs(name string) []ent.Value {
// RemovedEdges returns all edge names that were removed in this mutation. // RemovedEdges returns all edge names that were removed in this mutation.
func (m *FileMutation) RemovedEdges() []string { func (m *FileMutation) RemovedEdges() []string {
edges := make([]string, 0, 8) edges := make([]string, 0, 9)
if m.removedchildren != nil { if m.removedchildren != nil {
edges = append(edges, file.EdgeChildren) edges = append(edges, file.EdgeChildren)
} }
@ -4462,6 +5312,9 @@ func (m *FileMutation) RemovedEdges() []string {
if m.removedshares != nil { if m.removedshares != nil {
edges = append(edges, file.EdgeShares) edges = append(edges, file.EdgeShares)
} }
if m.removedacl_entries != nil {
edges = append(edges, file.EdgeACLEntries)
}
if m.removeddirect_links != nil { if m.removeddirect_links != nil {
edges = append(edges, file.EdgeDirectLinks) edges = append(edges, file.EdgeDirectLinks)
} }
@ -4496,6 +5349,12 @@ func (m *FileMutation) RemovedIDs(name string) []ent.Value {
ids = append(ids, id) ids = append(ids, id)
} }
return ids return ids
case file.EdgeACLEntries:
ids := make([]ent.Value, 0, len(m.removedacl_entries))
for id := range m.removedacl_entries {
ids = append(ids, id)
}
return ids
case file.EdgeDirectLinks: case file.EdgeDirectLinks:
ids := make([]ent.Value, 0, len(m.removeddirect_links)) ids := make([]ent.Value, 0, len(m.removeddirect_links))
for id := range m.removeddirect_links { for id := range m.removeddirect_links {
@ -4508,7 +5367,7 @@ func (m *FileMutation) RemovedIDs(name string) []ent.Value {
// ClearedEdges returns all edge names that were cleared in this mutation. // ClearedEdges returns all edge names that were cleared in this mutation.
func (m *FileMutation) ClearedEdges() []string { func (m *FileMutation) ClearedEdges() []string {
edges := make([]string, 0, 8) edges := make([]string, 0, 9)
if m.clearedowner { if m.clearedowner {
edges = append(edges, file.EdgeOwner) edges = append(edges, file.EdgeOwner)
} }
@ -4530,6 +5389,9 @@ func (m *FileMutation) ClearedEdges() []string {
if m.clearedshares { if m.clearedshares {
edges = append(edges, file.EdgeShares) edges = append(edges, file.EdgeShares)
} }
if m.clearedacl_entries {
edges = append(edges, file.EdgeACLEntries)
}
if m.cleareddirect_links { if m.cleareddirect_links {
edges = append(edges, file.EdgeDirectLinks) edges = append(edges, file.EdgeDirectLinks)
} }
@ -4554,6 +5416,8 @@ func (m *FileMutation) EdgeCleared(name string) bool {
return m.clearedentities return m.clearedentities
case file.EdgeShares: case file.EdgeShares:
return m.clearedshares return m.clearedshares
case file.EdgeACLEntries:
return m.clearedacl_entries
case file.EdgeDirectLinks: case file.EdgeDirectLinks:
return m.cleareddirect_links return m.cleareddirect_links
} }
@ -4602,6 +5466,9 @@ func (m *FileMutation) ResetEdge(name string) error {
case file.EdgeShares: case file.EdgeShares:
m.ResetShares() m.ResetShares()
return nil return nil
case file.EdgeACLEntries:
m.ResetACLEntries()
return nil
case file.EdgeDirectLinks: case file.EdgeDirectLinks:
m.ResetDirectLinks() m.ResetDirectLinks()
return nil return nil

@ -4,6 +4,12 @@ package ent
// SetUpdatedAt sets the "updated_at" field. // SetUpdatedAt sets the "updated_at" field.
func (m *AclEntryMutation) SetRawID(t int) {
m.id = &t
}
// SetUpdatedAt sets the "updated_at" field.
func (m *DavAccountMutation) SetRawID(t int) { func (m *DavAccountMutation) SetRawID(t int) {
m.id = &t m.id = &t
} }

@ -6,6 +6,9 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
) )
// AclEntry is the predicate function for aclentry builders.
type AclEntry func(*sql.Selector)
// DavAccount is the predicate function for davaccount builders. // DavAccount is the predicate function for davaccount builders.
type DavAccount func(*sql.Selector) type DavAccount func(*sql.Selector)

@ -5,6 +5,7 @@ package runtime
import ( import (
"time" "time"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/davaccount"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
@ -30,6 +31,25 @@ import (
// (default values, validators, hooks and policies) and stitches it // (default values, validators, hooks and policies) and stitches it
// to their package variables. // to their package variables.
func init() { func init() {
aclentryMixin := schema.AclEntry{}.Mixin()
aclentryMixinHooks0 := aclentryMixin[0].Hooks()
aclentry.Hooks[0] = aclentryMixinHooks0[0]
aclentryMixinInters0 := aclentryMixin[0].Interceptors()
aclentry.Interceptors[0] = aclentryMixinInters0[0]
aclentryMixinFields0 := aclentryMixin[0].Fields()
_ = aclentryMixinFields0
aclentryFields := schema.AclEntry{}.Fields()
_ = aclentryFields
// aclentryDescCreatedAt is the schema descriptor for created_at field.
aclentryDescCreatedAt := aclentryMixinFields0[0].Descriptor()
// aclentry.DefaultCreatedAt holds the default value on creation for the created_at field.
aclentry.DefaultCreatedAt = aclentryDescCreatedAt.Default.(func() time.Time)
// aclentryDescUpdatedAt is the schema descriptor for updated_at field.
aclentryDescUpdatedAt := aclentryMixinFields0[1].Descriptor()
// aclentry.DefaultUpdatedAt holds the default value on creation for the updated_at field.
aclentry.DefaultUpdatedAt = aclentryDescUpdatedAt.Default.(func() time.Time)
// aclentry.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
aclentry.UpdateDefaultUpdatedAt = aclentryDescUpdatedAt.UpdateDefault.(func() time.Time)
davaccountMixin := schema.DavAccount{}.Mixin() davaccountMixin := schema.DavAccount{}.Mixin()
davaccountMixinHooks0 := davaccountMixin[0].Hooks() davaccountMixinHooks0 := davaccountMixin[0].Hooks()
davaccount.Hooks[0] = davaccountMixinHooks0[0] davaccount.Hooks[0] = davaccountMixinHooks0[0]

@ -0,0 +1,54 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
)
// AclEntry holds the schema definition for per-file access control entries.
// Each row grants a capability bitmask (read/create/update/delete) on a file
// or directory to a subject: a specific user, a user group, anonymous
// visitors, or every other authenticated user.
type AclEntry struct {
ent.Schema
}
// Fields of the AclEntry.
func (AclEntry) Fields() []ent.Field {
return []ent.Field{
field.Int("file_id"),
field.Enum("subject_type").
Values("user", "group", "anonymous", "everyone"),
field.Int("subject_id").
Optional(),
field.Bytes("permissions").GoType(&boolset.BooleanSet{}),
}
}
// Edges of the AclEntry.
func (AclEntry) Edges() []ent.Edge {
return []ent.Edge{
edge.From("file", File.Type).
Ref("acl_entries").
Field("file_id").
Unique().
Required(),
}
}
// Indexes of the AclEntry.
func (AclEntry) Indexes() []ent.Index {
return []ent.Index{
index.Fields("file_id", "subject_type", "subject_id").
Unique(),
}
}
func (AclEntry) Mixin() []ent.Mixin {
return []ent.Mixin{
CommonMixin{},
}
}

@ -68,6 +68,7 @@ func (File) Edges() []ent.Edge {
edge.To("metadata", Metadata.Type), edge.To("metadata", Metadata.Type),
edge.To("entities", Entity.Type), edge.To("entities", Entity.Type),
edge.To("shares", Share.Type), edge.To("shares", Share.Type),
edge.To("acl_entries", AclEntry.Type),
edge.To("direct_links", DirectLink.Type), edge.To("direct_links", DirectLink.Type),
} }
} }

@ -14,6 +14,8 @@ import (
// Tx is a transactional client that is created by calling Client.Tx(). // Tx is a transactional client that is created by calling Client.Tx().
type Tx struct { type Tx struct {
config config
// AclEntry is the client for interacting with the AclEntry builders.
AclEntry *AclEntryClient
// DavAccount is the client for interacting with the DavAccount builders. // DavAccount is the client for interacting with the DavAccount builders.
DavAccount *DavAccountClient DavAccount *DavAccountClient
// DirectLink is the client for interacting with the DirectLink builders. // DirectLink is the client for interacting with the DirectLink builders.
@ -179,6 +181,7 @@ func (tx *Tx) Client() *Client {
} }
func (tx *Tx) init() { func (tx *Tx) init() {
tx.AclEntry = NewAclEntryClient(tx.config)
tx.DavAccount = NewDavAccountClient(tx.config) tx.DavAccount = NewDavAccountClient(tx.config)
tx.DirectLink = NewDirectLinkClient(tx.config) tx.DirectLink = NewDirectLinkClient(tx.config)
tx.Entity = NewEntityClient(tx.config) tx.Entity = NewEntityClient(tx.config)
@ -205,7 +208,7 @@ func (tx *Tx) init() {
// of them in order to commit or rollback the transaction. // of them in order to commit or rollback the transaction.
// //
// If a closed transaction is embedded in one of the generated entities, and the entity // If a closed transaction is embedded in one of the generated entities, and the entity
// applies a query, for example: DavAccount.QueryXXX(), the query will be executed // applies a query, for example: AclEntry.QueryXXX(), the query will be executed
// through the driver which created this transaction. // through the driver which created this transaction.
// //
// Note that txDriver is not goroutine safe. // Note that txDriver is not goroutine safe.

@ -179,6 +179,15 @@
"typeToSearch": "Enter name or email...", "typeToSearch": "Enter name or email...",
"searchProperty": "Search files with the same property", "searchProperty": "Search files with the same property",
"permissions": "Permissions", "permissions": "Permissions",
"aclDes": "Granular permissions for \"{{name}}\". Listed entries override the share link's default permissions for matching visitors.",
"aclSubject": "Subject",
"aclAnonymous": "Anonymous visitors",
"aclEveryone": "Everyone else",
"aclGroup": "group",
"aclPerm_read": "Read",
"aclPerm_create": "Create",
"aclPerm_update": "Update",
"aclPerm_delete": "Delete",
"quality": "Quality", "quality": "Quality",
"audioTrack": "Audio", "audioTrack": "Audio",
"auto": "Auto", "auto": "Auto",

@ -1345,6 +1345,8 @@
"allNodes": "All nodes", "allNodes": "All nodes",
"esclateAnonymity": "Escalate anonymity", "esclateAnonymity": "Escalate anonymity",
"esclateAnonymityDes": "When enabled, users can assign higher permissions for anonymous users (write/delete/create). When disabled, users can only assign read-only permission for anonymous users. Changing this setting will not affect existing sharing links or files.", "esclateAnonymityDes": "When enabled, users can assign higher permissions for anonymous users (write/delete/create). When disabled, users can only assign read-only permission for anonymous users. Changing this setting will not affect existing sharing links or files.",
"setExplicitUser": "Set file permissions",
"setExplicitUserDes": "Members can set granular read/create/update/delete permissions on files and folders they own, for specific users, groups, anonymous visitors, or everyone else.",
"defaultPinned": "Default pinned shares", "defaultPinned": "Default pinned shares",
"defaultPinnedDes": "Users of this group will always see these shared links pinned at the top of their sidebar. Users cannot unpin them. Search shared links by their numeric ID, which can be found in the leftmost column of the <0>share list</0>.", "defaultPinnedDes": "Users of this group will always see these shared links pinned at the top of their sidebar. Users cannot unpin them. Search shared links by their numeric ID, which can be found in the leftmost column of the <0>share list</0>.",
"pinnedShare": "Share", "pinnedShare": "Share",

@ -179,6 +179,15 @@
"typeToSearch": "输入昵称或邮箱...", "typeToSearch": "输入昵称或邮箱...",
"searchProperty": "搜索相同属性的文件", "searchProperty": "搜索相同属性的文件",
"permissions": "权限", "permissions": "权限",
"aclDes": "\"{{name}}\" 的细粒度权限。列出的条目将覆盖分享链接默认权限,适用于匹配的访客。",
"aclSubject": "对象",
"aclAnonymous": "匿名访客",
"aclEveryone": "其他所有人",
"aclGroup": "用户组",
"aclPerm_read": "读取",
"aclPerm_create": "创建",
"aclPerm_update": "更新",
"aclPerm_delete": "删除",
"quality": "清晰度", "quality": "清晰度",
"audioTrack": "音轨", "audioTrack": "音轨",
"auto": "自动", "auto": "自动",

@ -1345,6 +1345,8 @@
"allNodes": "所有节点", "allNodes": "所有节点",
"esclateAnonymity": "提升匿名用户权限", "esclateAnonymity": "提升匿名用户权限",
"esclateAnonymityDes": "开启后,用户可以为匿名用户设置更高权限 (修改/创建/删除);关闭后,用户最高只能赋予匿名用户只读权限。更改此设置不会影响已设置的分享链接或文件。", "esclateAnonymityDes": "开启后,用户可以为匿名用户设置更高权限 (修改/创建/删除);关闭后,用户最高只能赋予匿名用户只读权限。更改此设置不会影响已设置的分享链接或文件。",
"setExplicitUser": "设置文件权限",
"setExplicitUserDes": "开启后,用户组成员可以为其拥有的文件和文件夹设置细粒度权限(读取/创建/更新/删除),对象可为指定用户、用户组、匿名访客或所有其他人。",
"defaultPinned": "默认固定分享", "defaultPinned": "默认固定分享",
"defaultPinnedDes": "此用户组的用户登录后,侧边栏会始终在最顶部展示这些分享快捷方式,用户无法取消固定。请通过数字 ID 搜索分享链接,你可在 <0>分享列表</0> 最左侧看到数字 ID。", "defaultPinnedDes": "此用户组的用户登录后,侧边栏会始终在最顶部展示这些分享快捷方式,用户无法取消固定。请通过数字 ID 搜索分享链接,你可在 <0>分享列表</0> 最左侧看到数字 ID。",
"pinnedShare": "分享", "pinnedShare": "分享",

@ -44,6 +44,9 @@ import {
User as UserEnt, User as UserEnt,
} from "./dashboard.ts"; } from "./dashboard.ts";
import { import {
AclEntry,
AclSubject,
AclUpsertService,
ArchiveListFilesResponse, ArchiveListFilesResponse,
ArchiveListFilesService, ArchiveListFilesService,
CreateFileService, CreateFileService,
@ -731,6 +734,75 @@ export function getFileEntityUrl(req: FileURLService): ThunkResponse<FileURLResp
}; };
} }
export function getAclEntries(uri: string): ThunkResponse<AclEntry[]> {
return async (dispatch, _getState) => {
return await dispatch(
send(
"/file/acl",
{
method: "GET",
params: { uri },
},
{
...defaultOpts,
},
),
);
};
}
export function upsertAclEntry(req: AclUpsertService): ThunkResponse<AclEntry> {
return async (dispatch, _getState) => {
return await dispatch(
send(
"/file/acl",
{
method: "PUT",
data: req,
},
{
...defaultOpts,
},
),
);
};
}
export function deleteAclEntry(uri: string, id: number): ThunkResponse<void> {
return async (dispatch, _getState) => {
return await dispatch(
send(
"/file/acl",
{
method: "DELETE",
params: { uri, id },
},
{
...defaultOpts,
},
),
);
};
}
export function searchAclSubjects(keyword: string): ThunkResponse<AclSubject[]> {
return async (dispatch, _getState) => {
return await dispatch(
send(
"/file/acl/subjects",
{
method: "GET",
params: { keyword },
},
{
...defaultOpts,
bypassSnackbar: () => true,
},
),
);
};
}
export function getFileInfo(req: GetFileInfoService, skipError = false): ThunkResponse<FileResponse> { export function getFileInfo(req: GetFileInfoService, skipError = false): ThunkResponse<FileResponse> {
return async (dispatch, _getState) => { return async (dispatch, _getState) => {
return await dispatch( return await dispatch(

@ -287,6 +287,31 @@ export const NavigatorCapability = {
enter_folder: 23, enter_folder: 23,
}; };
export type AclPermissionKey = "read" | "create" | "update" | "delete";
export type AclSubjectType = "user" | "group" | "anonymous" | "everyone";
export interface AclEntry {
id: number;
subject_type: AclSubjectType;
subject_id: number;
subject_name?: string;
permissions: AclPermissionKey[];
}
export interface AclSubject {
type: "user" | "group";
id: number;
name: string;
}
export interface AclUpsertService {
uri: string;
subject_type: AclSubjectType;
subject_id?: number;
permissions: AclPermissionKey[];
}
export interface PinFileService { export interface PinFileService {
uri: string; uri: string;
name?: string; name?: string;

@ -96,6 +96,7 @@ export const GroupPermission = {
remote_download: 9, remote_download: 9,
redirected_source: 11, redirected_source: 11,
advance_delete: 12, advance_delete: 12,
set_explicit_user: 15,
unique_direct_link: 17, unique_direct_link: 17,
webdav_read_only: 18, webdav_read_only: 18,
admin_users: 19, admin_users: 19,

@ -37,6 +37,18 @@ const ShareSection = () => {
[setGroup], [setGroup],
); );
const onSetExplicitUserChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setGroup((p: GroupEnt) => ({
...p,
permissions: new Boolset(p.permissions)
.set(GroupPermission.set_explicit_user, e.target.checked)
.toString(),
}));
},
[setGroup],
);
return ( return (
<SettingSection> <SettingSection>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
@ -84,14 +96,19 @@ const ShareSection = () => {
<SettingForm lgWidth={5}> <SettingForm lgWidth={5}>
<FormControl fullWidth> <FormControl fullWidth>
<FormControlLabel <FormControlLabel
control={<Switch checked={false} />} control={
<Switch
checked={permission.enabled(GroupPermission.set_explicit_user)}
onChange={onSetExplicitUserChange}
/>
}
label={ label={
<Box sx={{ display: "flex", alignItems: "center" }}> <Box sx={{ display: "flex", alignItems: "center" }}>
{t("group.esclateAnonymity")} {t("group.setExplicitUser")}
</Box> </Box>
} }
/> />
<NoMarginHelperText>{t("group.esclateAnonymityDes")}</NoMarginHelperText> <NoMarginHelperText>{t("group.setExplicitUserDes")}</NoMarginHelperText>
</FormControl> </FormControl>
</SettingForm> </SettingForm>
)} )}

@ -3,6 +3,7 @@ import { useCallback, useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; import { closeContextMenu } from "../../../redux/fileManagerSlice.ts";
import { import {
setAclDialog,
setCreateArchiveDialog, setCreateArchiveDialog,
setDirectLinkManagementDialog, setDirectLinkManagementDialog,
setManageShareDialog, setManageShareDialog,
@ -18,6 +19,7 @@ import CloudArrowIUp from "../../Icons/CloudArrowIUp.tsx";
import HistoryOutlined from "../../Icons/HistoryOutlined.tsx"; import HistoryOutlined from "../../Icons/HistoryOutlined.tsx";
import ImageArrowCounterclockwise from "../../Icons/ImageAarowCounterclockwise.tsx"; import ImageArrowCounterclockwise from "../../Icons/ImageAarowCounterclockwise.tsx";
import LinkSetting from "../../Icons/LinkSetting.tsx"; import LinkSetting from "../../Icons/LinkSetting.tsx";
import PersonLock from "../../Icons/PersonLock.tsx";
import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx"; import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx";
import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx"; import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx";
@ -76,6 +78,23 @@ const MoreMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
<ListItemText>{t("application:fileManager.manageShares")}</ListItemText> <ListItemText>{t("application:fileManager.manageShares")}</ListItemText>
</CascadingMenuItem> </CascadingMenuItem>
)} )}
{displayOpt.showAcl && (
<CascadingMenuItem
onClick={onClick(() =>
dispatch(
setAclDialog({
open: true,
file: targets[0],
}),
),
)}
>
<ListItemIcon>
<PersonLock fontSize="small" />
</ListItemIcon>
<ListItemText>{t("application:fileManager.permissions")}</ListItemText>
</CascadingMenuItem>
)}
{displayOpt.showDirectLinkManagement && ( {displayOpt.showDirectLinkManagement && (
<CascadingMenuItem <CascadingMenuItem
onClick={onClick(() => onClick={onClick(() =>

@ -100,6 +100,7 @@ export interface DisplayOption {
showVersionControl?: boolean; showVersionControl?: boolean;
showDirectLinkManagement?: boolean; showDirectLinkManagement?: boolean;
showManageShares?: boolean; showManageShares?: boolean;
showAcl?: boolean;
showCreateArchive?: boolean; showCreateArchive?: boolean;
showResetThumb?: boolean; showResetThumb?: boolean;
showDirPolicy?: boolean; showDirPolicy?: boolean;
@ -351,9 +352,16 @@ export const getActionOpt = (
display.orCapability && display.orCapability &&
display.orCapability.enabled(NavigatorCapability.update_metadata); display.orCapability.enabled(NavigatorCapability.update_metadata);
display.showAcl =
targets.length == 1 &&
!!targets[0].owned &&
!!currentUser &&
groupBs.enabled(GroupPermission.set_explicit_user);
display.showMore = display.showMore =
display.showVersionControl || display.showVersionControl ||
display.showManageShares || display.showManageShares ||
display.showAcl ||
display.showCreateArchive || display.showCreateArchive ||
display.showDirectLinkManagement || display.showDirectLinkManagement ||
display.showResetThumb; display.showResetThumb;

@ -0,0 +1,300 @@
import {
Box,
Checkbox,
DialogContent,
IconButton,
Skeleton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from "@mui/material";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { deleteAclEntry, getAclEntries, searchAclSubjects, upsertAclEntry } from "../../../../api/api.ts";
import { AclEntry, AclPermissionKey, AclSubject, AclSubjectType } from "../../../../api/explorer.ts";
import { closeAclDialog } from "../../../../redux/globalStateSlice.ts";
import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts";
import AutoHeight from "../../../Common/AutoHeight.tsx";
import {
DenseAutocomplete,
DenseFilledTextField,
NoWrapTableCell,
StyledTableContainerPaper,
} from "../../../Common/StyledComponents.tsx";
import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx";
import Dismiss from "../../../Icons/Dismiss.tsx";
import Globe from "../../../Icons/Globe.tsx";
import PeopleTeam from "../../../Icons/PeopleTeam.tsx";
import PersonOutlined from "../../../Icons/PersonOutlined.tsx";
import PersonStar from "../../../Icons/PersonStar.tsx";
const permissionOrder: AclPermissionKey[] = ["read", "create", "update", "delete"];
interface PendingSubject {
type: AclSubjectType;
id: number;
label: string;
}
const AclDialog = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const [entries, setEntries] = useState<AclEntry[] | undefined>(undefined);
const [pending, setPending] = useState<number[]>([]);
const [subjectOptions, setSubjectOptions] = useState<PendingSubject[]>([]);
const [keyword, setKeyword] = useState("");
const open = useAppSelector((state) => state.globalState.aclDialogOpen);
const target = useAppSelector((state) => state.globalState.aclDialogFile);
const uri = target?.path;
const generalSubject = useCallback(
(type: "anonymous" | "everyone"): PendingSubject => ({
type,
id: 0,
label: t(`fileManager.acl${type === "anonymous" ? "Anonymous" : "Everyone"}`),
}),
[t],
);
const subjectLabel = useCallback(
(e: AclEntry) => {
if (e.subject_type === "anonymous" || e.subject_type === "everyone") {
return t(`fileManager.acl${e.subject_type === "anonymous" ? "Anonymous" : "Everyone"}`);
}
return e.subject_name || `#${e.subject_id}`;
},
[t],
);
useEffect(() => {
if (open && uri) {
setEntries(undefined);
dispatch(getAclEntries(uri)).then((res) => setEntries(res));
}
}, [open, uri, dispatch]);
// Search selectable subjects; anonymous/everyone are always offered when not
// already present on the file.
useEffect(() => {
if (!open) {
return;
}
const handle = setTimeout(() => {
const fixed: PendingSubject[] = ["anonymous", "everyone"]
.filter((ty) => !entries?.some((e) => e.subject_type === ty))
.map((ty) => generalSubject(ty as "anonymous" | "everyone"))
.filter((s) => keyword == "" || s.label.toLowerCase().includes(keyword.toLowerCase()));
dispatch(searchAclSubjects(keyword))
.then((res: AclSubject[]) => {
const dynamic = (res || [])
.filter((s) => !entries?.some((e) => e.subject_type === s.type && e.subject_id === s.id))
.map((s) => ({
type: s.type as AclSubjectType,
id: s.id,
label: s.type === "user" ? s.name : `${s.name} (${t("fileManager.aclGroup")})`,
}));
setSubjectOptions([...dynamic, ...fixed]);
})
.catch(() => setSubjectOptions(fixed));
}, 300);
return () => clearTimeout(handle);
}, [keyword, open, entries, dispatch, generalSubject, t]);
const togglePermission = useCallback(
(entry: AclEntry, perm: AclPermissionKey) => {
if (!uri) {
return;
}
const next = entry.permissions.includes(perm)
? entry.permissions.filter((p) => p !== perm)
: [...entry.permissions, perm];
setPending((p) => [...p, entry.id]);
dispatch(
upsertAclEntry({
uri,
subject_type: entry.subject_type,
subject_id: entry.subject_id,
permissions: next,
}),
)
.then((res) => {
setEntries((prev) =>
prev?.map((e) => (e.id === entry.id ? { ...e, permissions: res.permissions } : e)),
);
})
.finally(() => setPending((p) => p.filter((id) => id !== entry.id)));
},
[uri, dispatch],
);
const removeEntry = useCallback(
(entry: AclEntry) => {
if (!uri) {
return;
}
setPending((p) => [...p, entry.id]);
dispatch(deleteAclEntry(uri, entry.id))
.then(() => setEntries((prev) => prev?.filter((e) => e.id !== entry.id)))
.finally(() => setPending((p) => p.filter((id) => id !== entry.id)));
},
[uri, dispatch],
);
const addSubject = useCallback(
(subject: PendingSubject | null) => {
if (!subject || !uri) {
return;
}
setPending((p) => [...p, -1]);
dispatch(
upsertAclEntry({
uri,
subject_type: subject.type,
subject_id: subject.id,
permissions: ["read"],
}),
)
.then((res) => {
setEntries((prev) => [...(prev ?? []), { ...res, subject_name: subject.label }]);
setKeyword("");
})
.finally(() => setPending((p) => p.filter((id) => id !== -1)));
},
[uri, dispatch],
);
const subjectIcon = useMemo(
() => ({
user: <PersonOutlined fontSize="small" />,
group: <PeopleTeam fontSize="small" />,
anonymous: <PersonStar fontSize="small" />,
everyone: <Globe fontSize="small" />,
}),
[],
);
return (
<DraggableDialog
title={t("application:fileManager.permissions")}
loading={entries === undefined}
dialogProps={{
open: open ?? false,
onClose: () => dispatch(closeAclDialog()),
fullWidth: true,
maxWidth: "md",
}}
>
<DialogContent>
<AutoHeight>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{t("fileManager.aclDes", { name: target?.name })}
</Typography>
<DenseAutocomplete
options={subjectOptions}
inputValue={keyword}
onInputChange={(_, v) => setKeyword(v)}
getOptionLabel={(o) => (o as PendingSubject).label}
onChange={(_, v) => addSubject(v as PendingSubject | null)}
value={null}
blurOnSelect
sx={{ mb: 2 }}
renderOption={(props, option) => {
const o = option as PendingSubject;
return (
<li {...props} key={`${o.type}:${o.id}`}>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
{subjectIcon[o.type]}
{o.label}
</Box>
</li>
);
}}
renderInput={(params) => (
<DenseFilledTextField
{...params}
placeholder={t("application:fileManager.typeToSearch")}
variant="outlined"
size="small"
/>
)}
/>
<TableContainer component={StyledTableContainerPaper}>
<Table sx={{ width: "100%" }} size="small">
<TableHead>
<TableRow>
<NoWrapTableCell>{t("fileManager.aclSubject")}</NoWrapTableCell>
{permissionOrder.map((p) => (
<TableCell key={p} align="center" padding="checkbox">
{t(`fileManager.aclPerm_${p}`)}
</TableCell>
))}
<TableCell padding="checkbox" />
</TableRow>
</TableHead>
<TableBody>
{entries === undefined && (
<TableRow>
<NoWrapTableCell>
<Skeleton variant="text" width={140} />
</NoWrapTableCell>
{permissionOrder.map((p) => (
<TableCell key={p} align="center" padding="checkbox">
<Skeleton variant="circular" width={18} height={18} sx={{ mx: "auto" }} />
</TableCell>
))}
<TableCell padding="checkbox" />
</TableRow>
)}
{entries?.map((e) => (
<TableRow key={e.id} hover>
<NoWrapTableCell component="th" scope="row">
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
{subjectIcon[e.subject_type]}
{subjectLabel(e)}
</Box>
</NoWrapTableCell>
{permissionOrder.map((p) => (
<TableCell key={p} align="center" padding="checkbox">
<Checkbox
size="small"
checked={e.permissions.includes(p)}
disabled={pending.includes(e.id)}
onChange={() => togglePermission(e, p)}
/>
</TableCell>
))}
<TableCell padding="checkbox" align="right">
<IconButton
size="small"
disabled={pending.includes(e.id)}
onClick={() => removeEntry(e)}
>
<Dismiss fontSize="small" />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{entries && entries.length === 0 && (
<Box sx={{ p: 1, width: "100%", textAlign: "center" }}>
<Typography variant="caption" color="text.secondary">
{t("application:setting.listEmpty")}
</Typography>
</Box>
)}
</TableContainer>
</AutoHeight>
</DialogContent>
</DraggableDialog>
);
};
export default AclDialog;

@ -10,6 +10,7 @@ import ShareDialog from "./Share/ShareDialog.tsx";
import VersionControl from "./VersionControl.tsx"; import VersionControl from "./VersionControl.tsx";
import ManageShares from "./Share/ManageShares.tsx"; import ManageShares from "./Share/ManageShares.tsx";
import StoragePolicy from "./StoragePolicy.tsx"; import StoragePolicy from "./StoragePolicy.tsx";
import AclDialog from "./Acl/AclDialog.tsx";
import StaleVersionConfirm from "./StaleVersionConfirm.tsx"; import StaleVersionConfirm from "./StaleVersionConfirm.tsx";
import SaveAs from "./SaveAs.tsx"; import SaveAs from "./SaveAs.tsx";
import Photopea from "../../Viewers/Photopea/Photopea.tsx"; import Photopea from "../../Viewers/Photopea/Photopea.tsx";
@ -61,6 +62,7 @@ const Dialogs = () => {
<ShareDialog /> <ShareDialog />
<VersionControl /> <VersionControl />
<ManageShares /> <ManageShares />
<AclDialog />
<StaleVersionConfirm /> <StaleVersionConfirm />
<SaveAs /> <SaveAs />
<Photopea /> <Photopea />

@ -156,6 +156,10 @@ export interface GlobalStateSlice {
manageShareDialogOpen?: boolean; manageShareDialogOpen?: boolean;
manageShareDialogFile?: FileResponse; manageShareDialogFile?: FileResponse;
// File permission (ACL) dialog
aclDialogOpen?: boolean;
aclDialogFile?: FileResponse;
// Stale version action dialog // Stale version action dialog
staleVersionDialogOpen?: boolean; staleVersionDialogOpen?: boolean;
staleVersionUri?: string; staleVersionUri?: string;
@ -426,6 +430,7 @@ export const globalStateSlice = createSlice({
state.shareLinkDialogOpen = state.shareLinkDialogOpen ? false : undefined; state.shareLinkDialogOpen = state.shareLinkDialogOpen ? false : undefined;
state.versionControlDialogOpen = state.versionControlDialogOpen ? false : undefined; state.versionControlDialogOpen = state.versionControlDialogOpen ? false : undefined;
state.manageShareDialogOpen = state.manageShareDialogOpen ? false : undefined; state.manageShareDialogOpen = state.manageShareDialogOpen ? false : undefined;
state.aclDialogOpen = state.aclDialogOpen ? false : undefined;
state.createNewDialogOpen = state.createNewDialogOpen ? false : undefined; state.createNewDialogOpen = state.createNewDialogOpen ? false : undefined;
state.selectOptionDialogOpen = state.selectOptionDialogOpen ? false : undefined; state.selectOptionDialogOpen = state.selectOptionDialogOpen ? false : undefined;
state.batchDownloadLogDialogOpen = state.batchDownloadLogDialogOpen ? false : undefined; state.batchDownloadLogDialogOpen = state.batchDownloadLogDialogOpen ? false : undefined;
@ -801,6 +806,13 @@ export const globalStateSlice = createSlice({
closeManageShareDialog: (state) => { closeManageShareDialog: (state) => {
state.manageShareDialogOpen = false; state.manageShareDialogOpen = false;
}, },
setAclDialog: (state, action: PayloadAction<{ open: boolean; file: FileResponse }>) => {
state.aclDialogOpen = action.payload.open;
state.aclDialogFile = action.payload.file;
},
closeAclDialog: (state) => {
state.aclDialogOpen = false;
},
setImageViewer: (state, action: PayloadAction<ImageViewerState>) => { setImageViewer: (state, action: PayloadAction<ImageViewerState>) => {
state.imageViewer = action.payload; state.imageViewer = action.payload;
}, },
@ -930,6 +942,8 @@ export const {
closeImageViewer, closeImageViewer,
setManageShareDialog, setManageShareDialog,
closeManageShareDialog, closeManageShareDialog,
setAclDialog,
closeAclDialog,
setVersionControlDialog, setVersionControlDialog,
closeVersionControlDialog, closeVersionControlDialog,
closeSidebar, closeSidebar,

@ -0,0 +1,156 @@
package inventory
import (
"context"
"fmt"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
)
type (
AclClient interface {
TxOperator
// List returns all ACL entries for a file, ordered by subject type.
List(ctx context.Context, fileID int) ([]*ent.AclEntry, error)
// Upsert creates or updates the entry for (file, subject).
Upsert(ctx context.Context, params *UpsertAclEntryParams) (*ent.AclEntry, error)
// Delete removes an ACL entry scoped to the given file.
Delete(ctx context.Context, fileID, id int) error
// EffectivePermissions unions the permission bits of all entries on
// fileID matching the acting user. Anonymous users (id 0) only match
// the anonymous tier; authenticated users match their explicit user
// and group rows plus the everyone tier. Returns nil when no entry
// matches — callers then fall back to share-level defaults.
EffectivePermissions(ctx context.Context, fileID int, user *ent.User) (*boolset.BooleanSet, error)
}
UpsertAclEntryParams struct {
FileID int
SubjectType aclentry.SubjectType
SubjectID int
Permissions *boolset.BooleanSet
}
)
func NewAclClient(client *ent.Client, dbType conf.DBType) AclClient {
return &aclClient{
client: client,
maxSQlParam: sqlParamLimit(dbType),
}
}
type aclClient struct {
maxSQlParam int
client *ent.Client
}
func (c *aclClient) SetClient(newClient *ent.Client) TxOperator {
return &aclClient{client: newClient, maxSQlParam: c.maxSQlParam}
}
func (c *aclClient) GetClient() *ent.Client {
return c.client
}
func (c *aclClient) List(ctx context.Context, fileID int) ([]*ent.AclEntry, error) {
return c.client.AclEntry.Query().
Where(aclentry.FileID(fileID)).
Order(aclentry.BySubjectType(), aclentry.BySubjectID()).
All(ctx)
}
func (c *aclClient) Upsert(ctx context.Context, params *UpsertAclEntryParams) (*ent.AclEntry, error) {
if params.Permissions == nil {
params.Permissions = &boolset.BooleanSet{}
}
id, err := c.client.AclEntry.Create().
SetFileID(params.FileID).
SetSubjectType(params.SubjectType).
SetSubjectID(params.SubjectID).
SetPermissions(params.Permissions).
OnConflictColumns(aclentry.FieldFileID, aclentry.FieldSubjectType, aclentry.FieldSubjectID).
UpdateNewValues().
ID(ctx)
if err != nil {
return nil, fmt.Errorf("failed to upsert ACL entry: %w", err)
}
return c.client.AclEntry.Get(ctx, id)
}
func (c *aclClient) Delete(ctx context.Context, fileID, id int) error {
affected, err := c.client.AclEntry.Delete().
Where(aclentry.ID(id), aclentry.FileID(fileID)).
Exec(ctx)
if err != nil {
return fmt.Errorf("failed to delete ACL entry: %w", err)
}
if affected == 0 {
return &ent.NotFoundError{}
}
return nil
}
func (c *aclClient) EffectivePermissions(ctx context.Context, fileID int, user *ent.User) (*boolset.BooleanSet, error) {
entries, err := c.client.AclEntry.Query().
Where(aclentry.FileID(fileID)).
All(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query ACL entries: %w", err)
}
if len(entries) == 0 {
return nil, nil
}
if IsAnonymousUser(user) {
for _, e := range entries {
if e.SubjectType == aclentry.SubjectTypeAnonymous {
if e.Permissions == nil {
return &boolset.BooleanSet{}, nil
}
return e.Permissions, nil
}
}
return nil, nil
}
groupID := 0
if group := user.Edges.Group; group != nil {
groupID = group.ID
}
res := &boolset.BooleanSet{}
matched := false
for _, e := range entries {
hit := false
switch e.SubjectType {
case aclentry.SubjectTypeEveryone:
hit = true
case aclentry.SubjectTypeUser:
hit = e.SubjectID == user.ID
case aclentry.SubjectTypeGroup:
hit = groupID > 0 && e.SubjectID == groupID
}
if !hit {
continue
}
matched = true
if e.Permissions == nil {
continue
}
for i := 0; i <= int(types.AclPermDelete); i++ {
if e.Permissions.Enabled(i) {
boolset.Set(i, true, res)
}
}
}
if !matched {
return nil, nil
}
return res, nil
}

@ -0,0 +1,154 @@
package inventory
import (
"context"
"testing"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/enttest"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
"github.com/stretchr/testify/require"
)
func aclFixture(t *testing.T, client *ent.Client) (*ent.User, *ent.Group, *ent.File) {
t.Helper()
ctx := context.Background()
group := client.Group.Create().SetName("g").SetPermissions(&boolset.BooleanSet{}).SaveX(ctx)
user := client.User.Create().SetEmail("u@example.com").SetNick("u").SetGroup(group).SaveX(ctx)
file := client.File.Create().SetName("dir").SetType(int(types.FileTypeFolder)).SetOwner(user).SaveX(ctx)
user.SetGroup(group)
return user, group, file
}
func aclEntry(t *testing.T, client *ent.Client, fileID int, st aclentry.SubjectType, sid int, perms ...types.AclPermission) *ent.AclEntry {
t.Helper()
bs := &boolset.BooleanSet{}
for _, p := range perms {
boolset.Set(int(p), true, bs)
}
return client.AclEntry.Create().
SetFileID(fileID).
SetSubjectType(st).
SetSubjectID(sid).
SetPermissions(bs).
SaveX(context.Background())
}
func TestAclUpsertAndList(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
t.Cleanup(func() { require.NoError(t, client.Close()) })
ctx := context.Background()
_, _, file := aclFixture(t, client)
c := NewAclClient(client, conf.SQLiteDB)
bs := &boolset.BooleanSet{}
boolset.Set(int(types.AclPermRead), true, bs)
e, err := c.Upsert(ctx, &UpsertAclEntryParams{
FileID: file.ID, SubjectType: aclentry.SubjectTypeUser, SubjectID: 7, Permissions: bs,
})
require.NoError(t, err)
require.NotZero(t, e.ID)
// Second upsert on same (file, subject) updates rather than duplicating.
boolset.Set(int(types.AclPermDelete), true, bs)
e2, err := c.Upsert(ctx, &UpsertAclEntryParams{
FileID: file.ID, SubjectType: aclentry.SubjectTypeUser, SubjectID: 7, Permissions: bs,
})
require.NoError(t, err)
require.Equal(t, e.ID, e2.ID)
entries, err := c.List(ctx, file.ID)
require.NoError(t, err)
require.Len(t, entries, 1)
require.True(t, entries[0].Permissions.Enabled(int(types.AclPermDelete)))
require.NoError(t, c.Delete(ctx, file.ID, e.ID))
entries, err = c.List(ctx, file.ID)
require.NoError(t, err)
require.Empty(t, entries)
// Delete of a missing row reports not found.
require.Error(t, c.Delete(ctx, file.ID, e.ID))
}
func TestAclEffectivePermissionsAnonymous(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
t.Cleanup(func() { require.NoError(t, client.Close()) })
ctx := context.Background()
_, _, file := aclFixture(t, client)
c := NewAclClient(client, conf.SQLiteDB)
anon := &ent.User{ID: 0}
// No rows at all -> nil (fall back to share defaults).
res, err := c.EffectivePermissions(ctx, file.ID, anon)
require.NoError(t, err)
require.Nil(t, res)
// An anonymous row applies to anonymous visitors; other tiers do not.
aclEntry(t, client, file.ID, aclentry.SubjectTypeEveryone, 0, types.AclPermRead)
res, err = c.EffectivePermissions(ctx, file.ID, anon)
require.NoError(t, err)
require.Nil(t, res)
aclEntry(t, client, file.ID, aclentry.SubjectTypeAnonymous, 0, types.AclPermCreate)
res, err = c.EffectivePermissions(ctx, file.ID, anon)
require.NoError(t, err)
require.NotNil(t, res)
require.True(t, res.Enabled(int(types.AclPermCreate)))
require.False(t, res.Enabled(int(types.AclPermRead)))
}
func TestAclEffectivePermissionsUnion(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
t.Cleanup(func() { require.NoError(t, client.Close()) })
ctx := context.Background()
user, group, file := aclFixture(t, client)
c := NewAclClient(client, conf.SQLiteDB)
aclEntry(t, client, file.ID, aclentry.SubjectTypeEveryone, 0, types.AclPermRead)
aclEntry(t, client, file.ID, aclentry.SubjectTypeGroup, group.ID, types.AclPermCreate)
aclEntry(t, client, file.ID, aclentry.SubjectTypeUser, user.ID, types.AclPermDelete)
// Unrelated rows must not leak in.
aclEntry(t, client, file.ID, aclentry.SubjectTypeUser, user.ID+99, types.AclPermUpdate)
aclEntry(t, client, file.ID, aclentry.SubjectTypeGroup, group.ID+99, types.AclPermUpdate)
res, err := c.EffectivePermissions(ctx, file.ID, user)
require.NoError(t, err)
require.NotNil(t, res)
require.True(t, res.Enabled(int(types.AclPermRead)))
require.True(t, res.Enabled(int(types.AclPermCreate)))
require.True(t, res.Enabled(int(types.AclPermDelete)))
require.False(t, res.Enabled(int(types.AclPermUpdate)))
}
func TestAclEffectivePermissionsNoMatchIsNil(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
t.Cleanup(func() { require.NoError(t, client.Close()) })
ctx := context.Background()
user, _, file := aclFixture(t, client)
c := NewAclClient(client, conf.SQLiteDB)
aclEntry(t, client, file.ID, aclentry.SubjectTypeUser, user.ID+99, types.AclPermRead)
res, err := c.EffectivePermissions(ctx, file.ID, user)
require.NoError(t, err)
require.Nil(t, res)
}
func TestAclEffectivePermissionsExplicitEmptyRevokes(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
t.Cleanup(func() { require.NoError(t, client.Close()) })
ctx := context.Background()
user, _, file := aclFixture(t, client)
c := NewAclClient(client, conf.SQLiteDB)
// An everyone row with zero bits = explicit revocation for all
// authenticated visitors; distinct from nil (no match).
aclEntry(t, client, file.ID, aclentry.SubjectTypeEveryone, 0)
res, err := c.EffectivePermissions(ctx, file.ID, user)
require.NoError(t, err)
require.NotNil(t, res)
require.False(t, res.Enabled(int(types.AclPermRead)))
}

@ -9,6 +9,7 @@ import (
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
"github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/ent/directlink" "github.com/cloudreve/Cloudreve/v4/ent/directlink"
"github.com/cloudreve/Cloudreve/v4/ent/entity" "github.com/cloudreve/Cloudreve/v4/ent/entity"
"github.com/cloudreve/Cloudreve/v4/ent/file" "github.com/cloudreve/Cloudreve/v4/ent/file"
@ -637,6 +638,10 @@ func (f *fileClient) Delete(ctx context.Context, files []*ent.File, options *typ
return nil, nil, fmt.Errorf("failed to delete direct links of files %v: %w", group, err) return nil, nil, fmt.Errorf("failed to delete direct links of files %v: %w", group, err)
} }
if _, err := f.client.AclEntry.Delete().Where(aclentry.FileIDIn(chunks[i]...)).Exec(hardDeleteCtx); err != nil {
return nil, nil, fmt.Errorf("failed to delete ACL entries of files %v: %w", group, err)
}
// 5. Delete files. // 5. Delete files.
if _, err := f.client.File.Delete().Where(group).Exec(hardDeleteCtx); err != nil { if _, err := f.client.File.Delete().Where(group).Exec(hardDeleteCtx); err != nil {
return nil, nil, fmt.Errorf("failed to delete files %v: %w", group, err) return nil, nil, fmt.Errorf("failed to delete files %v: %w", group, err)

@ -151,6 +151,7 @@ type (
FilePermission int FilePermission int
DavAccountOption int DavAccountOption int
NodeCapability int NodeCapability int
AclPermission int
NodeSetting struct { NodeSetting struct {
Provider DownloaderProvider `json:"provider,omitempty"` Provider DownloaderProvider `json:"provider,omitempty"`
@ -320,7 +321,9 @@ const (
GroupPermissionAdvanceDelete GroupPermissionAdvanceDelete
GroupPermission_CommunityPlaceholder3 GroupPermission_CommunityPlaceholder3
GroupPermission_CommunityPlaceholder4 GroupPermission_CommunityPlaceholder4
GroupPermissionSetExplicitUser_placeholder // GroupPermissionSetExplicitUser allows members to manage per-file ACL
// entries (Permissions dialog) on files they own.
GroupPermissionSetExplicitUser
GroupPermissionIgnoreFileOwnership // not used GroupPermissionIgnoreFileOwnership // not used
GroupPermissionUniqueRedirectDirectLink GroupPermissionUniqueRedirectDirectLink
// GroupPermissionWebDAVReadOnly restricts the group's WebDAV access to // GroupPermissionWebDAVReadOnly restricts the group's WebDAV access to
@ -343,6 +346,24 @@ const (
GroupPermissionAdminReports GroupPermissionAdminReports
) )
// AclPermission is a bit position in an ACL entry's permission bitmask.
// Read grants listing/download, Create grants uploads and new entries,
// Update grants rename/metadata/content changes, Delete grants removal.
const (
AclPermRead AclPermission = iota
AclPermCreate
AclPermUpdate
AclPermDelete
)
// AclPermissionList maps each ACL bit to its API-facing key.
var AclPermissionList = map[AclPermission]string{
AclPermRead: "read",
AclPermCreate: "create",
AclPermUpdate: "update",
AclPermDelete: "delete",
}
// DelegatedAdminPermissions lists every per-section admin permission bit. // DelegatedAdminPermissions lists every per-section admin permission bit.
// GroupPermissionIsAdmin implies all of them. // GroupPermissionIsAdmin implies all of them.
func DelegatedAdminPermissions() []GroupPermission { func DelegatedAdminPermissions() []GroupPermission {

@ -97,6 +97,7 @@ func (f *DBFS) writePermitted(file *File, capability NavigatorCapability) bool {
type DBFSDependencies struct { type DBFSDependencies struct {
FileClient inventory.FileClient FileClient inventory.FileClient
ShareClient inventory.ShareClient ShareClient inventory.ShareClient
AclClient inventory.AclClient
UserClient inventory.UserClient UserClient inventory.UserClient
StoragePolicyClient inventory.StoragePolicyClient StoragePolicyClient inventory.StoragePolicyClient
DirectLinkClient inventory.DirectLinkClient DirectLinkClient inventory.DirectLinkClient
@ -116,6 +117,7 @@ func NewDatabaseFS(u *ent.User, deps DBFSDependencies) fs.FileSystem {
navigators: make(map[string]Navigator), navigators: make(map[string]Navigator),
fileClient: deps.FileClient, fileClient: deps.FileClient,
shareClient: deps.ShareClient, shareClient: deps.ShareClient,
aclClient: deps.AclClient,
l: deps.Logger, l: deps.Logger,
ls: deps.LockSystem, ls: deps.LockSystem,
settingClient: deps.SettingProvider, settingClient: deps.SettingProvider,
@ -137,6 +139,7 @@ type DBFS struct {
userClient inventory.UserClient userClient inventory.UserClient
storagePolicyClient inventory.StoragePolicyClient storagePolicyClient inventory.StoragePolicyClient
shareClient inventory.ShareClient shareClient inventory.ShareClient
aclClient inventory.AclClient
directLinkClient inventory.DirectLinkClient directLinkClient inventory.DirectLinkClient
l logging.Logger l logging.Logger
ls lock.LockSystem ls lock.LockSystem
@ -930,7 +933,7 @@ func (f *DBFS) getNavigator(ctx context.Context, path *fs.URI, requiredCapabilit
case constants.FileSystemMy: case constants.FileSystemMy:
n = NewMyNavigator(f.user, f.fileClient, f.userClient, f.l, config, f.hasher) n = NewMyNavigator(f.user, f.fileClient, f.userClient, f.l, config, f.hasher)
case constants.FileSystemShare: case constants.FileSystemShare:
n = NewShareNavigator(f.user, f.fileClient, f.shareClient, f.l, config, f.hasher) n = NewShareNavigator(f.user, f.fileClient, f.shareClient, f.aclClient, f.l, config, f.hasher)
case constants.FileSystemTrash: case constants.FileSystemTrash:
n = NewTrashNavigator(f.user, f.fileClient, f.l, config, f.hasher) n = NewTrashNavigator(f.user, f.fileClient, f.l, config, f.hasher)
case constants.FileSystemSharedWithMe: case constants.FileSystemSharedWithMe:

@ -192,3 +192,77 @@ func TestShareNavigatorExecuteHookPreviewOnly(t *testing.T) {
require.Equal(t, 1, sc.calls) require.Equal(t, 1, sc.calls)
}) })
} }
func TestShareCapabilitiesAcl(t *testing.T) {
owner := &ent.User{ID: 1}
visitor := &ent.User{ID: 2}
enabled := func(bs *boolset.BooleanSet, c NavigatorCapability) bool {
return bs.Enabled(int(c))
}
aclBs := func(perms ...types.AclPermission) *boolset.BooleanSet {
bs := &boolset.BooleanSet{}
for _, p := range perms {
boolset.Set(int(p), true, bs)
}
return bs
}
t.Run("matched acl overrides share props", func(t *testing.T) {
n := &shareNavigator{
user: visitor,
owner: owner,
share: shareWithProps(&types.ShareProps{AllowEdit: true}),
aclCaps: aclBs(types.AclPermRead),
}
caps := n.shareCapabilities()
require.True(t, enabled(caps, NavigatorCapabilityDownloadFile))
require.False(t, enabled(caps, NavigatorCapabilityRenameFile))
require.False(t, enabled(caps, NavigatorCapabilityDeleteFile))
})
t.Run("explicit empty acl revokes fallback", func(t *testing.T) {
n := &shareNavigator{
user: visitor,
owner: owner,
share: shareWithProps(nil),
aclCaps: aclBs(),
}
caps := n.shareCapabilities()
require.False(t, enabled(caps, NavigatorCapabilityDownloadFile))
require.False(t, enabled(caps, NavigatorCapabilityListChildren))
})
t.Run("no match falls back to share props", func(t *testing.T) {
n := &shareNavigator{
user: visitor,
owner: owner,
share: shareWithProps(&types.ShareProps{AllowUpload: true}),
}
caps := n.shareCapabilities()
require.True(t, enabled(caps, NavigatorCapabilityUploadFile))
require.True(t, enabled(caps, NavigatorCapabilityDownloadFile))
})
t.Run("owner bypasses acl", func(t *testing.T) {
n := &shareNavigator{
user: owner,
owner: owner,
share: shareWithProps(nil),
aclCaps: aclBs(),
}
caps := n.shareCapabilities()
require.True(t, enabled(caps, NavigatorCapabilityDownloadFile))
})
t.Run("create update delete bits map", func(t *testing.T) {
caps := aclPermsToCapabilities(aclBs(types.AclPermCreate, types.AclPermUpdate, types.AclPermDelete))
require.True(t, enabled(caps, NavigatorCapabilityUploadFile))
require.True(t, enabled(caps, NavigatorCapabilityCreateFile))
require.True(t, enabled(caps, NavigatorCapabilityRenameFile))
require.True(t, enabled(caps, NavigatorCapabilityUpdateMetadata))
require.True(t, enabled(caps, NavigatorCapabilityDeleteFile))
require.True(t, enabled(caps, NavigatorCapabilitySoftDelete))
require.False(t, enabled(caps, NavigatorCapabilityDownloadFile))
})
}

@ -38,12 +38,13 @@ var shareNavigatorCapability = &boolset.BooleanSet{}
// NewShareNavigator creates a navigator for user's "shared" file system. // NewShareNavigator creates a navigator for user's "shared" file system.
func NewShareNavigator(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient, func NewShareNavigator(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient,
l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator { aclClient inventory.AclClient, l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator {
n := &shareNavigator{ n := &shareNavigator{
user: u, user: u,
l: l, l: l,
fileClient: fileClient, fileClient: fileClient,
shareClient: shareClient, shareClient: shareClient,
aclClient: aclClient,
config: config, config: config,
} }
n.baseNavigator = newBaseNavigator(fileClient, defaultFilter, u, hasher, config) n.baseNavigator = newBaseNavigator(fileClient, defaultFilter, u, hasher, config)
@ -56,6 +57,7 @@ type (
user *ent.User user *ent.User
fileClient inventory.FileClient fileClient inventory.FileClient
shareClient inventory.ShareClient shareClient inventory.ShareClient
aclClient inventory.AclClient
config *setting.DBFS config *setting.DBFS
*baseNavigator *baseNavigator
@ -64,6 +66,10 @@ type (
ownerRoot *File ownerRoot *File
share *ent.Share share *ent.Share
owner *ent.User owner *ent.User
// aclCaps carries the unioned permission bits of ACL entries on the
// shared file matching the acting user. nil means no entry matched —
// capabilities then fall back to share props.
aclCaps *boolset.BooleanSet
disableRecycle bool disableRecycle bool
persist func() persist func()
} }
@ -74,6 +80,7 @@ type (
SingleFileShare bool SingleFileShare bool
Share *ent.Share Share *ent.Share
Owner *ent.User Owner *ent.User
AclCaps *boolset.BooleanSet
} }
) )
@ -86,6 +93,7 @@ func (n *shareNavigator) PersistState(kv cache.Driver, key string) {
SingleFileShare: n.singleFileShare, SingleFileShare: n.singleFileShare,
Share: n.share, Share: n.share,
Owner: n.owner, Owner: n.owner,
AclCaps: n.aclCaps,
}, ContextHintTTL) }, ContextHintTTL)
} }
} }
@ -97,6 +105,7 @@ func (n *shareNavigator) RestoreState(s State) error {
n.ownerRoot = state.OwnerRoot n.ownerRoot = state.OwnerRoot
n.singleFileShare = state.SingleFileShare n.singleFileShare = state.SingleFileShare
n.share = state.Share n.share = state.Share
n.aclCaps = state.AclCaps
n.owner = state.Owner n.owner = state.Owner
return nil return nil
} }
@ -142,6 +151,14 @@ func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error)
// Share must be assigned before capabilities are derived from its props. // Share must be assigned before capabilities are derived from its props.
n.share = share n.share = share
// Resolve per-file ACL entries for non-owner visitors; matched rows
// replace the share-props capability set for this user.
if n.aclClient != nil && n.user.ID != n.owner.ID {
if caps, err := n.aclClient.EffectivePermissions(ctx, share.Edges.File.ID, n.user); err == nil {
n.aclCaps = caps
}
}
// Share permission setting should overwrite root folder's permission // Share permission setting should overwrite root folder's permission
n.shareRoot = newFile(nil, share.Edges.File) n.shareRoot = newFile(nil, share.Edges.File)
@ -296,6 +313,12 @@ func (n *shareNavigator) Capabilities(isSearching bool) *fs.NavigatorProps {
// shareCapabilities derives the effective capability set from share props. // shareCapabilities derives the effective capability set from share props.
func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet { func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet {
// Matched ACL entries fully define a non-owner visitor's capabilities;
// when no entry matched (nil) share props apply as the link default.
if n.aclCaps != nil && n.owner != nil && n.user.ID != n.owner.ID {
return aclPermsToCapabilities(n.aclCaps)
}
bs := &boolset.BooleanSet{} bs := &boolset.BooleanSet{}
boolset.Sets(map[NavigatorCapability]bool{ boolset.Sets(map[NavigatorCapability]bool{
NavigatorCapabilityListChildren: true, NavigatorCapabilityListChildren: true,
@ -345,6 +368,45 @@ func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet {
return bs return bs
} }
// aclPermsToCapabilities maps ACL permission bits (read/create/update/delete)
// to the navigator capability set granted through a share link.
func aclPermsToCapabilities(perms *boolset.BooleanSet) *boolset.BooleanSet {
bs := &boolset.BooleanSet{}
if perms.Enabled(int(types.AclPermRead)) {
boolset.Sets(map[NavigatorCapability]bool{
NavigatorCapabilityListChildren: true,
NavigatorCapabilityDownloadFile: true,
NavigatorCapabilityEnterFolder: true,
NavigatorCapabilityInfo: true,
NavigatorCapabilityGenerateThumb: true,
}, bs)
}
if perms.Enabled(int(types.AclPermCreate)) {
boolset.Sets(map[NavigatorCapability]bool{
NavigatorCapabilityUploadFile: true,
NavigatorCapabilityCreateFile: true,
NavigatorCapabilityLockFile: true,
NavigatorCapabilityEnterFolder: true,
}, bs)
}
if perms.Enabled(int(types.AclPermUpdate)) {
boolset.Sets(map[NavigatorCapability]bool{
NavigatorCapabilityRenameFile: true,
NavigatorCapabilityUpdateMetadata: true,
NavigatorCapabilityUploadFile: true,
NavigatorCapabilityCreateFile: true,
NavigatorCapabilityLockFile: true,
}, bs)
}
if perms.Enabled(int(types.AclPermDelete)) {
boolset.Sets(map[NavigatorCapability]bool{
NavigatorCapabilityDeleteFile: true,
NavigatorCapabilitySoftDelete: true,
}, bs)
}
return bs
}
func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) {
oldBase := n.baseNavigator.fileClient oldBase := n.baseNavigator.fileClient
revertFile, err := followTxClients(ctx, &n.fileClient) revertFile, err := followTxClients(ctx, &n.fileClient)

@ -168,6 +168,7 @@ func NewFileManager(dep dependency.Dep, u *ent.User) FileManager {
fs: dbfs.NewDatabaseFS(u, dbfs.DBFSDependencies{ fs: dbfs.NewDatabaseFS(u, dbfs.DBFSDependencies{
FileClient: dep.FileClient(), FileClient: dep.FileClient(),
ShareClient: dep.ShareClient(), ShareClient: dep.ShareClient(),
AclClient: dep.AclClient(),
UserClient: dep.UserClient(), UserClient: dep.UserClient(),
StoragePolicyClient: dep.StoragePolicyClient(), StoragePolicyClient: dep.StoragePolicyClient(),
DirectLinkClient: dep.DirectLinkClient(), DirectLinkClient: dep.DirectLinkClient(),

@ -353,6 +353,57 @@ func PatchMetadata(c *gin.Context) {
c.JSON(200, serializer.Response{}) c.JSON(200, serializer.Response{})
} }
// ListAcl lists ACL entries of a file.
func ListAcl(c *gin.Context) {
service := ParametersFromContext[*explorer.AclListService](c, explorer.AclListParamCtx{})
resp, err := service.Get(c)
if respondErr(c, err) {
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// UpsertAcl creates or updates an ACL entry.
func UpsertAcl(c *gin.Context) {
service := ParametersFromContext[*explorer.AclUpsertService](c, explorer.AclUpsertParamCtx{})
resp, err := service.Update(c)
if respondErr(c, err) {
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// DeleteAcl removes an ACL entry.
func DeleteAcl(c *gin.Context) {
service := ParametersFromContext[*explorer.AclDeleteService](c, explorer.AclDeleteParamCtx{})
err := service.Delete(c)
if respondErr(c, err) {
return
}
c.JSON(200, serializer.Response{})
}
// SearchAclSubjects resolves selectable ACL subjects (users by exact email,
// groups by name).
func SearchAclSubjects(c *gin.Context) {
service := ParametersFromContext[*explorer.AclSubjectSearchService](c, explorer.AclSubjectSearchParamCtx{})
resp, err := service.Get(c)
if respondErr(c, err) {
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// GetFileInfo gets file info // GetFileInfo gets file info
func GetFileInfo(c *gin.Context) { func GetFileInfo(c *gin.Context) {
service := ParametersFromContext[*explorer.GetFileInfoService](c, explorer.GetFileInfoParameterCtx{}) service := ParametersFromContext[*explorer.GetFileInfoService](c, explorer.GetFileInfoParameterCtx{})

@ -804,6 +804,28 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine {
controllers.FromQuery[explorer.GetFileInfoService](explorer.GetFileInfoParameterCtx{}), controllers.FromQuery[explorer.GetFileInfoService](explorer.GetFileInfoParameterCtx{}),
controllers.GetFileInfo, controllers.GetFileInfo,
) )
// Per-file ACL entries (Permissions dialog)
acl := file.Group("acl")
{
acl.GET("",
controllers.FromQuery[explorer.AclListService](explorer.AclListParamCtx{}),
controllers.ListAcl,
)
acl.GET("subjects",
controllers.FromQuery[explorer.AclSubjectSearchService](explorer.AclSubjectSearchParamCtx{}),
controllers.SearchAclSubjects,
)
acl.PUT("",
middleware.RequiredScopes(types.ScopeFilesWrite),
controllers.FromJSON[explorer.AclUpsertService](explorer.AclUpsertParamCtx{}),
controllers.UpsertAcl,
)
acl.DELETE("",
middleware.RequiredScopes(types.ScopeFilesWrite),
controllers.FromQuery[explorer.AclDeleteService](explorer.AclDeleteParamCtx{}),
controllers.DeleteAcl,
)
}
// Version management // Version management
version := file.Group("version", middleware.RequiredScopes(types.ScopeFilesWrite)) version := file.Group("version", middleware.RequiredScopes(types.ScopeFilesWrite))
{ {

@ -0,0 +1,240 @@
package explorer
import (
"fmt"
"strings"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/aclentry"
"github.com/cloudreve/Cloudreve/v4/inventory"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
)
// AclEntryResponse is one row in the Permissions dialog.
type AclEntryResponse struct {
ID int `json:"id"`
SubjectType string `json:"subject_type"`
SubjectID int `json:"subject_id"`
SubjectName string `json:"subject_name"`
Permissions []string `json:"permissions"`
}
// AclSubjectResponse is a selectable subject in the Permissions dialog.
type AclSubjectResponse struct {
Type string `json:"type"`
ID int `json:"id"`
Name string `json:"name"`
}
type (
// AclListService lists ACL entries of a file.
AclListService struct {
Uri string `form:"uri" binding:"required"`
}
AclListParamCtx struct{}
// AclUpsertService creates or updates an ACL entry.
AclUpsertService struct {
Uri string `json:"uri" binding:"required"`
SubjectType string `json:"subject_type" binding:"required,oneof=user group anonymous everyone"`
SubjectID int `json:"subject_id"`
Permissions []string `json:"permissions"`
}
AclUpsertParamCtx struct{}
// AclDeleteService removes an ACL entry.
AclDeleteService struct {
Uri string `form:"uri" binding:"required"`
ID int `form:"id" binding:"required,min=1"`
}
AclDeleteParamCtx struct{}
// AclSubjectSearchService resolves selectable subjects for the dialog.
AclSubjectSearchService struct {
Keyword string `form:"keyword"`
}
AclSubjectSearchParamCtx struct{}
)
func aclPermissionsToSet(perms []string) (*boolset.BooleanSet, error) {
bs := &boolset.BooleanSet{}
nameToBit := lo.Invert(types.AclPermissionList)
for _, p := range perms {
bit, ok := nameToBit[p]
if !ok {
return nil, fmt.Errorf("unknown permission %q", p)
}
boolset.Set(int(bit), true, bs)
}
return bs, nil
}
func aclSetToPermissions(bs *boolset.BooleanSet) []string {
res := []string{}
for bit, name := range types.AclPermissionList {
if bs != nil && bs.Enabled(int(bit)) {
res = append(res, name)
}
}
return res
}
// ownedAclFile resolves the target file and enforces that the acting user
// owns it and their group may manage explicit permissions.
func ownedAclFile(c *gin.Context, uriRaw string) (int, error) {
user := inventory.UserFromContext(c)
uri, err := fs.NewUriFromString(uriRaw)
if err != nil {
return 0, serializer.NewError(serializer.CodeParamErr, "unknown uri", err)
}
m := manager.NewFileManager(dependency.FromContext(c), user)
defer m.Recycle()
file, err := m.Get(c, uri)
if err != nil {
return 0, fmt.Errorf("failed to get file: %w", err)
}
if file.OwnerID() != user.ID {
return 0, serializer.NewError(serializer.CodeNoPermissionErr, "Only the owner can manage permissions", nil)
}
if user.Edges.Group == nil || user.Edges.Group.Permissions == nil ||
!user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionSetExplicitUser)) {
return 0, serializer.NewError(serializer.CodeNoPermissionErr, "Permission management is not enabled for your group", nil)
}
return file.ID(), nil
}
func (s *AclListService) Get(c *gin.Context) ([]*AclEntryResponse, error) {
dep := dependency.FromContext(c)
fileID, err := ownedAclFile(c, s.Uri)
if err != nil {
return nil, err
}
entries, err := dep.AclClient().List(c, fileID)
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to list permissions", err)
}
// Resolve subject display names.
names := map[string]string{}
for _, e := range entries {
key := fmt.Sprintf("%s:%d", e.SubjectType, e.SubjectID)
switch e.SubjectType {
case aclentry.SubjectTypeUser:
if u, err := dep.UserClient().GetByID(c, e.SubjectID); err == nil {
names[key] = u.Email
}
case aclentry.SubjectTypeGroup:
if g, err := dep.GroupClient().GetByID(c, e.SubjectID); err == nil {
names[key] = g.Name
}
}
}
return lo.Map(entries, func(e *ent.AclEntry, _ int) *AclEntryResponse {
name := names[fmt.Sprintf("%s:%d", e.SubjectType, e.SubjectID)]
if e.SubjectType == aclentry.SubjectTypeAnonymous {
name = "anonymous"
} else if e.SubjectType == aclentry.SubjectTypeEveryone {
name = "everyone"
}
return &AclEntryResponse{
ID: e.ID,
SubjectType: string(e.SubjectType),
SubjectID: e.SubjectID,
SubjectName: name,
Permissions: aclSetToPermissions(e.Permissions),
}
}), nil
}
func (s *AclUpsertService) Update(c *gin.Context) (*AclEntryResponse, error) {
dep := dependency.FromContext(c)
fileID, err := ownedAclFile(c, s.Uri)
if err != nil {
return nil, err
}
perms, err := aclPermissionsToSet(s.Permissions)
if err != nil {
return nil, serializer.NewError(serializer.CodeParamErr, "Invalid permissions", err)
}
subjectType := aclentry.SubjectType(s.SubjectType)
if (subjectType == aclentry.SubjectTypeAnonymous || subjectType == aclentry.SubjectTypeEveryone) && s.SubjectID != 0 {
return nil, serializer.NewError(serializer.CodeParamErr, "General access entries must not carry a subject id", nil)
}
if (subjectType == aclentry.SubjectTypeUser || subjectType == aclentry.SubjectTypeGroup) && s.SubjectID <= 0 {
return nil, serializer.NewError(serializer.CodeParamErr, "Subject id is required", nil)
}
e, err := dep.AclClient().Upsert(c, &inventory.UpsertAclEntryParams{
FileID: fileID,
SubjectType: subjectType,
SubjectID: s.SubjectID,
Permissions: perms,
})
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to save permission", err)
}
return &AclEntryResponse{
ID: e.ID,
SubjectType: string(e.SubjectType),
SubjectID: e.SubjectID,
Permissions: aclSetToPermissions(e.Permissions),
}, nil
}
func (s *AclDeleteService) Delete(c *gin.Context) error {
dep := dependency.FromContext(c)
fileID, err := ownedAclFile(c, s.Uri)
if err != nil {
return err
}
if err := dep.AclClient().Delete(c, fileID, s.ID); err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to delete permission", err)
}
return nil
}
func (s *AclSubjectSearchService) Get(c *gin.Context) ([]*AclSubjectResponse, error) {
user := inventory.UserFromContext(c)
if user.Edges.Group == nil || user.Edges.Group.Permissions == nil ||
!user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionSetExplicitUser)) {
return nil, serializer.NewError(serializer.CodeNoPermissionErr, "Permission management is not enabled for your group", nil)
}
dep := dependency.FromContext(c)
res := []*AclSubjectResponse{}
// Exact email match only — user enumeration by prefix is not exposed.
if s.Keyword != "" {
if u, err := dep.UserClient().GetByEmail(c, s.Keyword); err == nil {
res = append(res, &AclSubjectResponse{Type: "user", ID: u.ID, Name: u.Email})
}
}
groups, err := dep.GroupClient().ListAll(c)
if err == nil {
for _, g := range groups {
if s.Keyword == "" || strings.Contains(strings.ToLower(g.Name), strings.ToLower(s.Keyword)) {
res = append(res, &AclSubjectResponse{Type: "group", ID: g.ID, Name: g.Name})
}
}
}
return res, nil
}
Loading…
Cancel
Save