feat: QQ Connect sign-in and external account linking

Adds a second third-party sign-in provider alongside generic OIDC SSO:

- sso_binding entity + inventory client: persistent (provider, subject)
  identity bindings, one binding per (user, provider), conflict-safe
  re-bind.
- QQ Connect OAuth2 flow (graph.qq.com): authorize redirect, code
  exchange, JSONP openid resolution with client_id echo check, optional
  get_user_info nickname for provisioning.
- link=1 mode binds the QQ identity to the signed-in user and returns
  to /settings?tab=security; login mode reuses the one-time SSO ticket
  handoff. Provisioning uses synthetic @connect.qq.local addresses and
  is gated by qq_connect_register_enabled.
- DELETE /user/setting/sso_binding/:provider under
  ScopeUserSecurityInfoWrite, with a lockout guard refusing to remove
  the last viable sign-in method (no password, no passkey, no other
  binding).
- Server-side single-use state in KV, SSRF validation of the provider
  endpoints, link/unlink activity events, qq_connect_app_secret in
  RedactedSettings.
- Admin: QQ Connect accordion in user/session settings; user: linked
  accounts section with unlink, QQ button on the sign-in page;
  qq_connect_enabled in the login site config.

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
parent e02c1cff3e
commit fd86e28e87

@ -114,6 +114,8 @@ type Dep interface {
ActivityClient() inventory.ActivityClient
// AbuseReportClient Get a singleton inventory.AbuseReportClient instance for the abuse review queue.
AbuseReportClient() inventory.AbuseReportClient
// SsoBindingClient Get a singleton inventory.SsoBindingClient instance for external identity bindings.
SsoBindingClient() inventory.SsoBindingClient
// TaskClient Get a singleton inventory.TaskClient instance for access DB task store.
TaskClient() inventory.TaskClient
// ForkWithLogger create a shallow copy of dependency with a new correlated logger, used as per-request dep.
@ -174,6 +176,7 @@ type dependency struct {
vasClient inventory.VasClient
activityClient inventory.ActivityClient
abuseReportClient inventory.AbuseReportClient
ssoBindingClient inventory.SsoBindingClient
settingProvider setting.Provider
userClient inventory.UserClient
groupClient inventory.GroupClient
@ -870,6 +873,15 @@ func (d *dependency) AbuseReportClient() inventory.AbuseReportClient {
return d.abuseReportClient
}
func (d *dependency) SsoBindingClient() inventory.SsoBindingClient {
if d.ssoBindingClient != nil {
return d.ssoBindingClient
}
d.ssoBindingClient = inventory.NewSsoBindingClient(d.DBClient(), d.ConfigProvider().Database().Type)
return d.ssoBindingClient
}
func (d *dependency) TaskClient() inventory.TaskClient {
if d.taskClient != nil {
return d.taskClient

@ -36,6 +36,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@ -91,6 +92,8 @@ type Client struct {
SharePurchase *SharePurchaseClient
// Sku is the client for interacting with the Sku builders.
Sku *SkuClient
// SsoBinding is the client for interacting with the SsoBinding builders.
SsoBinding *SsoBindingClient
// StoragePolicy is the client for interacting with the StoragePolicy builders.
StoragePolicy *StoragePolicyClient
// Task is the client for interacting with the Task builders.
@ -131,6 +134,7 @@ func (c *Client) init() {
c.Share = NewShareClient(c.config)
c.SharePurchase = NewSharePurchaseClient(c.config)
c.Sku = NewSkuClient(c.config)
c.SsoBinding = NewSsoBindingClient(c.config)
c.StoragePolicy = NewStoragePolicyClient(c.config)
c.Task = NewTaskClient(c.config)
c.User = NewUserClient(c.config)
@ -248,6 +252,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
Share: NewShareClient(cfg),
SharePurchase: NewSharePurchaseClient(cfg),
Sku: NewSkuClient(cfg),
SsoBinding: NewSsoBindingClient(cfg),
StoragePolicy: NewStoragePolicyClient(cfg),
Task: NewTaskClient(cfg),
User: NewUserClient(cfg),
@ -292,6 +297,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
Share: NewShareClient(cfg),
SharePurchase: NewSharePurchaseClient(cfg),
Sku: NewSkuClient(cfg),
SsoBinding: NewSsoBindingClient(cfg),
StoragePolicy: NewStoragePolicyClient(cfg),
Task: NewTaskClient(cfg),
User: NewUserClient(cfg),
@ -328,8 +334,8 @@ func (c *Client) Use(hooks ...Hook) {
c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount,
c.DirectLink, c.Entity, c.File, c.FsEvent, c.GiftCode, c.Group,
c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey,
c.Setting, c.Share, c.SharePurchase, c.Sku, c.StoragePolicy, c.Task, c.User,
c.UserGrant,
c.Setting, c.Share, c.SharePurchase, c.Sku, c.SsoBinding, c.StoragePolicy,
c.Task, c.User, c.UserGrant,
} {
n.Use(hooks...)
}
@ -342,8 +348,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
c.AbuseReport, c.AclEntry, c.ActivityEvent, c.CreditTxn, c.DavAccount,
c.DirectLink, c.Entity, c.File, c.FsEvent, c.GiftCode, c.Group,
c.InvitationCode, c.Metadata, c.Node, c.OAuthClient, c.OAuthGrant, c.Passkey,
c.Setting, c.Share, c.SharePurchase, c.Sku, c.StoragePolicy, c.Task, c.User,
c.UserGrant,
c.Setting, c.Share, c.SharePurchase, c.Sku, c.SsoBinding, c.StoragePolicy,
c.Task, c.User, c.UserGrant,
} {
n.Intercept(interceptors...)
}
@ -394,6 +400,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.SharePurchase.mutate(ctx, m)
case *SkuMutation:
return c.Sku.mutate(ctx, m)
case *SsoBindingMutation:
return c.SsoBinding.mutate(ctx, m)
case *StoragePolicyMutation:
return c.StoragePolicy.mutate(ctx, m)
case *TaskMutation:
@ -3753,6 +3761,157 @@ func (c *SkuClient) mutate(ctx context.Context, m *SkuMutation) (Value, error) {
}
}
// SsoBindingClient is a client for the SsoBinding schema.
type SsoBindingClient struct {
config
}
// NewSsoBindingClient returns a client for the SsoBinding from the given config.
func NewSsoBindingClient(c config) *SsoBindingClient {
return &SsoBindingClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `ssobinding.Hooks(f(g(h())))`.
func (c *SsoBindingClient) Use(hooks ...Hook) {
c.hooks.SsoBinding = append(c.hooks.SsoBinding, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `ssobinding.Intercept(f(g(h())))`.
func (c *SsoBindingClient) Intercept(interceptors ...Interceptor) {
c.inters.SsoBinding = append(c.inters.SsoBinding, interceptors...)
}
// Create returns a builder for creating a SsoBinding entity.
func (c *SsoBindingClient) Create() *SsoBindingCreate {
mutation := newSsoBindingMutation(c.config, OpCreate)
return &SsoBindingCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of SsoBinding entities.
func (c *SsoBindingClient) CreateBulk(builders ...*SsoBindingCreate) *SsoBindingCreateBulk {
return &SsoBindingCreateBulk{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 *SsoBindingClient) MapCreateBulk(slice any, setFunc func(*SsoBindingCreate, int)) *SsoBindingCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &SsoBindingCreateBulk{err: fmt.Errorf("calling to SsoBindingClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*SsoBindingCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &SsoBindingCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for SsoBinding.
func (c *SsoBindingClient) Update() *SsoBindingUpdate {
mutation := newSsoBindingMutation(c.config, OpUpdate)
return &SsoBindingUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *SsoBindingClient) UpdateOne(sb *SsoBinding) *SsoBindingUpdateOne {
mutation := newSsoBindingMutation(c.config, OpUpdateOne, withSsoBinding(sb))
return &SsoBindingUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *SsoBindingClient) UpdateOneID(id int) *SsoBindingUpdateOne {
mutation := newSsoBindingMutation(c.config, OpUpdateOne, withSsoBindingID(id))
return &SsoBindingUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for SsoBinding.
func (c *SsoBindingClient) Delete() *SsoBindingDelete {
mutation := newSsoBindingMutation(c.config, OpDelete)
return &SsoBindingDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *SsoBindingClient) DeleteOne(sb *SsoBinding) *SsoBindingDeleteOne {
return c.DeleteOneID(sb.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *SsoBindingClient) DeleteOneID(id int) *SsoBindingDeleteOne {
builder := c.Delete().Where(ssobinding.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &SsoBindingDeleteOne{builder}
}
// Query returns a query builder for SsoBinding.
func (c *SsoBindingClient) Query() *SsoBindingQuery {
return &SsoBindingQuery{
config: c.config,
ctx: &QueryContext{Type: TypeSsoBinding},
inters: c.Interceptors(),
}
}
// Get returns a SsoBinding entity by its id.
func (c *SsoBindingClient) Get(ctx context.Context, id int) (*SsoBinding, error) {
return c.Query().Where(ssobinding.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *SsoBindingClient) GetX(ctx context.Context, id int) *SsoBinding {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryUser queries the user edge of a SsoBinding.
func (c *SsoBindingClient) QueryUser(sb *SsoBinding) *UserQuery {
query := (&UserClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := sb.ID
step := sqlgraph.NewStep(
sqlgraph.From(ssobinding.Table, ssobinding.FieldID, id),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ssobinding.UserTable, ssobinding.UserColumn),
)
fromV = sqlgraph.Neighbors(sb.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *SsoBindingClient) Hooks() []Hook {
hooks := c.hooks.SsoBinding
return append(hooks[:len(hooks):len(hooks)], ssobinding.Hooks[:]...)
}
// Interceptors returns the client interceptors.
func (c *SsoBindingClient) Interceptors() []Interceptor {
inters := c.inters.SsoBinding
return append(inters[:len(inters):len(inters)], ssobinding.Interceptors[:]...)
}
func (c *SsoBindingClient) mutate(ctx context.Context, m *SsoBindingMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&SsoBindingCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&SsoBindingUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&SsoBindingUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&SsoBindingDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown SsoBinding mutation op: %q", m.Op())
}
}
// StoragePolicyClient is a client for the StoragePolicy schema.
type StoragePolicyClient struct {
config
@ -4435,6 +4594,22 @@ func (c *UserClient) QuerySharePurchases(u *User) *SharePurchaseQuery {
return query
}
// QuerySSOBindings queries the sso_bindings edge of a User.
func (c *UserClient) QuerySSOBindings(u *User) *SsoBindingQuery {
query := (&SsoBindingClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := u.ID
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, id),
sqlgraph.To(ssobinding.Table, ssobinding.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.SSOBindingsTable, user.SSOBindingsColumn),
)
fromV = sqlgraph.Neighbors(u.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *UserClient) Hooks() []Hook {
hooks := c.hooks.User
@ -4618,14 +4793,14 @@ type (
hooks struct {
AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity,
File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient,
OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, StoragePolicy, Task,
User, UserGrant []ent.Hook
OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, SsoBinding,
StoragePolicy, Task, User, UserGrant []ent.Hook
}
inters struct {
AbuseReport, AclEntry, ActivityEvent, CreditTxn, DavAccount, DirectLink, Entity,
File, FsEvent, GiftCode, Group, InvitationCode, Metadata, Node, OAuthClient,
OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, StoragePolicy, Task,
User, UserGrant []ent.Interceptor
OAuthGrant, Passkey, Setting, Share, SharePurchase, Sku, SsoBinding,
StoragePolicy, Task, User, UserGrant []ent.Interceptor
}
)

@ -33,6 +33,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@ -118,6 +119,7 @@ func checkColumn(table, column string) error {
share.Table: share.ValidColumn,
sharepurchase.Table: sharepurchase.ValidColumn,
sku.Table: sku.ValidColumn,
ssobinding.Table: ssobinding.ValidColumn,
storagepolicy.Table: storagepolicy.ValidColumn,
task.Table: task.ValidColumn,
user.Table: user.ValidColumn,

@ -261,6 +261,18 @@ func (f SkuFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error)
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SkuMutation", m)
}
// The SsoBindingFunc type is an adapter to allow the use of ordinary
// function as SsoBinding mutator.
type SsoBindingFunc func(context.Context, *ent.SsoBindingMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f SsoBindingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.SsoBindingMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SsoBindingMutation", m)
}
// The StoragePolicyFunc type is an adapter to allow the use of ordinary
// function as StoragePolicy mutator.
type StoragePolicyFunc func(context.Context, *ent.StoragePolicyMutation) (ent.Value, error)

@ -30,6 +30,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@ -659,6 +660,33 @@ func (f TraverseSku) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.SkuQuery", q)
}
// The SsoBindingFunc type is an adapter to allow the use of ordinary function as a Querier.
type SsoBindingFunc func(context.Context, *ent.SsoBindingQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f SsoBindingFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.SsoBindingQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.SsoBindingQuery", q)
}
// The TraverseSsoBinding type is an adapter to allow the use of ordinary function as Traverser.
type TraverseSsoBinding func(context.Context, *ent.SsoBindingQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseSsoBinding) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseSsoBinding) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.SsoBindingQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.SsoBindingQuery", q)
}
// The StoragePolicyFunc type is an adapter to allow the use of ordinary function as a Querier.
type StoragePolicyFunc func(context.Context, *ent.StoragePolicyQuery) (ent.Value, error)
@ -812,6 +840,8 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.SharePurchaseQuery, predicate.SharePurchase, sharepurchase.OrderOption]{typ: ent.TypeSharePurchase, tq: q}, nil
case *ent.SkuQuery:
return &query[*ent.SkuQuery, predicate.Sku, sku.OrderOption]{typ: ent.TypeSku, tq: q}, nil
case *ent.SsoBindingQuery:
return &query[*ent.SsoBindingQuery, predicate.SsoBinding, ssobinding.OrderOption]{typ: ent.TypeSsoBinding, tq: q}, nil
case *ent.StoragePolicyQuery:
return &query[*ent.StoragePolicyQuery, predicate.StoragePolicy, storagepolicy.OrderOption]{typ: ent.TypeStoragePolicy, tq: q}, nil
case *ent.TaskQuery:

File diff suppressed because one or more lines are too long

@ -664,6 +664,42 @@ var (
Columns: SkusColumns,
PrimaryKey: []*schema.Column{SkusColumns[0]},
}
// SSOBindingsColumns holds the columns for the "sso_bindings" table.
SSOBindingsColumns = []*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: "provider", Type: field.TypeString, Size: 32},
{Name: "subject", Type: field.TypeString, Size: 128},
{Name: "user_id", Type: field.TypeInt},
}
// SSOBindingsTable holds the schema information for the "sso_bindings" table.
SSOBindingsTable = &schema.Table{
Name: "sso_bindings",
Columns: SSOBindingsColumns,
PrimaryKey: []*schema.Column{SSOBindingsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "sso_bindings_users_sso_bindings",
Columns: []*schema.Column{SSOBindingsColumns[6]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
},
Indexes: []*schema.Index{
{
Name: "ssobinding_provider_subject",
Unique: true,
Columns: []*schema.Column{SSOBindingsColumns[4], SSOBindingsColumns[5]},
},
{
Name: "ssobinding_user_id_provider",
Unique: true,
Columns: []*schema.Column{SSOBindingsColumns[6], SSOBindingsColumns[4]},
},
},
}
// StoragePoliciesColumns holds the columns for the "storage_policies" table.
StoragePoliciesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
@ -872,6 +908,7 @@ var (
SharesTable,
SharePurchasesTable,
SkusTable,
SSOBindingsTable,
StoragePoliciesTable,
TasksTable,
UsersTable,
@ -902,6 +939,7 @@ func init() {
SharesTable.ForeignKeys[1].RefTable = UsersTable
SharePurchasesTable.ForeignKeys[0].RefTable = SharesTable
SharePurchasesTable.ForeignKeys[1].RefTable = UsersTable
SSOBindingsTable.ForeignKeys[0].RefTable = UsersTable
StoragePoliciesTable.ForeignKeys[0].RefTable = NodesTable
TasksTable.ForeignKeys[0].RefTable = UsersTable
UsersTable.ForeignKeys[0].RefTable = GroupsTable

@ -33,6 +33,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@ -73,6 +74,7 @@ const (
TypeShare = "Share"
TypeSharePurchase = "SharePurchase"
TypeSku = "Sku"
TypeSsoBinding = "SsoBinding"
TypeStoragePolicy = "StoragePolicy"
TypeTask = "Task"
TypeUser = "User"
@ -20617,6 +20619,681 @@ func (m *SkuMutation) ResetEdge(name string) error {
return fmt.Errorf("unknown Sku edge %s", name)
}
// SsoBindingMutation represents an operation that mutates the SsoBinding nodes in the graph.
type SsoBindingMutation struct {
config
op Op
typ string
id *int
created_at *time.Time
updated_at *time.Time
deleted_at *time.Time
provider *string
subject *string
clearedFields map[string]struct{}
user *int
cleareduser bool
done bool
oldValue func(context.Context) (*SsoBinding, error)
predicates []predicate.SsoBinding
}
var _ ent.Mutation = (*SsoBindingMutation)(nil)
// ssobindingOption allows management of the mutation configuration using functional options.
type ssobindingOption func(*SsoBindingMutation)
// newSsoBindingMutation creates new mutation for the SsoBinding entity.
func newSsoBindingMutation(c config, op Op, opts ...ssobindingOption) *SsoBindingMutation {
m := &SsoBindingMutation{
config: c,
op: op,
typ: TypeSsoBinding,
clearedFields: make(map[string]struct{}),
}
for _, opt := range opts {
opt(m)
}
return m
}
// withSsoBindingID sets the ID field of the mutation.
func withSsoBindingID(id int) ssobindingOption {
return func(m *SsoBindingMutation) {
var (
err error
once sync.Once
value *SsoBinding
)
m.oldValue = func(ctx context.Context) (*SsoBinding, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
value, err = m.Client().SsoBinding.Get(ctx, id)
}
})
return value, err
}
m.id = &id
}
}
// withSsoBinding sets the old SsoBinding of the mutation.
func withSsoBinding(node *SsoBinding) ssobindingOption {
return func(m *SsoBindingMutation) {
m.oldValue = func(context.Context) (*SsoBinding, 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 SsoBindingMutation) 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 SsoBindingMutation) 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 *SsoBindingMutation) 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 *SsoBindingMutation) 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().SsoBinding.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 *SsoBindingMutation) SetCreatedAt(t time.Time) {
m.created_at = &t
}
// CreatedAt returns the value of the "created_at" field in the mutation.
func (m *SsoBindingMutation) 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 SsoBinding entity.
// If the SsoBinding 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 *SsoBindingMutation) 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 *SsoBindingMutation) ResetCreatedAt() {
m.created_at = nil
}
// SetUpdatedAt sets the "updated_at" field.
func (m *SsoBindingMutation) SetUpdatedAt(t time.Time) {
m.updated_at = &t
}
// UpdatedAt returns the value of the "updated_at" field in the mutation.
func (m *SsoBindingMutation) 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 SsoBinding entity.
// If the SsoBinding 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 *SsoBindingMutation) 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 *SsoBindingMutation) ResetUpdatedAt() {
m.updated_at = nil
}
// SetDeletedAt sets the "deleted_at" field.
func (m *SsoBindingMutation) SetDeletedAt(t time.Time) {
m.deleted_at = &t
}
// DeletedAt returns the value of the "deleted_at" field in the mutation.
func (m *SsoBindingMutation) 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 SsoBinding entity.
// If the SsoBinding 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 *SsoBindingMutation) 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 *SsoBindingMutation) ClearDeletedAt() {
m.deleted_at = nil
m.clearedFields[ssobinding.FieldDeletedAt] = struct{}{}
}
// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation.
func (m *SsoBindingMutation) DeletedAtCleared() bool {
_, ok := m.clearedFields[ssobinding.FieldDeletedAt]
return ok
}
// ResetDeletedAt resets all changes to the "deleted_at" field.
func (m *SsoBindingMutation) ResetDeletedAt() {
m.deleted_at = nil
delete(m.clearedFields, ssobinding.FieldDeletedAt)
}
// SetProvider sets the "provider" field.
func (m *SsoBindingMutation) SetProvider(s string) {
m.provider = &s
}
// Provider returns the value of the "provider" field in the mutation.
func (m *SsoBindingMutation) Provider() (r string, exists bool) {
v := m.provider
if v == nil {
return
}
return *v, true
}
// OldProvider returns the old "provider" field's value of the SsoBinding entity.
// If the SsoBinding 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 *SsoBindingMutation) OldProvider(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldProvider is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldProvider requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldProvider: %w", err)
}
return oldValue.Provider, nil
}
// ResetProvider resets all changes to the "provider" field.
func (m *SsoBindingMutation) ResetProvider() {
m.provider = nil
}
// SetSubject sets the "subject" field.
func (m *SsoBindingMutation) SetSubject(s string) {
m.subject = &s
}
// Subject returns the value of the "subject" field in the mutation.
func (m *SsoBindingMutation) Subject() (r string, exists bool) {
v := m.subject
if v == nil {
return
}
return *v, true
}
// OldSubject returns the old "subject" field's value of the SsoBinding entity.
// If the SsoBinding 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 *SsoBindingMutation) OldSubject(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSubject is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSubject requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSubject: %w", err)
}
return oldValue.Subject, nil
}
// ResetSubject resets all changes to the "subject" field.
func (m *SsoBindingMutation) ResetSubject() {
m.subject = nil
}
// SetUserID sets the "user_id" field.
func (m *SsoBindingMutation) SetUserID(i int) {
m.user = &i
}
// UserID returns the value of the "user_id" field in the mutation.
func (m *SsoBindingMutation) UserID() (r int, exists bool) {
v := m.user
if v == nil {
return
}
return *v, true
}
// OldUserID returns the old "user_id" field's value of the SsoBinding entity.
// If the SsoBinding 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 *SsoBindingMutation) OldUserID(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUserID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldUserID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldUserID: %w", err)
}
return oldValue.UserID, nil
}
// ResetUserID resets all changes to the "user_id" field.
func (m *SsoBindingMutation) ResetUserID() {
m.user = nil
}
// ClearUser clears the "user" edge to the User entity.
func (m *SsoBindingMutation) ClearUser() {
m.cleareduser = true
m.clearedFields[ssobinding.FieldUserID] = struct{}{}
}
// UserCleared reports if the "user" edge to the User entity was cleared.
func (m *SsoBindingMutation) UserCleared() bool {
return m.cleareduser
}
// UserIDs returns the "user" edge IDs in the mutation.
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
// UserID instead. It exists only for internal usage by the builders.
func (m *SsoBindingMutation) UserIDs() (ids []int) {
if id := m.user; id != nil {
ids = append(ids, *id)
}
return
}
// ResetUser resets all changes to the "user" edge.
func (m *SsoBindingMutation) ResetUser() {
m.user = nil
m.cleareduser = false
}
// Where appends a list predicates to the SsoBindingMutation builder.
func (m *SsoBindingMutation) Where(ps ...predicate.SsoBinding) {
m.predicates = append(m.predicates, ps...)
}
// WhereP appends storage-level predicates to the SsoBindingMutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
func (m *SsoBindingMutation) WhereP(ps ...func(*sql.Selector)) {
p := make([]predicate.SsoBinding, len(ps))
for i := range ps {
p[i] = ps[i]
}
m.Where(p...)
}
// Op returns the operation name.
func (m *SsoBindingMutation) Op() Op {
return m.op
}
// SetOp allows setting the mutation operation.
func (m *SsoBindingMutation) SetOp(op Op) {
m.op = op
}
// Type returns the node type of this mutation (SsoBinding).
func (m *SsoBindingMutation) 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 *SsoBindingMutation) Fields() []string {
fields := make([]string, 0, 6)
if m.created_at != nil {
fields = append(fields, ssobinding.FieldCreatedAt)
}
if m.updated_at != nil {
fields = append(fields, ssobinding.FieldUpdatedAt)
}
if m.deleted_at != nil {
fields = append(fields, ssobinding.FieldDeletedAt)
}
if m.provider != nil {
fields = append(fields, ssobinding.FieldProvider)
}
if m.subject != nil {
fields = append(fields, ssobinding.FieldSubject)
}
if m.user != nil {
fields = append(fields, ssobinding.FieldUserID)
}
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 *SsoBindingMutation) Field(name string) (ent.Value, bool) {
switch name {
case ssobinding.FieldCreatedAt:
return m.CreatedAt()
case ssobinding.FieldUpdatedAt:
return m.UpdatedAt()
case ssobinding.FieldDeletedAt:
return m.DeletedAt()
case ssobinding.FieldProvider:
return m.Provider()
case ssobinding.FieldSubject:
return m.Subject()
case ssobinding.FieldUserID:
return m.UserID()
}
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 *SsoBindingMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
case ssobinding.FieldCreatedAt:
return m.OldCreatedAt(ctx)
case ssobinding.FieldUpdatedAt:
return m.OldUpdatedAt(ctx)
case ssobinding.FieldDeletedAt:
return m.OldDeletedAt(ctx)
case ssobinding.FieldProvider:
return m.OldProvider(ctx)
case ssobinding.FieldSubject:
return m.OldSubject(ctx)
case ssobinding.FieldUserID:
return m.OldUserID(ctx)
}
return nil, fmt.Errorf("unknown SsoBinding 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 *SsoBindingMutation) SetField(name string, value ent.Value) error {
switch name {
case ssobinding.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 ssobinding.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 ssobinding.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 ssobinding.FieldProvider:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetProvider(v)
return nil
case ssobinding.FieldSubject:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSubject(v)
return nil
case ssobinding.FieldUserID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetUserID(v)
return nil
}
return fmt.Errorf("unknown SsoBinding field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
func (m *SsoBindingMutation) AddedFields() []string {
var fields []string
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 *SsoBindingMutation) AddedField(name string) (ent.Value, bool) {
switch name {
}
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 *SsoBindingMutation) AddField(name string, value ent.Value) error {
switch name {
}
return fmt.Errorf("unknown SsoBinding numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
func (m *SsoBindingMutation) ClearedFields() []string {
var fields []string
if m.FieldCleared(ssobinding.FieldDeletedAt) {
fields = append(fields, ssobinding.FieldDeletedAt)
}
return fields
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
func (m *SsoBindingMutation) 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 *SsoBindingMutation) ClearField(name string) error {
switch name {
case ssobinding.FieldDeletedAt:
m.ClearDeletedAt()
return nil
}
return fmt.Errorf("unknown SsoBinding 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 *SsoBindingMutation) ResetField(name string) error {
switch name {
case ssobinding.FieldCreatedAt:
m.ResetCreatedAt()
return nil
case ssobinding.FieldUpdatedAt:
m.ResetUpdatedAt()
return nil
case ssobinding.FieldDeletedAt:
m.ResetDeletedAt()
return nil
case ssobinding.FieldProvider:
m.ResetProvider()
return nil
case ssobinding.FieldSubject:
m.ResetSubject()
return nil
case ssobinding.FieldUserID:
m.ResetUserID()
return nil
}
return fmt.Errorf("unknown SsoBinding field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *SsoBindingMutation) AddedEdges() []string {
edges := make([]string, 0, 1)
if m.user != nil {
edges = append(edges, ssobinding.EdgeUser)
}
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
func (m *SsoBindingMutation) AddedIDs(name string) []ent.Value {
switch name {
case ssobinding.EdgeUser:
if id := m.user; id != nil {
return []ent.Value{*id}
}
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *SsoBindingMutation) 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 *SsoBindingMutation) RemovedIDs(name string) []ent.Value {
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *SsoBindingMutation) ClearedEdges() []string {
edges := make([]string, 0, 1)
if m.cleareduser {
edges = append(edges, ssobinding.EdgeUser)
}
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
func (m *SsoBindingMutation) EdgeCleared(name string) bool {
switch name {
case ssobinding.EdgeUser:
return m.cleareduser
}
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 *SsoBindingMutation) ClearEdge(name string) error {
switch name {
case ssobinding.EdgeUser:
m.ClearUser()
return nil
}
return fmt.Errorf("unknown SsoBinding 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 *SsoBindingMutation) ResetEdge(name string) error {
switch name {
case ssobinding.EdgeUser:
m.ResetUser()
return nil
}
return fmt.Errorf("unknown SsoBinding edge %s", name)
}
// StoragePolicyMutation represents an operation that mutates the StoragePolicy nodes in the graph.
type StoragePolicyMutation struct {
config
@ -23486,6 +24163,9 @@ type UserMutation struct {
share_purchases map[int]struct{}
removedshare_purchases map[int]struct{}
clearedshare_purchases bool
sso_bindings map[int]struct{}
removedsso_bindings map[int]struct{}
clearedsso_bindings bool
done bool
oldValue func(context.Context) (*User, error)
predicates []predicate.User
@ -24997,6 +25677,60 @@ func (m *UserMutation) ResetSharePurchases() {
m.removedshare_purchases = nil
}
// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by ids.
func (m *UserMutation) AddSSOBindingIDs(ids ...int) {
if m.sso_bindings == nil {
m.sso_bindings = make(map[int]struct{})
}
for i := range ids {
m.sso_bindings[ids[i]] = struct{}{}
}
}
// ClearSSOBindings clears the "sso_bindings" edge to the SsoBinding entity.
func (m *UserMutation) ClearSSOBindings() {
m.clearedsso_bindings = true
}
// SSOBindingsCleared reports if the "sso_bindings" edge to the SsoBinding entity was cleared.
func (m *UserMutation) SSOBindingsCleared() bool {
return m.clearedsso_bindings
}
// RemoveSSOBindingIDs removes the "sso_bindings" edge to the SsoBinding entity by IDs.
func (m *UserMutation) RemoveSSOBindingIDs(ids ...int) {
if m.removedsso_bindings == nil {
m.removedsso_bindings = make(map[int]struct{})
}
for i := range ids {
delete(m.sso_bindings, ids[i])
m.removedsso_bindings[ids[i]] = struct{}{}
}
}
// RemovedSSOBindings returns the removed IDs of the "sso_bindings" edge to the SsoBinding entity.
func (m *UserMutation) RemovedSSOBindingsIDs() (ids []int) {
for id := range m.removedsso_bindings {
ids = append(ids, id)
}
return
}
// SSOBindingsIDs returns the "sso_bindings" edge IDs in the mutation.
func (m *UserMutation) SSOBindingsIDs() (ids []int) {
for id := range m.sso_bindings {
ids = append(ids, id)
}
return
}
// ResetSSOBindings resets all changes to the "sso_bindings" edge.
func (m *UserMutation) ResetSSOBindings() {
m.sso_bindings = nil
m.clearedsso_bindings = false
m.removedsso_bindings = nil
}
// Where appends a list predicates to the UserMutation builder.
func (m *UserMutation) Where(ps ...predicate.User) {
m.predicates = append(m.predicates, ps...)
@ -25463,7 +26197,7 @@ func (m *UserMutation) ResetField(name string) error {
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *UserMutation) AddedEdges() []string {
edges := make([]string, 0, 13)
edges := make([]string, 0, 14)
if m.group != nil {
edges = append(edges, user.EdgeGroup)
}
@ -25503,6 +26237,9 @@ func (m *UserMutation) AddedEdges() []string {
if m.share_purchases != nil {
edges = append(edges, user.EdgeSharePurchases)
}
if m.sso_bindings != nil {
edges = append(edges, user.EdgeSSOBindings)
}
return edges
}
@ -25586,13 +26323,19 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
case user.EdgeSSOBindings:
ids := make([]ent.Value, 0, len(m.sso_bindings))
for id := range m.sso_bindings {
ids = append(ids, id)
}
return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *UserMutation) RemovedEdges() []string {
edges := make([]string, 0, 13)
edges := make([]string, 0, 14)
if m.removedfiles != nil {
edges = append(edges, user.EdgeFiles)
}
@ -25629,6 +26372,9 @@ func (m *UserMutation) RemovedEdges() []string {
if m.removedshare_purchases != nil {
edges = append(edges, user.EdgeSharePurchases)
}
if m.removedsso_bindings != nil {
edges = append(edges, user.EdgeSSOBindings)
}
return edges
}
@ -25708,13 +26454,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
case user.EdgeSSOBindings:
ids := make([]ent.Value, 0, len(m.removedsso_bindings))
for id := range m.removedsso_bindings {
ids = append(ids, id)
}
return ids
}
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *UserMutation) ClearedEdges() []string {
edges := make([]string, 0, 13)
edges := make([]string, 0, 14)
if m.clearedgroup {
edges = append(edges, user.EdgeGroup)
}
@ -25754,6 +26506,9 @@ func (m *UserMutation) ClearedEdges() []string {
if m.clearedshare_purchases {
edges = append(edges, user.EdgeSharePurchases)
}
if m.clearedsso_bindings {
edges = append(edges, user.EdgeSSOBindings)
}
return edges
}
@ -25787,6 +26542,8 @@ func (m *UserMutation) EdgeCleared(name string) bool {
return m.clearedgrants
case user.EdgeSharePurchases:
return m.clearedshare_purchases
case user.EdgeSSOBindings:
return m.clearedsso_bindings
}
return false
}
@ -25845,6 +26602,9 @@ func (m *UserMutation) ResetEdge(name string) error {
case user.EdgeSharePurchases:
m.ResetSharePurchases()
return nil
case user.EdgeSSOBindings:
m.ResetSSOBindings()
return nil
}
return fmt.Errorf("unknown User edge %s", name)
}

@ -130,6 +130,12 @@ func (m *SkuMutation) SetRawID(t int) {
// SetUpdatedAt sets the "updated_at" field.
func (m *SsoBindingMutation) SetRawID(t int) {
m.id = &t
}
// SetUpdatedAt sets the "updated_at" field.
func (m *StoragePolicyMutation) SetRawID(t int) {
m.id = &t
}

@ -69,6 +69,9 @@ type SharePurchase func(*sql.Selector)
// Sku is the predicate function for sku builders.
type Sku func(*sql.Selector)
// SsoBinding is the predicate function for ssobinding builders.
type SsoBinding func(*sql.Selector)
// StoragePolicy is the predicate function for storagepolicy builders.
type StoragePolicy func(*sql.Selector)

@ -27,6 +27,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/sku"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/storagepolicy"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
@ -540,6 +541,61 @@ func init() {
skuDescWeight := skuFields[9].Descriptor()
// sku.DefaultWeight holds the default value on creation for the weight field.
sku.DefaultWeight = skuDescWeight.Default.(int)
ssobindingMixin := schema.SsoBinding{}.Mixin()
ssobindingMixinHooks0 := ssobindingMixin[0].Hooks()
ssobinding.Hooks[0] = ssobindingMixinHooks0[0]
ssobindingMixinInters0 := ssobindingMixin[0].Interceptors()
ssobinding.Interceptors[0] = ssobindingMixinInters0[0]
ssobindingMixinFields0 := ssobindingMixin[0].Fields()
_ = ssobindingMixinFields0
ssobindingFields := schema.SsoBinding{}.Fields()
_ = ssobindingFields
// ssobindingDescCreatedAt is the schema descriptor for created_at field.
ssobindingDescCreatedAt := ssobindingMixinFields0[0].Descriptor()
// ssobinding.DefaultCreatedAt holds the default value on creation for the created_at field.
ssobinding.DefaultCreatedAt = ssobindingDescCreatedAt.Default.(func() time.Time)
// ssobindingDescUpdatedAt is the schema descriptor for updated_at field.
ssobindingDescUpdatedAt := ssobindingMixinFields0[1].Descriptor()
// ssobinding.DefaultUpdatedAt holds the default value on creation for the updated_at field.
ssobinding.DefaultUpdatedAt = ssobindingDescUpdatedAt.Default.(func() time.Time)
// ssobinding.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
ssobinding.UpdateDefaultUpdatedAt = ssobindingDescUpdatedAt.UpdateDefault.(func() time.Time)
// ssobindingDescProvider is the schema descriptor for provider field.
ssobindingDescProvider := ssobindingFields[0].Descriptor()
// ssobinding.ProviderValidator is a validator for the "provider" field. It is called by the builders before save.
ssobinding.ProviderValidator = func() func(string) error {
validators := ssobindingDescProvider.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(provider string) error {
for _, fn := range fns {
if err := fn(provider); err != nil {
return err
}
}
return nil
}
}()
// ssobindingDescSubject is the schema descriptor for subject field.
ssobindingDescSubject := ssobindingFields[1].Descriptor()
// ssobinding.SubjectValidator is a validator for the "subject" field. It is called by the builders before save.
ssobinding.SubjectValidator = func() func(string) error {
validators := ssobindingDescSubject.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(subject string) error {
for _, fn := range fns {
if err := fn(subject); err != nil {
return err
}
}
return nil
}
}()
storagepolicyMixin := schema.StoragePolicy{}.Mixin()
storagepolicyMixinHooks0 := storagepolicyMixin[0].Hooks()
storagepolicy.Hooks[0] = storagepolicyMixinHooks0[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"
)
// SsoBinding holds the schema definition for external identity bindings.
// Each row links a local user to a subject at an external sign-in provider
// (e.g. a QQ Connect openid). One binding per (provider, subject) and one
// binding per (user, provider).
type SsoBinding struct {
ent.Schema
}
// Fields of the SsoBinding.
func (SsoBinding) Fields() []ent.Field {
return []ent.Field{
field.String("provider").
MaxLen(32).
NotEmpty(),
field.String("subject").
MaxLen(128).
NotEmpty(),
field.Int("user_id"),
}
}
// Edges of the SsoBinding.
func (SsoBinding) Edges() []ent.Edge {
return []ent.Edge{
edge.From("user", User.Type).
Ref("sso_bindings").
Field("user_id").
Unique().
Required(),
}
}
// Indexes of the SsoBinding.
func (SsoBinding) Indexes() []ent.Index {
return []ent.Index{
index.Fields("provider", "subject").Unique(),
index.Fields("user_id", "provider").Unique(),
}
}
func (SsoBinding) Mixin() []ent.Mixin {
return []ent.Mixin{
CommonMixin{},
}
}

@ -73,6 +73,7 @@ func (User) Edges() []ent.Edge {
edge.To("redeemed_codes", GiftCode.Type),
edge.To("grants", UserGrant.Type),
edge.To("share_purchases", SharePurchase.Type),
edge.To("sso_bindings", SsoBinding.Type),
}
}

@ -0,0 +1,201 @@
// 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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SsoBinding is the model entity for the SsoBinding schema.
type SsoBinding 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"`
// Provider holds the value of the "provider" field.
Provider string `json:"provider,omitempty"`
// Subject holds the value of the "subject" field.
Subject string `json:"subject,omitempty"`
// UserID holds the value of the "user_id" field.
UserID int `json:"user_id,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the SsoBindingQuery when eager-loading is set.
Edges SsoBindingEdges `json:"edges"`
selectValues sql.SelectValues
}
// SsoBindingEdges holds the relations/edges for other nodes in the graph.
type SsoBindingEdges struct {
// User holds the value of the user edge.
User *User `json:"user,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// UserOrErr returns the User value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e SsoBindingEdges) UserOrErr() (*User, error) {
if e.loadedTypes[0] {
if e.User == nil {
// Edge was loaded but was not found.
return nil, &NotFoundError{label: user.Label}
}
return e.User, nil
}
return nil, &NotLoadedError{edge: "user"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*SsoBinding) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case ssobinding.FieldID, ssobinding.FieldUserID:
values[i] = new(sql.NullInt64)
case ssobinding.FieldProvider, ssobinding.FieldSubject:
values[i] = new(sql.NullString)
case ssobinding.FieldCreatedAt, ssobinding.FieldUpdatedAt, ssobinding.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 SsoBinding fields.
func (sb *SsoBinding) 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 ssobinding.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
sb.ID = int(value.Int64)
case ssobinding.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 {
sb.CreatedAt = value.Time
}
case ssobinding.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 {
sb.UpdatedAt = value.Time
}
case ssobinding.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 {
sb.DeletedAt = new(time.Time)
*sb.DeletedAt = value.Time
}
case ssobinding.FieldProvider:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider", values[i])
} else if value.Valid {
sb.Provider = value.String
}
case ssobinding.FieldSubject:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field subject", values[i])
} else if value.Valid {
sb.Subject = value.String
}
case ssobinding.FieldUserID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field user_id", values[i])
} else if value.Valid {
sb.UserID = int(value.Int64)
}
default:
sb.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the SsoBinding.
// This includes values selected through modifiers, order, etc.
func (sb *SsoBinding) Value(name string) (ent.Value, error) {
return sb.selectValues.Get(name)
}
// QueryUser queries the "user" edge of the SsoBinding entity.
func (sb *SsoBinding) QueryUser() *UserQuery {
return NewSsoBindingClient(sb.config).QueryUser(sb)
}
// Update returns a builder for updating this SsoBinding.
// Note that you need to call SsoBinding.Unwrap() before calling this method if this SsoBinding
// was returned from a transaction, and the transaction was committed or rolled back.
func (sb *SsoBinding) Update() *SsoBindingUpdateOne {
return NewSsoBindingClient(sb.config).UpdateOne(sb)
}
// Unwrap unwraps the SsoBinding 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 (sb *SsoBinding) Unwrap() *SsoBinding {
_tx, ok := sb.config.driver.(*txDriver)
if !ok {
panic("ent: SsoBinding is not a transactional entity")
}
sb.config.driver = _tx.drv
return sb
}
// String implements the fmt.Stringer.
func (sb *SsoBinding) String() string {
var builder strings.Builder
builder.WriteString("SsoBinding(")
builder.WriteString(fmt.Sprintf("id=%v, ", sb.ID))
builder.WriteString("created_at=")
builder.WriteString(sb.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(sb.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", ")
if v := sb.DeletedAt; v != nil {
builder.WriteString("deleted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("provider=")
builder.WriteString(sb.Provider)
builder.WriteString(", ")
builder.WriteString("subject=")
builder.WriteString(sb.Subject)
builder.WriteString(", ")
builder.WriteString("user_id=")
builder.WriteString(fmt.Sprintf("%v", sb.UserID))
builder.WriteByte(')')
return builder.String()
}
// SetUser manually set the edge as loaded state.
func (e *SsoBinding) SetUser(v *User) {
e.Edges.User = v
e.Edges.loadedTypes[0] = true
}
// SsoBindings is a parsable slice of SsoBinding.
type SsoBindings []*SsoBinding

@ -0,0 +1,134 @@
// Code generated by ent, DO NOT EDIT.
package ssobinding
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the ssobinding type in the database.
Label = "sso_binding"
// 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"
// FieldProvider holds the string denoting the provider field in the database.
FieldProvider = "provider"
// FieldSubject holds the string denoting the subject field in the database.
FieldSubject = "subject"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// Table holds the table name of the ssobinding in the database.
Table = "sso_bindings"
// UserTable is the table that holds the user relation/edge.
UserTable = "sso_bindings"
// UserInverseTable is the table name for the User entity.
// It exists in this package in order to avoid circular dependency with the "user" package.
UserInverseTable = "users"
// UserColumn is the table column denoting the user relation/edge.
UserColumn = "user_id"
)
// Columns holds all SQL columns for ssobinding fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldDeletedAt,
FieldProvider,
FieldSubject,
FieldUserID,
}
// 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
// ProviderValidator is a validator for the "provider" field. It is called by the builders before save.
ProviderValidator func(string) error
// SubjectValidator is a validator for the "subject" field. It is called by the builders before save.
SubjectValidator func(string) error
)
// OrderOption defines the ordering options for the SsoBinding 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()
}
// ByProvider orders the results by the provider field.
func ByProvider(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProvider, opts...).ToFunc()
}
// BySubject orders the results by the subject field.
func BySubject(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubject, opts...).ToFunc()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()
}
// ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
}
}
func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
}

@ -0,0 +1,404 @@
// Code generated by ent, DO NOT EDIT.
package ssobinding
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int) predicate.SsoBinding {
return predicate.SsoBinding(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.SsoBinding {
return predicate.SsoBinding(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.SsoBinding {
return predicate.SsoBinding(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.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldDeletedAt, v))
}
// Provider applies equality check predicate on the "provider" field. It's identical to ProviderEQ.
func Provider(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldProvider, v))
}
// Subject applies equality check predicate on the "subject" field. It's identical to SubjectEQ.
func Subject(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldSubject, v))
}
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
func UserID(v int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldUserID, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLTE(FieldUpdatedAt, v))
}
// DeletedAtEQ applies the EQ predicate on the "deleted_at" field.
func DeletedAtEQ(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldDeletedAt, v))
}
// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field.
func DeletedAtNEQ(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNEQ(FieldDeletedAt, v))
}
// DeletedAtIn applies the In predicate on the "deleted_at" field.
func DeletedAtIn(vs ...time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIn(FieldDeletedAt, vs...))
}
// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field.
func DeletedAtNotIn(vs ...time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotIn(FieldDeletedAt, vs...))
}
// DeletedAtGT applies the GT predicate on the "deleted_at" field.
func DeletedAtGT(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGT(FieldDeletedAt, v))
}
// DeletedAtGTE applies the GTE predicate on the "deleted_at" field.
func DeletedAtGTE(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGTE(FieldDeletedAt, v))
}
// DeletedAtLT applies the LT predicate on the "deleted_at" field.
func DeletedAtLT(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLT(FieldDeletedAt, v))
}
// DeletedAtLTE applies the LTE predicate on the "deleted_at" field.
func DeletedAtLTE(v time.Time) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLTE(FieldDeletedAt, v))
}
// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field.
func DeletedAtIsNil() predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIsNull(FieldDeletedAt))
}
// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field.
func DeletedAtNotNil() predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotNull(FieldDeletedAt))
}
// ProviderEQ applies the EQ predicate on the "provider" field.
func ProviderEQ(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldProvider, v))
}
// ProviderNEQ applies the NEQ predicate on the "provider" field.
func ProviderNEQ(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNEQ(FieldProvider, v))
}
// ProviderIn applies the In predicate on the "provider" field.
func ProviderIn(vs ...string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIn(FieldProvider, vs...))
}
// ProviderNotIn applies the NotIn predicate on the "provider" field.
func ProviderNotIn(vs ...string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotIn(FieldProvider, vs...))
}
// ProviderGT applies the GT predicate on the "provider" field.
func ProviderGT(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGT(FieldProvider, v))
}
// ProviderGTE applies the GTE predicate on the "provider" field.
func ProviderGTE(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGTE(FieldProvider, v))
}
// ProviderLT applies the LT predicate on the "provider" field.
func ProviderLT(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLT(FieldProvider, v))
}
// ProviderLTE applies the LTE predicate on the "provider" field.
func ProviderLTE(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLTE(FieldProvider, v))
}
// ProviderContains applies the Contains predicate on the "provider" field.
func ProviderContains(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldContains(FieldProvider, v))
}
// ProviderHasPrefix applies the HasPrefix predicate on the "provider" field.
func ProviderHasPrefix(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldHasPrefix(FieldProvider, v))
}
// ProviderHasSuffix applies the HasSuffix predicate on the "provider" field.
func ProviderHasSuffix(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldHasSuffix(FieldProvider, v))
}
// ProviderEqualFold applies the EqualFold predicate on the "provider" field.
func ProviderEqualFold(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEqualFold(FieldProvider, v))
}
// ProviderContainsFold applies the ContainsFold predicate on the "provider" field.
func ProviderContainsFold(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldContainsFold(FieldProvider, v))
}
// SubjectEQ applies the EQ predicate on the "subject" field.
func SubjectEQ(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldSubject, v))
}
// SubjectNEQ applies the NEQ predicate on the "subject" field.
func SubjectNEQ(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNEQ(FieldSubject, v))
}
// SubjectIn applies the In predicate on the "subject" field.
func SubjectIn(vs ...string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIn(FieldSubject, vs...))
}
// SubjectNotIn applies the NotIn predicate on the "subject" field.
func SubjectNotIn(vs ...string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotIn(FieldSubject, vs...))
}
// SubjectGT applies the GT predicate on the "subject" field.
func SubjectGT(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGT(FieldSubject, v))
}
// SubjectGTE applies the GTE predicate on the "subject" field.
func SubjectGTE(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldGTE(FieldSubject, v))
}
// SubjectLT applies the LT predicate on the "subject" field.
func SubjectLT(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLT(FieldSubject, v))
}
// SubjectLTE applies the LTE predicate on the "subject" field.
func SubjectLTE(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldLTE(FieldSubject, v))
}
// SubjectContains applies the Contains predicate on the "subject" field.
func SubjectContains(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldContains(FieldSubject, v))
}
// SubjectHasPrefix applies the HasPrefix predicate on the "subject" field.
func SubjectHasPrefix(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldHasPrefix(FieldSubject, v))
}
// SubjectHasSuffix applies the HasSuffix predicate on the "subject" field.
func SubjectHasSuffix(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldHasSuffix(FieldSubject, v))
}
// SubjectEqualFold applies the EqualFold predicate on the "subject" field.
func SubjectEqualFold(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEqualFold(FieldSubject, v))
}
// SubjectContainsFold applies the ContainsFold predicate on the "subject" field.
func SubjectContainsFold(v string) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldContainsFold(FieldSubject, v))
}
// UserIDEQ applies the EQ predicate on the "user_id" field.
func UserIDEQ(v int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldEQ(FieldUserID, v))
}
// UserIDNEQ applies the NEQ predicate on the "user_id" field.
func UserIDNEQ(v int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNEQ(FieldUserID, v))
}
// UserIDIn applies the In predicate on the "user_id" field.
func UserIDIn(vs ...int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldIn(FieldUserID, vs...))
}
// UserIDNotIn applies the NotIn predicate on the "user_id" field.
func UserIDNotIn(vs ...int) predicate.SsoBinding {
return predicate.SsoBinding(sql.FieldNotIn(FieldUserID, vs...))
}
// HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.SsoBinding {
return predicate.SsoBinding(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
func HasUserWith(preds ...predicate.User) predicate.SsoBinding {
return predicate.SsoBinding(func(s *sql.Selector) {
step := newUserStep()
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.SsoBinding) predicate.SsoBinding {
return predicate.SsoBinding(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.SsoBinding) predicate.SsoBinding {
return predicate.SsoBinding(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.SsoBinding) predicate.SsoBinding {
return predicate.SsoBinding(sql.NotPredicates(p))
}

@ -0,0 +1,800 @@
// 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/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SsoBindingCreate is the builder for creating a SsoBinding entity.
type SsoBindingCreate struct {
config
mutation *SsoBindingMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetCreatedAt sets the "created_at" field.
func (sbc *SsoBindingCreate) SetCreatedAt(t time.Time) *SsoBindingCreate {
sbc.mutation.SetCreatedAt(t)
return sbc
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (sbc *SsoBindingCreate) SetNillableCreatedAt(t *time.Time) *SsoBindingCreate {
if t != nil {
sbc.SetCreatedAt(*t)
}
return sbc
}
// SetUpdatedAt sets the "updated_at" field.
func (sbc *SsoBindingCreate) SetUpdatedAt(t time.Time) *SsoBindingCreate {
sbc.mutation.SetUpdatedAt(t)
return sbc
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (sbc *SsoBindingCreate) SetNillableUpdatedAt(t *time.Time) *SsoBindingCreate {
if t != nil {
sbc.SetUpdatedAt(*t)
}
return sbc
}
// SetDeletedAt sets the "deleted_at" field.
func (sbc *SsoBindingCreate) SetDeletedAt(t time.Time) *SsoBindingCreate {
sbc.mutation.SetDeletedAt(t)
return sbc
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (sbc *SsoBindingCreate) SetNillableDeletedAt(t *time.Time) *SsoBindingCreate {
if t != nil {
sbc.SetDeletedAt(*t)
}
return sbc
}
// SetProvider sets the "provider" field.
func (sbc *SsoBindingCreate) SetProvider(s string) *SsoBindingCreate {
sbc.mutation.SetProvider(s)
return sbc
}
// SetSubject sets the "subject" field.
func (sbc *SsoBindingCreate) SetSubject(s string) *SsoBindingCreate {
sbc.mutation.SetSubject(s)
return sbc
}
// SetUserID sets the "user_id" field.
func (sbc *SsoBindingCreate) SetUserID(i int) *SsoBindingCreate {
sbc.mutation.SetUserID(i)
return sbc
}
// SetUser sets the "user" edge to the User entity.
func (sbc *SsoBindingCreate) SetUser(u *User) *SsoBindingCreate {
return sbc.SetUserID(u.ID)
}
// Mutation returns the SsoBindingMutation object of the builder.
func (sbc *SsoBindingCreate) Mutation() *SsoBindingMutation {
return sbc.mutation
}
// Save creates the SsoBinding in the database.
func (sbc *SsoBindingCreate) Save(ctx context.Context) (*SsoBinding, error) {
if err := sbc.defaults(); err != nil {
return nil, err
}
return withHooks(ctx, sbc.sqlSave, sbc.mutation, sbc.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (sbc *SsoBindingCreate) SaveX(ctx context.Context) *SsoBinding {
v, err := sbc.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (sbc *SsoBindingCreate) Exec(ctx context.Context) error {
_, err := sbc.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (sbc *SsoBindingCreate) ExecX(ctx context.Context) {
if err := sbc.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (sbc *SsoBindingCreate) defaults() error {
if _, ok := sbc.mutation.CreatedAt(); !ok {
if ssobinding.DefaultCreatedAt == nil {
return fmt.Errorf("ent: uninitialized ssobinding.DefaultCreatedAt (forgotten import ent/runtime?)")
}
v := ssobinding.DefaultCreatedAt()
sbc.mutation.SetCreatedAt(v)
}
if _, ok := sbc.mutation.UpdatedAt(); !ok {
if ssobinding.DefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized ssobinding.DefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := ssobinding.DefaultUpdatedAt()
sbc.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (sbc *SsoBindingCreate) check() error {
if _, ok := sbc.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "SsoBinding.created_at"`)}
}
if _, ok := sbc.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "SsoBinding.updated_at"`)}
}
if _, ok := sbc.mutation.Provider(); !ok {
return &ValidationError{Name: "provider", err: errors.New(`ent: missing required field "SsoBinding.provider"`)}
}
if v, ok := sbc.mutation.Provider(); ok {
if err := ssobinding.ProviderValidator(v); err != nil {
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.provider": %w`, err)}
}
}
if _, ok := sbc.mutation.Subject(); !ok {
return &ValidationError{Name: "subject", err: errors.New(`ent: missing required field "SsoBinding.subject"`)}
}
if v, ok := sbc.mutation.Subject(); ok {
if err := ssobinding.SubjectValidator(v); err != nil {
return &ValidationError{Name: "subject", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.subject": %w`, err)}
}
}
if _, ok := sbc.mutation.UserID(); !ok {
return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "SsoBinding.user_id"`)}
}
if _, ok := sbc.mutation.UserID(); !ok {
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "SsoBinding.user"`)}
}
return nil
}
func (sbc *SsoBindingCreate) sqlSave(ctx context.Context) (*SsoBinding, error) {
if err := sbc.check(); err != nil {
return nil, err
}
_node, _spec := sbc.createSpec()
if err := sqlgraph.CreateNode(ctx, sbc.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)
sbc.mutation.id = &_node.ID
sbc.mutation.done = true
return _node, nil
}
func (sbc *SsoBindingCreate) createSpec() (*SsoBinding, *sqlgraph.CreateSpec) {
var (
_node = &SsoBinding{config: sbc.config}
_spec = sqlgraph.NewCreateSpec(ssobinding.Table, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
)
if id, ok := sbc.mutation.ID(); ok {
_node.ID = id
id64 := int64(id)
_spec.ID.Value = id64
}
_spec.OnConflict = sbc.conflict
if value, ok := sbc.mutation.CreatedAt(); ok {
_spec.SetField(ssobinding.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if value, ok := sbc.mutation.UpdatedAt(); ok {
_spec.SetField(ssobinding.FieldUpdatedAt, field.TypeTime, value)
_node.UpdatedAt = value
}
if value, ok := sbc.mutation.DeletedAt(); ok {
_spec.SetField(ssobinding.FieldDeletedAt, field.TypeTime, value)
_node.DeletedAt = &value
}
if value, ok := sbc.mutation.Provider(); ok {
_spec.SetField(ssobinding.FieldProvider, field.TypeString, value)
_node.Provider = value
}
if value, ok := sbc.mutation.Subject(); ok {
_spec.SetField(ssobinding.FieldSubject, field.TypeString, value)
_node.Subject = value
}
if nodes := sbc.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: ssobinding.UserTable,
Columns: []string{ssobinding.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.UserID = 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.SsoBinding.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.SsoBindingUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (sbc *SsoBindingCreate) OnConflict(opts ...sql.ConflictOption) *SsoBindingUpsertOne {
sbc.conflict = opts
return &SsoBindingUpsertOne{
create: sbc,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.SsoBinding.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (sbc *SsoBindingCreate) OnConflictColumns(columns ...string) *SsoBindingUpsertOne {
sbc.conflict = append(sbc.conflict, sql.ConflictColumns(columns...))
return &SsoBindingUpsertOne{
create: sbc,
}
}
type (
// SsoBindingUpsertOne is the builder for "upsert"-ing
// one SsoBinding node.
SsoBindingUpsertOne struct {
create *SsoBindingCreate
}
// SsoBindingUpsert is the "OnConflict" setter.
SsoBindingUpsert struct {
*sql.UpdateSet
}
)
// SetUpdatedAt sets the "updated_at" field.
func (u *SsoBindingUpsert) SetUpdatedAt(v time.Time) *SsoBindingUpsert {
u.Set(ssobinding.FieldUpdatedAt, v)
return u
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *SsoBindingUpsert) UpdateUpdatedAt() *SsoBindingUpsert {
u.SetExcluded(ssobinding.FieldUpdatedAt)
return u
}
// SetDeletedAt sets the "deleted_at" field.
func (u *SsoBindingUpsert) SetDeletedAt(v time.Time) *SsoBindingUpsert {
u.Set(ssobinding.FieldDeletedAt, v)
return u
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *SsoBindingUpsert) UpdateDeletedAt() *SsoBindingUpsert {
u.SetExcluded(ssobinding.FieldDeletedAt)
return u
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *SsoBindingUpsert) ClearDeletedAt() *SsoBindingUpsert {
u.SetNull(ssobinding.FieldDeletedAt)
return u
}
// SetProvider sets the "provider" field.
func (u *SsoBindingUpsert) SetProvider(v string) *SsoBindingUpsert {
u.Set(ssobinding.FieldProvider, v)
return u
}
// UpdateProvider sets the "provider" field to the value that was provided on create.
func (u *SsoBindingUpsert) UpdateProvider() *SsoBindingUpsert {
u.SetExcluded(ssobinding.FieldProvider)
return u
}
// SetSubject sets the "subject" field.
func (u *SsoBindingUpsert) SetSubject(v string) *SsoBindingUpsert {
u.Set(ssobinding.FieldSubject, v)
return u
}
// UpdateSubject sets the "subject" field to the value that was provided on create.
func (u *SsoBindingUpsert) UpdateSubject() *SsoBindingUpsert {
u.SetExcluded(ssobinding.FieldSubject)
return u
}
// SetUserID sets the "user_id" field.
func (u *SsoBindingUpsert) SetUserID(v int) *SsoBindingUpsert {
u.Set(ssobinding.FieldUserID, v)
return u
}
// UpdateUserID sets the "user_id" field to the value that was provided on create.
func (u *SsoBindingUpsert) UpdateUserID() *SsoBindingUpsert {
u.SetExcluded(ssobinding.FieldUserID)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.SsoBinding.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *SsoBindingUpsertOne) UpdateNewValues() *SsoBindingUpsertOne {
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(ssobinding.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.SsoBinding.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *SsoBindingUpsertOne) Ignore() *SsoBindingUpsertOne {
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 *SsoBindingUpsertOne) DoNothing() *SsoBindingUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the SsoBindingCreate.OnConflict
// documentation for more info.
func (u *SsoBindingUpsertOne) Update(set func(*SsoBindingUpsert)) *SsoBindingUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&SsoBindingUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *SsoBindingUpsertOne) SetUpdatedAt(v time.Time) *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *SsoBindingUpsertOne) UpdateUpdatedAt() *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateUpdatedAt()
})
}
// SetDeletedAt sets the "deleted_at" field.
func (u *SsoBindingUpsertOne) SetDeletedAt(v time.Time) *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.SetDeletedAt(v)
})
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *SsoBindingUpsertOne) UpdateDeletedAt() *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateDeletedAt()
})
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *SsoBindingUpsertOne) ClearDeletedAt() *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.ClearDeletedAt()
})
}
// SetProvider sets the "provider" field.
func (u *SsoBindingUpsertOne) SetProvider(v string) *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.SetProvider(v)
})
}
// UpdateProvider sets the "provider" field to the value that was provided on create.
func (u *SsoBindingUpsertOne) UpdateProvider() *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateProvider()
})
}
// SetSubject sets the "subject" field.
func (u *SsoBindingUpsertOne) SetSubject(v string) *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.SetSubject(v)
})
}
// UpdateSubject sets the "subject" field to the value that was provided on create.
func (u *SsoBindingUpsertOne) UpdateSubject() *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateSubject()
})
}
// SetUserID sets the "user_id" field.
func (u *SsoBindingUpsertOne) SetUserID(v int) *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.SetUserID(v)
})
}
// UpdateUserID sets the "user_id" field to the value that was provided on create.
func (u *SsoBindingUpsertOne) UpdateUserID() *SsoBindingUpsertOne {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateUserID()
})
}
// Exec executes the query.
func (u *SsoBindingUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for SsoBindingCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *SsoBindingUpsertOne) 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 *SsoBindingUpsertOne) 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 *SsoBindingUpsertOne) IDX(ctx context.Context) int {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
func (m *SsoBindingCreate) SetRawID(t int) *SsoBindingCreate {
m.mutation.SetRawID(t)
return m
}
// SsoBindingCreateBulk is the builder for creating many SsoBinding entities in bulk.
type SsoBindingCreateBulk struct {
config
err error
builders []*SsoBindingCreate
conflict []sql.ConflictOption
}
// Save creates the SsoBinding entities in the database.
func (sbcb *SsoBindingCreateBulk) Save(ctx context.Context) ([]*SsoBinding, error) {
if sbcb.err != nil {
return nil, sbcb.err
}
specs := make([]*sqlgraph.CreateSpec, len(sbcb.builders))
nodes := make([]*SsoBinding, len(sbcb.builders))
mutators := make([]Mutator, len(sbcb.builders))
for i := range sbcb.builders {
func(i int, root context.Context) {
builder := sbcb.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*SsoBindingMutation)
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, sbcb.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
spec.OnConflict = sbcb.conflict
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, sbcb.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, sbcb.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (sbcb *SsoBindingCreateBulk) SaveX(ctx context.Context) []*SsoBinding {
v, err := sbcb.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (sbcb *SsoBindingCreateBulk) Exec(ctx context.Context) error {
_, err := sbcb.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (sbcb *SsoBindingCreateBulk) ExecX(ctx context.Context) {
if err := sbcb.Exec(ctx); err != nil {
panic(err)
}
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.SsoBinding.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.SsoBindingUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (sbcb *SsoBindingCreateBulk) OnConflict(opts ...sql.ConflictOption) *SsoBindingUpsertBulk {
sbcb.conflict = opts
return &SsoBindingUpsertBulk{
create: sbcb,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.SsoBinding.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (sbcb *SsoBindingCreateBulk) OnConflictColumns(columns ...string) *SsoBindingUpsertBulk {
sbcb.conflict = append(sbcb.conflict, sql.ConflictColumns(columns...))
return &SsoBindingUpsertBulk{
create: sbcb,
}
}
// SsoBindingUpsertBulk is the builder for "upsert"-ing
// a bulk of SsoBinding nodes.
type SsoBindingUpsertBulk struct {
create *SsoBindingCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.SsoBinding.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *SsoBindingUpsertBulk) UpdateNewValues() *SsoBindingUpsertBulk {
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(ssobinding.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.SsoBinding.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *SsoBindingUpsertBulk) Ignore() *SsoBindingUpsertBulk {
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 *SsoBindingUpsertBulk) DoNothing() *SsoBindingUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the SsoBindingCreateBulk.OnConflict
// documentation for more info.
func (u *SsoBindingUpsertBulk) Update(set func(*SsoBindingUpsert)) *SsoBindingUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&SsoBindingUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *SsoBindingUpsertBulk) SetUpdatedAt(v time.Time) *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *SsoBindingUpsertBulk) UpdateUpdatedAt() *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateUpdatedAt()
})
}
// SetDeletedAt sets the "deleted_at" field.
func (u *SsoBindingUpsertBulk) SetDeletedAt(v time.Time) *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.SetDeletedAt(v)
})
}
// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
func (u *SsoBindingUpsertBulk) UpdateDeletedAt() *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateDeletedAt()
})
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (u *SsoBindingUpsertBulk) ClearDeletedAt() *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.ClearDeletedAt()
})
}
// SetProvider sets the "provider" field.
func (u *SsoBindingUpsertBulk) SetProvider(v string) *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.SetProvider(v)
})
}
// UpdateProvider sets the "provider" field to the value that was provided on create.
func (u *SsoBindingUpsertBulk) UpdateProvider() *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateProvider()
})
}
// SetSubject sets the "subject" field.
func (u *SsoBindingUpsertBulk) SetSubject(v string) *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.SetSubject(v)
})
}
// UpdateSubject sets the "subject" field to the value that was provided on create.
func (u *SsoBindingUpsertBulk) UpdateSubject() *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateSubject()
})
}
// SetUserID sets the "user_id" field.
func (u *SsoBindingUpsertBulk) SetUserID(v int) *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.SetUserID(v)
})
}
// UpdateUserID sets the "user_id" field to the value that was provided on create.
func (u *SsoBindingUpsertBulk) UpdateUserID() *SsoBindingUpsertBulk {
return u.Update(func(s *SsoBindingUpsert) {
s.UpdateUserID()
})
}
// Exec executes the query.
func (u *SsoBindingUpsertBulk) 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 SsoBindingCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for SsoBindingCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *SsoBindingUpsertBulk) 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/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
)
// SsoBindingDelete is the builder for deleting a SsoBinding entity.
type SsoBindingDelete struct {
config
hooks []Hook
mutation *SsoBindingMutation
}
// Where appends a list predicates to the SsoBindingDelete builder.
func (sbd *SsoBindingDelete) Where(ps ...predicate.SsoBinding) *SsoBindingDelete {
sbd.mutation.Where(ps...)
return sbd
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (sbd *SsoBindingDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, sbd.sqlExec, sbd.mutation, sbd.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (sbd *SsoBindingDelete) ExecX(ctx context.Context) int {
n, err := sbd.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (sbd *SsoBindingDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(ssobinding.Table, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
if ps := sbd.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, sbd.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
sbd.mutation.done = true
return affected, err
}
// SsoBindingDeleteOne is the builder for deleting a single SsoBinding entity.
type SsoBindingDeleteOne struct {
sbd *SsoBindingDelete
}
// Where appends a list predicates to the SsoBindingDelete builder.
func (sbdo *SsoBindingDeleteOne) Where(ps ...predicate.SsoBinding) *SsoBindingDeleteOne {
sbdo.sbd.mutation.Where(ps...)
return sbdo
}
// Exec executes the deletion query.
func (sbdo *SsoBindingDeleteOne) Exec(ctx context.Context) error {
n, err := sbdo.sbd.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{ssobinding.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (sbdo *SsoBindingDeleteOne) ExecX(ctx context.Context) {
if err := sbdo.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/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SsoBindingQuery is the builder for querying SsoBinding entities.
type SsoBindingQuery struct {
config
ctx *QueryContext
order []ssobinding.OrderOption
inters []Interceptor
predicates []predicate.SsoBinding
withUser *UserQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the SsoBindingQuery builder.
func (sbq *SsoBindingQuery) Where(ps ...predicate.SsoBinding) *SsoBindingQuery {
sbq.predicates = append(sbq.predicates, ps...)
return sbq
}
// Limit the number of records to be returned by this query.
func (sbq *SsoBindingQuery) Limit(limit int) *SsoBindingQuery {
sbq.ctx.Limit = &limit
return sbq
}
// Offset to start from.
func (sbq *SsoBindingQuery) Offset(offset int) *SsoBindingQuery {
sbq.ctx.Offset = &offset
return sbq
}
// 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 (sbq *SsoBindingQuery) Unique(unique bool) *SsoBindingQuery {
sbq.ctx.Unique = &unique
return sbq
}
// Order specifies how the records should be ordered.
func (sbq *SsoBindingQuery) Order(o ...ssobinding.OrderOption) *SsoBindingQuery {
sbq.order = append(sbq.order, o...)
return sbq
}
// QueryUser chains the current query on the "user" edge.
func (sbq *SsoBindingQuery) QueryUser() *UserQuery {
query := (&UserClient{config: sbq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := sbq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := sbq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(ssobinding.Table, ssobinding.FieldID, selector),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ssobinding.UserTable, ssobinding.UserColumn),
)
fromU = sqlgraph.SetNeighbors(sbq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first SsoBinding entity from the query.
// Returns a *NotFoundError when no SsoBinding was found.
func (sbq *SsoBindingQuery) First(ctx context.Context) (*SsoBinding, error) {
nodes, err := sbq.Limit(1).All(setContextOp(ctx, sbq.ctx, "First"))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{ssobinding.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (sbq *SsoBindingQuery) FirstX(ctx context.Context) *SsoBinding {
node, err := sbq.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first SsoBinding ID from the query.
// Returns a *NotFoundError when no SsoBinding ID was found.
func (sbq *SsoBindingQuery) FirstID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = sbq.Limit(1).IDs(setContextOp(ctx, sbq.ctx, "FirstID")); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{ssobinding.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (sbq *SsoBindingQuery) FirstIDX(ctx context.Context) int {
id, err := sbq.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single SsoBinding entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one SsoBinding entity is found.
// Returns a *NotFoundError when no SsoBinding entities are found.
func (sbq *SsoBindingQuery) Only(ctx context.Context) (*SsoBinding, error) {
nodes, err := sbq.Limit(2).All(setContextOp(ctx, sbq.ctx, "Only"))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{ssobinding.Label}
default:
return nil, &NotSingularError{ssobinding.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (sbq *SsoBindingQuery) OnlyX(ctx context.Context) *SsoBinding {
node, err := sbq.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only SsoBinding ID in the query.
// Returns a *NotSingularError when more than one SsoBinding ID is found.
// Returns a *NotFoundError when no entities are found.
func (sbq *SsoBindingQuery) OnlyID(ctx context.Context) (id int, err error) {
var ids []int
if ids, err = sbq.Limit(2).IDs(setContextOp(ctx, sbq.ctx, "OnlyID")); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{ssobinding.Label}
default:
err = &NotSingularError{ssobinding.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (sbq *SsoBindingQuery) OnlyIDX(ctx context.Context) int {
id, err := sbq.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of SsoBindings.
func (sbq *SsoBindingQuery) All(ctx context.Context) ([]*SsoBinding, error) {
ctx = setContextOp(ctx, sbq.ctx, "All")
if err := sbq.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*SsoBinding, *SsoBindingQuery]()
return withInterceptors[[]*SsoBinding](ctx, sbq, qr, sbq.inters)
}
// AllX is like All, but panics if an error occurs.
func (sbq *SsoBindingQuery) AllX(ctx context.Context) []*SsoBinding {
nodes, err := sbq.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of SsoBinding IDs.
func (sbq *SsoBindingQuery) IDs(ctx context.Context) (ids []int, err error) {
if sbq.ctx.Unique == nil && sbq.path != nil {
sbq.Unique(true)
}
ctx = setContextOp(ctx, sbq.ctx, "IDs")
if err = sbq.Select(ssobinding.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (sbq *SsoBindingQuery) IDsX(ctx context.Context) []int {
ids, err := sbq.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (sbq *SsoBindingQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, sbq.ctx, "Count")
if err := sbq.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, sbq, querierCount[*SsoBindingQuery](), sbq.inters)
}
// CountX is like Count, but panics if an error occurs.
func (sbq *SsoBindingQuery) CountX(ctx context.Context) int {
count, err := sbq.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (sbq *SsoBindingQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, sbq.ctx, "Exist")
switch _, err := sbq.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 (sbq *SsoBindingQuery) ExistX(ctx context.Context) bool {
exist, err := sbq.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the SsoBindingQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (sbq *SsoBindingQuery) Clone() *SsoBindingQuery {
if sbq == nil {
return nil
}
return &SsoBindingQuery{
config: sbq.config,
ctx: sbq.ctx.Clone(),
order: append([]ssobinding.OrderOption{}, sbq.order...),
inters: append([]Interceptor{}, sbq.inters...),
predicates: append([]predicate.SsoBinding{}, sbq.predicates...),
withUser: sbq.withUser.Clone(),
// clone intermediate query.
sql: sbq.sql.Clone(),
path: sbq.path,
}
}
// WithUser tells the query-builder to eager-load the nodes that are connected to
// the "user" edge. The optional arguments are used to configure the query builder of the edge.
func (sbq *SsoBindingQuery) WithUser(opts ...func(*UserQuery)) *SsoBindingQuery {
query := (&UserClient{config: sbq.config}).Query()
for _, opt := range opts {
opt(query)
}
sbq.withUser = query
return sbq
}
// 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.SsoBinding.Query().
// GroupBy(ssobinding.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (sbq *SsoBindingQuery) GroupBy(field string, fields ...string) *SsoBindingGroupBy {
sbq.ctx.Fields = append([]string{field}, fields...)
grbuild := &SsoBindingGroupBy{build: sbq}
grbuild.flds = &sbq.ctx.Fields
grbuild.label = ssobinding.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.SsoBinding.Query().
// Select(ssobinding.FieldCreatedAt).
// Scan(ctx, &v)
func (sbq *SsoBindingQuery) Select(fields ...string) *SsoBindingSelect {
sbq.ctx.Fields = append(sbq.ctx.Fields, fields...)
sbuild := &SsoBindingSelect{SsoBindingQuery: sbq}
sbuild.label = ssobinding.Label
sbuild.flds, sbuild.scan = &sbq.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a SsoBindingSelect configured with the given aggregations.
func (sbq *SsoBindingQuery) Aggregate(fns ...AggregateFunc) *SsoBindingSelect {
return sbq.Select().Aggregate(fns...)
}
func (sbq *SsoBindingQuery) prepareQuery(ctx context.Context) error {
for _, inter := range sbq.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, sbq); err != nil {
return err
}
}
}
for _, f := range sbq.ctx.Fields {
if !ssobinding.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if sbq.path != nil {
prev, err := sbq.path(ctx)
if err != nil {
return err
}
sbq.sql = prev
}
return nil
}
func (sbq *SsoBindingQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SsoBinding, error) {
var (
nodes = []*SsoBinding{}
_spec = sbq.querySpec()
loadedTypes = [1]bool{
sbq.withUser != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*SsoBinding).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &SsoBinding{config: sbq.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, sbq.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := sbq.withUser; query != nil {
if err := sbq.loadUser(ctx, query, nodes, nil,
func(n *SsoBinding, e *User) { n.Edges.User = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (sbq *SsoBindingQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*SsoBinding, init func(*SsoBinding), assign func(*SsoBinding, *User)) error {
ids := make([]int, 0, len(nodes))
nodeids := make(map[int][]*SsoBinding)
for i := range nodes {
fk := nodes[i].UserID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(user.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 "user_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (sbq *SsoBindingQuery) sqlCount(ctx context.Context) (int, error) {
_spec := sbq.querySpec()
_spec.Node.Columns = sbq.ctx.Fields
if len(sbq.ctx.Fields) > 0 {
_spec.Unique = sbq.ctx.Unique != nil && *sbq.ctx.Unique
}
return sqlgraph.CountNodes(ctx, sbq.driver, _spec)
}
func (sbq *SsoBindingQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(ssobinding.Table, ssobinding.Columns, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
_spec.From = sbq.sql
if unique := sbq.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if sbq.path != nil {
_spec.Unique = true
}
if fields := sbq.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, ssobinding.FieldID)
for i := range fields {
if fields[i] != ssobinding.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if sbq.withUser != nil {
_spec.Node.AddColumnOnce(ssobinding.FieldUserID)
}
}
if ps := sbq.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := sbq.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := sbq.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := sbq.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (sbq *SsoBindingQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(sbq.driver.Dialect())
t1 := builder.Table(ssobinding.Table)
columns := sbq.ctx.Fields
if len(columns) == 0 {
columns = ssobinding.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if sbq.sql != nil {
selector = sbq.sql
selector.Select(selector.Columns(columns...)...)
}
if sbq.ctx.Unique != nil && *sbq.ctx.Unique {
selector.Distinct()
}
for _, p := range sbq.predicates {
p(selector)
}
for _, p := range sbq.order {
p(selector)
}
if offset := sbq.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 := sbq.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// SsoBindingGroupBy is the group-by builder for SsoBinding entities.
type SsoBindingGroupBy struct {
selector
build *SsoBindingQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (sbgb *SsoBindingGroupBy) Aggregate(fns ...AggregateFunc) *SsoBindingGroupBy {
sbgb.fns = append(sbgb.fns, fns...)
return sbgb
}
// Scan applies the selector query and scans the result into the given value.
func (sbgb *SsoBindingGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, sbgb.build.ctx, "GroupBy")
if err := sbgb.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*SsoBindingQuery, *SsoBindingGroupBy](ctx, sbgb.build, sbgb, sbgb.build.inters, v)
}
func (sbgb *SsoBindingGroupBy) sqlScan(ctx context.Context, root *SsoBindingQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(sbgb.fns))
for _, fn := range sbgb.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*sbgb.flds)+len(sbgb.fns))
for _, f := range *sbgb.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*sbgb.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := sbgb.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// SsoBindingSelect is the builder for selecting fields of SsoBinding entities.
type SsoBindingSelect struct {
*SsoBindingQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (sbs *SsoBindingSelect) Aggregate(fns ...AggregateFunc) *SsoBindingSelect {
sbs.fns = append(sbs.fns, fns...)
return sbs
}
// Scan applies the selector query and scans the result into the given value.
func (sbs *SsoBindingSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, sbs.ctx, "Select")
if err := sbs.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*SsoBindingQuery, *SsoBindingSelect](ctx, sbs.SsoBindingQuery, sbs, sbs.inters, v)
}
func (sbs *SsoBindingSelect) sqlScan(ctx context.Context, root *SsoBindingQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(sbs.fns))
for _, fn := range sbs.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*sbs.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 := sbs.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

@ -0,0 +1,495 @@
// 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/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/user"
)
// SsoBindingUpdate is the builder for updating SsoBinding entities.
type SsoBindingUpdate struct {
config
hooks []Hook
mutation *SsoBindingMutation
}
// Where appends a list predicates to the SsoBindingUpdate builder.
func (sbu *SsoBindingUpdate) Where(ps ...predicate.SsoBinding) *SsoBindingUpdate {
sbu.mutation.Where(ps...)
return sbu
}
// SetUpdatedAt sets the "updated_at" field.
func (sbu *SsoBindingUpdate) SetUpdatedAt(t time.Time) *SsoBindingUpdate {
sbu.mutation.SetUpdatedAt(t)
return sbu
}
// SetDeletedAt sets the "deleted_at" field.
func (sbu *SsoBindingUpdate) SetDeletedAt(t time.Time) *SsoBindingUpdate {
sbu.mutation.SetDeletedAt(t)
return sbu
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (sbu *SsoBindingUpdate) SetNillableDeletedAt(t *time.Time) *SsoBindingUpdate {
if t != nil {
sbu.SetDeletedAt(*t)
}
return sbu
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (sbu *SsoBindingUpdate) ClearDeletedAt() *SsoBindingUpdate {
sbu.mutation.ClearDeletedAt()
return sbu
}
// SetProvider sets the "provider" field.
func (sbu *SsoBindingUpdate) SetProvider(s string) *SsoBindingUpdate {
sbu.mutation.SetProvider(s)
return sbu
}
// SetNillableProvider sets the "provider" field if the given value is not nil.
func (sbu *SsoBindingUpdate) SetNillableProvider(s *string) *SsoBindingUpdate {
if s != nil {
sbu.SetProvider(*s)
}
return sbu
}
// SetSubject sets the "subject" field.
func (sbu *SsoBindingUpdate) SetSubject(s string) *SsoBindingUpdate {
sbu.mutation.SetSubject(s)
return sbu
}
// SetNillableSubject sets the "subject" field if the given value is not nil.
func (sbu *SsoBindingUpdate) SetNillableSubject(s *string) *SsoBindingUpdate {
if s != nil {
sbu.SetSubject(*s)
}
return sbu
}
// SetUserID sets the "user_id" field.
func (sbu *SsoBindingUpdate) SetUserID(i int) *SsoBindingUpdate {
sbu.mutation.SetUserID(i)
return sbu
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (sbu *SsoBindingUpdate) SetNillableUserID(i *int) *SsoBindingUpdate {
if i != nil {
sbu.SetUserID(*i)
}
return sbu
}
// SetUser sets the "user" edge to the User entity.
func (sbu *SsoBindingUpdate) SetUser(u *User) *SsoBindingUpdate {
return sbu.SetUserID(u.ID)
}
// Mutation returns the SsoBindingMutation object of the builder.
func (sbu *SsoBindingUpdate) Mutation() *SsoBindingMutation {
return sbu.mutation
}
// ClearUser clears the "user" edge to the User entity.
func (sbu *SsoBindingUpdate) ClearUser() *SsoBindingUpdate {
sbu.mutation.ClearUser()
return sbu
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (sbu *SsoBindingUpdate) Save(ctx context.Context) (int, error) {
if err := sbu.defaults(); err != nil {
return 0, err
}
return withHooks(ctx, sbu.sqlSave, sbu.mutation, sbu.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (sbu *SsoBindingUpdate) SaveX(ctx context.Context) int {
affected, err := sbu.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (sbu *SsoBindingUpdate) Exec(ctx context.Context) error {
_, err := sbu.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (sbu *SsoBindingUpdate) ExecX(ctx context.Context) {
if err := sbu.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (sbu *SsoBindingUpdate) defaults() error {
if _, ok := sbu.mutation.UpdatedAt(); !ok {
if ssobinding.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized ssobinding.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := ssobinding.UpdateDefaultUpdatedAt()
sbu.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (sbu *SsoBindingUpdate) check() error {
if v, ok := sbu.mutation.Provider(); ok {
if err := ssobinding.ProviderValidator(v); err != nil {
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.provider": %w`, err)}
}
}
if v, ok := sbu.mutation.Subject(); ok {
if err := ssobinding.SubjectValidator(v); err != nil {
return &ValidationError{Name: "subject", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.subject": %w`, err)}
}
}
if _, ok := sbu.mutation.UserID(); sbu.mutation.UserCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "SsoBinding.user"`)
}
return nil
}
func (sbu *SsoBindingUpdate) sqlSave(ctx context.Context) (n int, err error) {
if err := sbu.check(); err != nil {
return n, err
}
_spec := sqlgraph.NewUpdateSpec(ssobinding.Table, ssobinding.Columns, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
if ps := sbu.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := sbu.mutation.UpdatedAt(); ok {
_spec.SetField(ssobinding.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := sbu.mutation.DeletedAt(); ok {
_spec.SetField(ssobinding.FieldDeletedAt, field.TypeTime, value)
}
if sbu.mutation.DeletedAtCleared() {
_spec.ClearField(ssobinding.FieldDeletedAt, field.TypeTime)
}
if value, ok := sbu.mutation.Provider(); ok {
_spec.SetField(ssobinding.FieldProvider, field.TypeString, value)
}
if value, ok := sbu.mutation.Subject(); ok {
_spec.SetField(ssobinding.FieldSubject, field.TypeString, value)
}
if sbu.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: ssobinding.UserTable,
Columns: []string{ssobinding.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := sbu.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: ssobinding.UserTable,
Columns: []string{ssobinding.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.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, sbu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{ssobinding.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
sbu.mutation.done = true
return n, nil
}
// SsoBindingUpdateOne is the builder for updating a single SsoBinding entity.
type SsoBindingUpdateOne struct {
config
fields []string
hooks []Hook
mutation *SsoBindingMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (sbuo *SsoBindingUpdateOne) SetUpdatedAt(t time.Time) *SsoBindingUpdateOne {
sbuo.mutation.SetUpdatedAt(t)
return sbuo
}
// SetDeletedAt sets the "deleted_at" field.
func (sbuo *SsoBindingUpdateOne) SetDeletedAt(t time.Time) *SsoBindingUpdateOne {
sbuo.mutation.SetDeletedAt(t)
return sbuo
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (sbuo *SsoBindingUpdateOne) SetNillableDeletedAt(t *time.Time) *SsoBindingUpdateOne {
if t != nil {
sbuo.SetDeletedAt(*t)
}
return sbuo
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (sbuo *SsoBindingUpdateOne) ClearDeletedAt() *SsoBindingUpdateOne {
sbuo.mutation.ClearDeletedAt()
return sbuo
}
// SetProvider sets the "provider" field.
func (sbuo *SsoBindingUpdateOne) SetProvider(s string) *SsoBindingUpdateOne {
sbuo.mutation.SetProvider(s)
return sbuo
}
// SetNillableProvider sets the "provider" field if the given value is not nil.
func (sbuo *SsoBindingUpdateOne) SetNillableProvider(s *string) *SsoBindingUpdateOne {
if s != nil {
sbuo.SetProvider(*s)
}
return sbuo
}
// SetSubject sets the "subject" field.
func (sbuo *SsoBindingUpdateOne) SetSubject(s string) *SsoBindingUpdateOne {
sbuo.mutation.SetSubject(s)
return sbuo
}
// SetNillableSubject sets the "subject" field if the given value is not nil.
func (sbuo *SsoBindingUpdateOne) SetNillableSubject(s *string) *SsoBindingUpdateOne {
if s != nil {
sbuo.SetSubject(*s)
}
return sbuo
}
// SetUserID sets the "user_id" field.
func (sbuo *SsoBindingUpdateOne) SetUserID(i int) *SsoBindingUpdateOne {
sbuo.mutation.SetUserID(i)
return sbuo
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (sbuo *SsoBindingUpdateOne) SetNillableUserID(i *int) *SsoBindingUpdateOne {
if i != nil {
sbuo.SetUserID(*i)
}
return sbuo
}
// SetUser sets the "user" edge to the User entity.
func (sbuo *SsoBindingUpdateOne) SetUser(u *User) *SsoBindingUpdateOne {
return sbuo.SetUserID(u.ID)
}
// Mutation returns the SsoBindingMutation object of the builder.
func (sbuo *SsoBindingUpdateOne) Mutation() *SsoBindingMutation {
return sbuo.mutation
}
// ClearUser clears the "user" edge to the User entity.
func (sbuo *SsoBindingUpdateOne) ClearUser() *SsoBindingUpdateOne {
sbuo.mutation.ClearUser()
return sbuo
}
// Where appends a list predicates to the SsoBindingUpdate builder.
func (sbuo *SsoBindingUpdateOne) Where(ps ...predicate.SsoBinding) *SsoBindingUpdateOne {
sbuo.mutation.Where(ps...)
return sbuo
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (sbuo *SsoBindingUpdateOne) Select(field string, fields ...string) *SsoBindingUpdateOne {
sbuo.fields = append([]string{field}, fields...)
return sbuo
}
// Save executes the query and returns the updated SsoBinding entity.
func (sbuo *SsoBindingUpdateOne) Save(ctx context.Context) (*SsoBinding, error) {
if err := sbuo.defaults(); err != nil {
return nil, err
}
return withHooks(ctx, sbuo.sqlSave, sbuo.mutation, sbuo.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (sbuo *SsoBindingUpdateOne) SaveX(ctx context.Context) *SsoBinding {
node, err := sbuo.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (sbuo *SsoBindingUpdateOne) Exec(ctx context.Context) error {
_, err := sbuo.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (sbuo *SsoBindingUpdateOne) ExecX(ctx context.Context) {
if err := sbuo.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (sbuo *SsoBindingUpdateOne) defaults() error {
if _, ok := sbuo.mutation.UpdatedAt(); !ok {
if ssobinding.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized ssobinding.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := ssobinding.UpdateDefaultUpdatedAt()
sbuo.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (sbuo *SsoBindingUpdateOne) check() error {
if v, ok := sbuo.mutation.Provider(); ok {
if err := ssobinding.ProviderValidator(v); err != nil {
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.provider": %w`, err)}
}
}
if v, ok := sbuo.mutation.Subject(); ok {
if err := ssobinding.SubjectValidator(v); err != nil {
return &ValidationError{Name: "subject", err: fmt.Errorf(`ent: validator failed for field "SsoBinding.subject": %w`, err)}
}
}
if _, ok := sbuo.mutation.UserID(); sbuo.mutation.UserCleared() && !ok {
return errors.New(`ent: clearing a required unique edge "SsoBinding.user"`)
}
return nil
}
func (sbuo *SsoBindingUpdateOne) sqlSave(ctx context.Context) (_node *SsoBinding, err error) {
if err := sbuo.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(ssobinding.Table, ssobinding.Columns, sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt))
id, ok := sbuo.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SsoBinding.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := sbuo.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, ssobinding.FieldID)
for _, f := range fields {
if !ssobinding.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != ssobinding.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := sbuo.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := sbuo.mutation.UpdatedAt(); ok {
_spec.SetField(ssobinding.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := sbuo.mutation.DeletedAt(); ok {
_spec.SetField(ssobinding.FieldDeletedAt, field.TypeTime, value)
}
if sbuo.mutation.DeletedAtCleared() {
_spec.ClearField(ssobinding.FieldDeletedAt, field.TypeTime)
}
if value, ok := sbuo.mutation.Provider(); ok {
_spec.SetField(ssobinding.FieldProvider, field.TypeString, value)
}
if value, ok := sbuo.mutation.Subject(); ok {
_spec.SetField(ssobinding.FieldSubject, field.TypeString, value)
}
if sbuo.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: ssobinding.UserTable,
Columns: []string{ssobinding.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := sbuo.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: ssobinding.UserTable,
Columns: []string{ssobinding.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &SsoBinding{config: sbuo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, sbuo.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{ssobinding.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
sbuo.mutation.done = true
return _node, nil
}

@ -56,6 +56,8 @@ type Tx struct {
SharePurchase *SharePurchaseClient
// Sku is the client for interacting with the Sku builders.
Sku *SkuClient
// SsoBinding is the client for interacting with the SsoBinding builders.
SsoBinding *SsoBindingClient
// StoragePolicy is the client for interacting with the StoragePolicy builders.
StoragePolicy *StoragePolicyClient
// Task is the client for interacting with the Task builders.
@ -216,6 +218,7 @@ func (tx *Tx) init() {
tx.Share = NewShareClient(tx.config)
tx.SharePurchase = NewSharePurchaseClient(tx.config)
tx.Sku = NewSkuClient(tx.config)
tx.SsoBinding = NewSsoBindingClient(tx.config)
tx.StoragePolicy = NewStoragePolicyClient(tx.config)
tx.Task = NewTaskClient(tx.config)
tx.User = NewUserClient(tx.config)

@ -86,9 +86,11 @@ type UserEdges struct {
Grants []*UserGrant `json:"grants,omitempty"`
// SharePurchases holds the value of the share_purchases edge.
SharePurchases []*SharePurchase `json:"share_purchases,omitempty"`
// SSOBindings holds the value of the sso_bindings edge.
SSOBindings []*SsoBinding `json:"sso_bindings,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [13]bool
loadedTypes [14]bool
}
// GroupOrErr returns the Group value or an error if the edge
@ -212,6 +214,15 @@ func (e UserEdges) SharePurchasesOrErr() ([]*SharePurchase, error) {
return nil, &NotLoadedError{edge: "share_purchases"}
}
// SSOBindingsOrErr returns the SSOBindings value or an error if the edge
// was not loaded in eager-loading.
func (e UserEdges) SSOBindingsOrErr() ([]*SsoBinding, error) {
if e.loadedTypes[13] {
return e.SSOBindings, nil
}
return nil, &NotLoadedError{edge: "sso_bindings"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*User) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
@ -425,6 +436,11 @@ func (u *User) QuerySharePurchases() *SharePurchaseQuery {
return NewUserClient(u.config).QuerySharePurchases(u)
}
// QuerySSOBindings queries the "sso_bindings" edge of the User entity.
func (u *User) QuerySSOBindings() *SsoBindingQuery {
return NewUserClient(u.config).QuerySSOBindings(u)
}
// Update returns a builder for updating this User.
// Note that you need to call User.Unwrap() before calling this method if this User
// was returned from a transaction, and the transaction was committed or rolled back.
@ -581,5 +597,11 @@ func (e *User) SetSharePurchases(v []*SharePurchase) {
e.Edges.loadedTypes[12] = true
}
// SetSSOBindings manually set the edge as loaded state.
func (e *User) SetSSOBindings(v []*SsoBinding) {
e.Edges.SSOBindings = v
e.Edges.loadedTypes[13] = true
}
// Users is a parsable slice of User.
type Users []*User

@ -75,6 +75,8 @@ const (
EdgeGrants = "grants"
// EdgeSharePurchases holds the string denoting the share_purchases edge name in mutations.
EdgeSharePurchases = "share_purchases"
// EdgeSSOBindings holds the string denoting the sso_bindings edge name in mutations.
EdgeSSOBindings = "sso_bindings"
// Table holds the table name of the user in the database.
Table = "users"
// GroupTable is the table that holds the group relation/edge.
@ -168,6 +170,13 @@ const (
SharePurchasesInverseTable = "share_purchases"
// SharePurchasesColumn is the table column denoting the share_purchases relation/edge.
SharePurchasesColumn = "buyer_id"
// SSOBindingsTable is the table that holds the sso_bindings relation/edge.
SSOBindingsTable = "sso_bindings"
// SSOBindingsInverseTable is the table name for the SsoBinding entity.
// It exists in this package in order to avoid circular dependency with the "ssobinding" package.
SSOBindingsInverseTable = "sso_bindings"
// SSOBindingsColumn is the table column denoting the sso_bindings relation/edge.
SSOBindingsColumn = "user_id"
)
// Columns holds all SQL columns for user fields.
@ -512,6 +521,20 @@ func BySharePurchases(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
sqlgraph.OrderByNeighborTerms(s, newSharePurchasesStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// BySSOBindingsCount orders the results by sso_bindings count.
func BySSOBindingsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newSSOBindingsStep(), opts...)
}
}
// BySSOBindings orders the results by sso_bindings terms.
func BySSOBindings(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newSSOBindingsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newGroupStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
@ -603,3 +626,10 @@ func newSharePurchasesStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, SharePurchasesTable, SharePurchasesColumn),
)
}
func newSSOBindingsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(SSOBindingsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, SSOBindingsTable, SSOBindingsColumn),
)
}

@ -1214,6 +1214,29 @@ func HasSharePurchasesWith(preds ...predicate.SharePurchase) predicate.User {
})
}
// HasSSOBindings applies the HasEdge predicate on the "sso_bindings" edge.
func HasSSOBindings() predicate.User {
return predicate.User(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, SSOBindingsTable, SSOBindingsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasSSOBindingsWith applies the HasEdge predicate on the "sso_bindings" edge with a given conditions (other predicates).
func HasSSOBindingsWith(preds ...predicate.SsoBinding) predicate.User {
return predicate.User(func(s *sql.Selector) {
step := newSSOBindingsStep()
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.User) predicate.User {
return predicate.User(sql.AndPredicates(predicates...))

@ -22,6 +22,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/passkey"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@ -419,6 +420,21 @@ func (uc *UserCreate) AddSharePurchases(s ...*SharePurchase) *UserCreate {
return uc.AddSharePurchaseIDs(ids...)
}
// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by IDs.
func (uc *UserCreate) AddSSOBindingIDs(ids ...int) *UserCreate {
uc.mutation.AddSSOBindingIDs(ids...)
return uc
}
// AddSSOBindings adds the "sso_bindings" edges to the SsoBinding entity.
func (uc *UserCreate) AddSSOBindings(s ...*SsoBinding) *UserCreate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uc.AddSSOBindingIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (uc *UserCreate) Mutation() *UserMutation {
return uc.mutation
@ -836,6 +852,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := uc.mutation.SSOBindingsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SSOBindingsTable,
Columns: []string{user.SSOBindingsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}

@ -23,6 +23,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@ -48,6 +49,7 @@ type UserQuery struct {
withRedeemedCodes *GiftCodeQuery
withGrants *UserGrantQuery
withSharePurchases *SharePurchaseQuery
withSSOBindings *SsoBindingQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@ -370,6 +372,28 @@ func (uq *UserQuery) QuerySharePurchases() *SharePurchaseQuery {
return query
}
// QuerySSOBindings chains the current query on the "sso_bindings" edge.
func (uq *UserQuery) QuerySSOBindings() *SsoBindingQuery {
query := (&SsoBindingClient{config: uq.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := uq.prepareQuery(ctx); err != nil {
return nil, err
}
selector := uq.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, selector),
sqlgraph.To(ssobinding.Table, ssobinding.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.SSOBindingsTable, user.SSOBindingsColumn),
)
fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first User entity from the query.
// Returns a *NotFoundError when no User was found.
func (uq *UserQuery) First(ctx context.Context) (*User, error) {
@ -575,6 +599,7 @@ func (uq *UserQuery) Clone() *UserQuery {
withRedeemedCodes: uq.withRedeemedCodes.Clone(),
withGrants: uq.withGrants.Clone(),
withSharePurchases: uq.withSharePurchases.Clone(),
withSSOBindings: uq.withSSOBindings.Clone(),
// clone intermediate query.
sql: uq.sql.Clone(),
path: uq.path,
@ -724,6 +749,17 @@ func (uq *UserQuery) WithSharePurchases(opts ...func(*SharePurchaseQuery)) *User
return uq
}
// WithSSOBindings tells the query-builder to eager-load the nodes that are connected to
// the "sso_bindings" edge. The optional arguments are used to configure the query builder of the edge.
func (uq *UserQuery) WithSSOBindings(opts ...func(*SsoBindingQuery)) *UserQuery {
query := (&SsoBindingClient{config: uq.config}).Query()
for _, opt := range opts {
opt(query)
}
uq.withSSOBindings = query
return uq
}
// 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.
//
@ -802,7 +838,7 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
var (
nodes = []*User{}
_spec = uq.querySpec()
loadedTypes = [13]bool{
loadedTypes = [14]bool{
uq.withGroup != nil,
uq.withFiles != nil,
uq.withDavAccounts != nil,
@ -816,6 +852,7 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
uq.withRedeemedCodes != nil,
uq.withGrants != nil,
uq.withSharePurchases != nil,
uq.withSSOBindings != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
@ -926,6 +963,13 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
return nil, err
}
}
if query := uq.withSSOBindings; query != nil {
if err := uq.loadSSOBindings(ctx, query, nodes,
func(n *User) { n.Edges.SSOBindings = []*SsoBinding{} },
func(n *User, e *SsoBinding) { n.Edges.SSOBindings = append(n.Edges.SSOBindings, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
@ -1319,6 +1363,36 @@ func (uq *UserQuery) loadSharePurchases(ctx context.Context, query *SharePurchas
}
return nil
}
func (uq *UserQuery) loadSSOBindings(ctx context.Context, query *SsoBindingQuery, nodes []*User, init func(*User), assign func(*User, *SsoBinding)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int]*User)
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(ssobinding.FieldUserID)
}
query.Where(predicate.SsoBinding(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(user.SSOBindingsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.UserID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) {
_spec := uq.querySpec()

@ -23,6 +23,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/ent/predicate"
"github.com/cloudreve/Cloudreve/v4/ent/share"
"github.com/cloudreve/Cloudreve/v4/ent/sharepurchase"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/ent/task"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/ent/usergrant"
@ -489,6 +490,21 @@ func (uu *UserUpdate) AddSharePurchases(s ...*SharePurchase) *UserUpdate {
return uu.AddSharePurchaseIDs(ids...)
}
// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by IDs.
func (uu *UserUpdate) AddSSOBindingIDs(ids ...int) *UserUpdate {
uu.mutation.AddSSOBindingIDs(ids...)
return uu
}
// AddSSOBindings adds the "sso_bindings" edges to the SsoBinding entity.
func (uu *UserUpdate) AddSSOBindings(s ...*SsoBinding) *UserUpdate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uu.AddSSOBindingIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (uu *UserUpdate) Mutation() *UserMutation {
return uu.mutation
@ -752,6 +768,27 @@ func (uu *UserUpdate) RemoveSharePurchases(s ...*SharePurchase) *UserUpdate {
return uu.RemoveSharePurchaseIDs(ids...)
}
// ClearSSOBindings clears all "sso_bindings" edges to the SsoBinding entity.
func (uu *UserUpdate) ClearSSOBindings() *UserUpdate {
uu.mutation.ClearSSOBindings()
return uu
}
// RemoveSSOBindingIDs removes the "sso_bindings" edge to SsoBinding entities by IDs.
func (uu *UserUpdate) RemoveSSOBindingIDs(ids ...int) *UserUpdate {
uu.mutation.RemoveSSOBindingIDs(ids...)
return uu
}
// RemoveSSOBindings removes "sso_bindings" edges to SsoBinding entities.
func (uu *UserUpdate) RemoveSSOBindings(s ...*SsoBinding) *UserUpdate {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uu.RemoveSSOBindingIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (uu *UserUpdate) Save(ctx context.Context) (int, error) {
if err := uu.defaults(); err != nil {
@ -1470,6 +1507,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if uu.mutation.SSOBindingsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SSOBindingsTable,
Columns: []string{user.SSOBindingsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uu.mutation.RemovedSSOBindingsIDs(); len(nodes) > 0 && !uu.mutation.SSOBindingsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SSOBindingsTable,
Columns: []string{user.SSOBindingsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(ssobinding.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 := uu.mutation.SSOBindingsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SSOBindingsTable,
Columns: []string{user.SSOBindingsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(ssobinding.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, uu.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{user.Label}
@ -1937,6 +2019,21 @@ func (uuo *UserUpdateOne) AddSharePurchases(s ...*SharePurchase) *UserUpdateOne
return uuo.AddSharePurchaseIDs(ids...)
}
// AddSSOBindingIDs adds the "sso_bindings" edge to the SsoBinding entity by IDs.
func (uuo *UserUpdateOne) AddSSOBindingIDs(ids ...int) *UserUpdateOne {
uuo.mutation.AddSSOBindingIDs(ids...)
return uuo
}
// AddSSOBindings adds the "sso_bindings" edges to the SsoBinding entity.
func (uuo *UserUpdateOne) AddSSOBindings(s ...*SsoBinding) *UserUpdateOne {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uuo.AddSSOBindingIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (uuo *UserUpdateOne) Mutation() *UserMutation {
return uuo.mutation
@ -2200,6 +2297,27 @@ func (uuo *UserUpdateOne) RemoveSharePurchases(s ...*SharePurchase) *UserUpdateO
return uuo.RemoveSharePurchaseIDs(ids...)
}
// ClearSSOBindings clears all "sso_bindings" edges to the SsoBinding entity.
func (uuo *UserUpdateOne) ClearSSOBindings() *UserUpdateOne {
uuo.mutation.ClearSSOBindings()
return uuo
}
// RemoveSSOBindingIDs removes the "sso_bindings" edge to SsoBinding entities by IDs.
func (uuo *UserUpdateOne) RemoveSSOBindingIDs(ids ...int) *UserUpdateOne {
uuo.mutation.RemoveSSOBindingIDs(ids...)
return uuo
}
// RemoveSSOBindings removes "sso_bindings" edges to SsoBinding entities.
func (uuo *UserUpdateOne) RemoveSSOBindings(s ...*SsoBinding) *UserUpdateOne {
ids := make([]int, len(s))
for i := range s {
ids[i] = s[i].ID
}
return uuo.RemoveSSOBindingIDs(ids...)
}
// Where appends a list predicates to the UserUpdate builder.
func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne {
uuo.mutation.Where(ps...)
@ -2948,6 +3066,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if uuo.mutation.SSOBindingsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SSOBindingsTable,
Columns: []string{user.SSOBindingsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := uuo.mutation.RemovedSSOBindingsIDs(); len(nodes) > 0 && !uuo.mutation.SSOBindingsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SSOBindingsTable,
Columns: []string{user.SSOBindingsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(ssobinding.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 := uuo.mutation.SSOBindingsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.SSOBindingsTable,
Columns: []string{user.SSOBindingsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(ssobinding.FieldID, field.TypeInt),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &User{config: uuo.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues

@ -894,6 +894,12 @@
"oauthGrantLastUsed": "Last used at <0></0>",
"revokeOAuthGrant": "Revoke Authorization",
"revokeOAuthGrantConfirm": "Are you sure you want to revoke this app's authorization? Once revoked, this app will no longer be able to access your account.",
"linkedAccounts": "Linked accounts",
"providerQQ": "QQ",
"linkedAt": "Linked at <0></0>",
"unlinkAccount": "Unlink",
"unlinkAccountConfirm": "Are you sure you want to unlink this account? You will no longer be able to sign in with it.",
"linkQQAccount": "Link QQ account",
"nickNameDes": "This is your public display name. It can be your real name or a pseudonym.",
"changeEmail": "Change",
"changeEmailDes": "A confirmation link will be sent to the new address. Your current email ({{email}}) stays active until confirmed.",

@ -850,6 +850,14 @@
"ssoAutoRedirectDes": "Skip the login form and send visitors straight to the identity provider. Append <0>?nosso=1</0> to the login URL to reach the password form (e.g. for admin recovery).",
"ssoCallbackUrl": "Callback URL",
"ssoCallbackUrlDes": "Register this URL as the redirect/callback URL in your identity provider: <0>{{url}}</0>",
"qqConnect": "QQ Connect",
"qqAppID": "App ID",
"qqAppIDDes": "The App ID of the website application created in the <0>QQ Connect console</0> (connect.qq.com).",
"qqAppSecret": "App Key",
"qqCallbackUrl": "Callback URL",
"qqCallbackUrlDes": "Register this URL as the website callback domain/URL in the QQ Connect console: <0>{{url}}</0>",
"qqRegisterEnabled": "Allow automatic registration",
"qqRegisterEnabledDes": "Automatically create a local account when a user signs in via QQ for the first time. QQ supplies no email address, so provisioned accounts use a synthetic @connect.qq.local address.",
"themeVisible": "Visible",
"shareDefaultPrivate": "Private share by default",
"shareDefaultPrivateDes": "New shares default to private (password protected). Users can override this in their personal settings.",

@ -894,6 +894,12 @@
"oauthGrantLastUsed": "上次使用于 <0></0>",
"revokeOAuthGrant": "撤销授权",
"revokeOAuthGrantConfirm": "确定要撤销此应用的授权吗?撤销后,该应用将无法再访问你的账户。",
"linkedAccounts": "已绑定账号",
"providerQQ": "QQ",
"linkedAt": "绑定于 <0></0>",
"unlinkAccount": "解绑",
"unlinkAccountConfirm": "确定要解绑此账号吗?解绑后将无法再使用该账号登录。",
"linkQQAccount": "绑定 QQ 账号",
"nickNameDes": "用于公开展示的名字,可使用真实姓名或昵称",
"changeEmail": "更换",
"changeEmailDes": "确认链接将发送到新邮箱。在确认之前,当前邮箱({{email}})仍然有效。",

@ -850,6 +850,14 @@
"ssoAutoRedirectDes": "跳过登录表单,直接跳转至身份提供商。在登录地址后附加 <0>?nosso=1</0> 可进入密码登录表单(例如管理员账户恢复)。",
"ssoCallbackUrl": "回调地址",
"ssoCallbackUrlDes": "请在身份提供方中将此 URL 注册为 Redirect/Callback URL:<0>{{url}}</0>",
"qqConnect": "QQ 互联",
"qqAppID": "App ID",
"qqAppIDDes": "在 <0>QQ 互联开放平台</0>(connect.qq.com)创建的网站应用的 App ID。",
"qqAppSecret": "App Key",
"qqCallbackUrl": "回调地址",
"qqCallbackUrlDes": "请在 QQ 互联控制台中将此 URL 注册为网站回调域/回调地址:<0>{{url}}</0>",
"qqRegisterEnabled": "允许自动注册",
"qqRegisterEnabledDes": "用户首次通过 QQ 登录时自动创建本地账号。QQ 不提供邮箱,自动注册的账号使用 @connect.qq.local 合成邮箱。",
"themeVisible": "可见",
"shareDefaultPrivate": "默认私密分享",
"shareDefaultPrivateDes": "新建分享默认启用私密分享(密码保护)。用户仍可在个人设置中覆盖此默认值。",

@ -2606,6 +2606,22 @@ export function sendRevokeOAuthGrant(grant_id: string): ThunkResponse {
};
}
export function sendUnbindSso(provider: string): ThunkResponse {
return async (dispatch, _getState) => {
return await dispatch(
send(
`/user/setting/sso_binding/${provider}`,
{
method: "DELETE",
},
{
...defaultOpts,
},
),
);
};
}
export function sendFullTextSearch(query: string, offset?: number): ThunkResponse {
const params = new URLSearchParams();
params.set("query", query);

@ -32,6 +32,7 @@ export interface SiteConfig {
sso_enabled?: boolean;
sso_display_name?: string;
sso_auto_redirect?: boolean;
qq_connect_enabled?: boolean;
download_cdn_routes?: { name: string; url: string }[];
abuse_captcha?: boolean;
allow_select_node?: boolean;

@ -150,6 +150,12 @@ export interface UserSettings {
trash_retention?: number;
preferred_policy?: string;
oauth_grants?: OAuthGrant[];
linked_accounts?: LinkedAccount[];
}
export interface LinkedAccount {
provider: string;
created_at: string;
}
export interface OAuthGrant {

@ -201,6 +201,10 @@ const Settings = () => {
"sso_scopes",
"sso_register_enabled",
"sso_auto_redirect",
"qq_connect_enabled",
"qq_connect_app_id",
"qq_connect_app_secret",
"qq_connect_register_enabled",
"email_filter_mode",
"email_filter_list",
"email_disable_subaddress",

@ -0,0 +1,104 @@
import { ExpandMoreRounded } from "@mui/icons-material";
import { AccordionDetails, FormControl, FormControlLabel, Switch, Typography } from "@mui/material";
import { useContext, useMemo } from "react";
import { Trans, useTranslation } from "react-i18next";
import { isTrueVal } from "../../../../session/utils.ts";
import { Code } from "../../../Common/Code.tsx";
import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx";
import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx";
import { SettingContext } from "../SettingWrapper.tsx";
import { AccordionSummary, StyledAccordion } from "./SSOSettings.tsx";
const QQConnectSettings = () => {
const { t } = useTranslation("dashboard");
const { setSettings, values } = useContext(SettingContext);
const callbackURL = useMemo(() => {
const primary = (values.siteURL ?? "").split(",")[0]?.trim().replace(/\/+$/, "");
return primary ? `${primary}/api/v4/session/qq/callback` : "";
}, [values.siteURL]);
const enabled = isTrueVal(values.qq_connect_enabled);
return (
<StyledAccordion disableGutters>
<AccordionSummary expandIcon={<ExpandMoreRounded />}>
<FormControlLabel
control={
<Switch
size="small"
checked={enabled}
onChange={(e) =>
setSettings({
qq_connect_enabled: e.target.checked ? "1" : "0",
})
}
onClick={(e) => e.stopPropagation()}
/>
}
label={t("settings.qqConnect")}
/>
</AccordionSummary>
<AccordionDetails sx={{ display: "block" }}>
<SettingSectionContent>
<FormControl fullWidth>
<DenseFilledTextField
label={t("settings.qqAppID")}
value={values.qq_connect_app_id}
onChange={(e) => setSettings({ qq_connect_app_id: e.target.value })}
required={enabled}
/>
<NoMarginHelperText>
<Trans i18nKey="settings.qqAppIDDes" ns="dashboard" components={[<Code key="0" />]} />
</NoMarginHelperText>
</FormControl>
<FormControl fullWidth>
<DenseFilledTextField
label={t("settings.qqAppSecret")}
value={values.qq_connect_app_secret ?? ""}
onChange={(e) => setSettings({ qq_connect_app_secret: e.target.value })}
type="password"
placeholder={t("oauth.secretRedactedPlaceholder")}
/>
<NoMarginHelperText>{t("oauth.clientSecretDesExisting")}</NoMarginHelperText>
</FormControl>
{callbackURL && (
<FormControl fullWidth>
<DenseFilledTextField
label={t("settings.qqCallbackUrl")}
value={callbackURL}
slotProps={{ input: { readOnly: true } }}
/>
<NoMarginHelperText>
<Trans
i18nKey="settings.qqCallbackUrlDes"
ns="dashboard"
values={{ url: callbackURL }}
components={[<Code key="0" />]}
/>
</NoMarginHelperText>
</FormControl>
)}
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={isTrueVal(values.qq_connect_register_enabled)}
onChange={(e) =>
setSettings({
qq_connect_register_enabled: e.target.checked ? "1" : "0",
})
}
/>
}
label={<Typography variant="body2">{t("settings.qqRegisterEnabled")}</Typography>}
/>
<NoMarginHelperText>{t("settings.qqRegisterEnabledDes")}</NoMarginHelperText>
</FormControl>
</SettingSectionContent>
</AccordionDetails>
</StyledAccordion>
);
};
export default QQConnectSettings;

@ -12,6 +12,7 @@ import GroupSelectionInput from "../../Common/GroupSelectionInput.tsx";
import SharesInput from "../../Common/SharesInput.tsx";
import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx";
import { SettingContext } from "../SettingWrapper.tsx";
import QQConnectSettings from "./QQConnectSettings.tsx";
import SSOSettings from "./SSOSettings.tsx";
const UserSession = () => {
@ -303,6 +304,9 @@ const UserSession = () => {
<SettingForm lgWidth={5}>
<SSOSettings />
</SettingForm>
<SettingForm lgWidth={5}>
<QQConnectSettings />
</SettingForm>
</SettingSectionContent>
</SettingSection>
<SettingSection>

@ -8,6 +8,7 @@ import { useQuery } from "../../../../util";
import { OutlineIconTextField } from "../../../Common/Form/OutlineIconTextField.tsx";
import MailOutlined from "../../../Icons/MailOutlined.tsx";
import PasskeyLoginButton from "../Signin/PasskeyLoginButton.tsx";
import QQLoginButton from "../Signin/QQLoginButton.tsx";
import SSOLoginButton from "../Signin/SSOLoginButton.tsx";
import { Control } from "../Signin/SignIn.tsx";
@ -53,11 +54,13 @@ interface PhaseCollectEmailProps {
const PhaseCollectEmail = ({ email, setEmail, control, onOAuthPasskeyLogin }: PhaseCollectEmailProps) => {
const { t } = useTranslation();
const query = useQuery();
const { register_enabled, authn, sso_enabled } = useAppSelector((state) => state.siteConfig.login.config);
const { register_enabled, authn, sso_enabled, qq_connect_enabled } = useAppSelector(
(state) => state.siteConfig.login.config,
);
const tos = useAppSelector((state) => state.siteConfig.login.config.tos_url);
const privacyPolicy = useAppSelector((state) => state.siteConfig.login.config.privacy_policy_url);
const showFooter = tos || privacyPolicy || authn || sso_enabled;
const showFooter = tos || privacyPolicy || authn || sso_enabled || qq_connect_enabled;
useEffect(() => {
if (!!query.get("email")) {
@ -101,6 +104,7 @@ const PhaseCollectEmail = ({ email, setEmail, control, onOAuthPasskeyLogin }: Ph
<Stack spacing={1}>
{authn && <PasskeyLoginButton autoComplete onLoginSuccess={onOAuthPasskeyLogin} />}
<SSOLoginButton />
<QQLoginButton />
</Stack>
<LegalLinks />
</>

@ -0,0 +1,31 @@
import { Icon } from "@iconify/react";
import { Button, ButtonProps } from "@mui/material";
import { useTranslation } from "react-i18next";
import { ApiPrefix } from "../../../../api/request.ts";
import { useAppSelector } from "../../../../redux/hooks.ts";
import { useQuery } from "../../../../util";
export default function QQLoginButton(props: ButtonProps) {
const { t } = useTranslation();
const query = useQuery();
const { qq_connect_enabled } = useAppSelector((state) => state.siteConfig.login.config);
if (!qq_connect_enabled) {
return null;
}
const startLogin = () => {
const redirect = query.get("redirect");
const target = new URL(ApiPrefix + "/session/qq/login", window.location.origin);
if (redirect) {
target.searchParams.set("redirect", redirect);
}
window.location.assign(target.toString());
};
return (
<Button fullWidth variant="outlined" startIcon={<Icon icon="ri:qq-fill" />} onClick={startLogin} {...props}>
{t("login.signInWith", { name: "QQ" })}
</Button>
);
}

@ -17,8 +17,8 @@ import { useSnackbar } from "notistack";
import React, { useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { sendRevokeOAuthGrant, sendUpdateUserSetting } from "../../../../api/api.ts";
import { OAuthGrant, Passkey, UserSettings } from "../../../../api/user.ts";
import { sendRevokeOAuthGrant, sendUnbindSso, sendUpdateUserSetting } from "../../../../api/api.ts";
import { LinkedAccount, OAuthGrant, Passkey, UserSettings } from "../../../../api/user.ts";
import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts";
import { confirmOperation } from "../../../../redux/thunks/dialog.ts";
import SessionManager from "../../../../session";
@ -161,6 +161,75 @@ const OAuthGrantItem = ({ grant, onRevoked }: { grant: OAuthGrant; onRevoked: (c
);
};
const linkedAccountProviderName = (t: (key: string) => string, provider: string): string => {
switch (provider) {
case "qq":
return t("setting.providerQQ");
default:
return provider;
}
};
const LinkedAccountItem = ({
account,
onUnlinked,
}: {
account: LinkedAccount;
onUnlinked: (provider: string) => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const dispatch = useAppDispatch();
const [loading, setLoading] = useState(false);
const onUnbind = () => {
dispatch(confirmOperation(t("setting.unlinkAccountConfirm"))).then(() => {
setLoading(true);
dispatch(sendUnbindSso(account.provider))
.then(() => {
onUnlinked(account.provider);
})
.finally(() => {
setLoading(false);
});
});
};
return (
<StyledOAuthGrantListItem sx={{ pr: "150px" }}>
<ListItemAvatar>
<Avatar sx={{ bgcolor: theme.palette.primary.main }}>
{account.provider === "qq" ? <Icon icon="ri:qq-fill" style={{ fontSize: 24 }} /> : <AppsListOutlined />}
</Avatar>
</ListItemAvatar>
<StyledListItemText
primary={linkedAccountProviderName(t, account.provider)}
secondaryTypographyProps={{
variant: "caption",
}}
secondary={
<Trans
i18nKey={"setting.linkedAt"}
ns={"application"}
components={[<TimeBadge key="0" datetime={account.created_at} variant={"inherit"} />]}
/>
}
/>
<ListItemSecondaryAction>
<LoadingButton
loading={loading}
variant={"outlined"}
onClick={onUnbind}
startIcon={<Dismiss />}
color={"error"}
>
<span>{t("setting.unlinkAccount")}</span>
</LoadingButton>
</ListItemSecondaryAction>
</StyledOAuthGrantListItem>
);
};
const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
const { t } = useTranslation();
const theme = useTheme();
@ -170,6 +239,7 @@ const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
const navigate = useNavigate();
const authEnabled = useAppSelector((s) => s.siteConfig.login.config.authn);
const qqConnectEnabled = useAppSelector((s) => s.siteConfig.login.config.qq_connect_enabled);
const resetPwdFormRef = React.createRef<HTMLFormElement>();
const [showResetPassword, setShowResetPassword] = useState(false);
@ -248,6 +318,13 @@ const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
});
};
const onAccountUnlinked = (provider: string) => {
setSetting({
...setting,
linked_accounts: setting.linked_accounts?.filter((x) => x.provider != provider),
});
};
return (
<Stack spacing={3}>
<SettingForm title={t("login.password")} lgWidth={5}>
@ -353,6 +430,27 @@ const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => {
</List>
</SettingForm>
)}
{(qqConnectEnabled || (setting.linked_accounts && setting.linked_accounts.length > 0)) && (
<SettingForm title={t("setting.linkedAccounts")} lgWidth={5}>
<List disablePadding>
{setting.linked_accounts?.map((account) => (
<LinkedAccountItem key={account.provider} account={account} onUnlinked={onAccountUnlinked} />
))}
</List>
{qqConnectEnabled && !setting.linked_accounts?.some((a) => a.provider === "qq") && (
<SecondaryButton
sx={{ mt: 1 }}
variant={"contained"}
startIcon={<Icon icon="ri:qq-fill" />}
onClick={() => {
window.location.href = "/api/v4/session/qq/login?link=1";
}}
>
{t("setting.linkQQAccount")}
</SecondaryButton>
)}
</SettingForm>
)}
<Enable2FADialog open={enable2FAOpen} onClose={() => setEnable2FAOpen(false)} on2FAEnabled={on2FAChange(true)} />
<Disable2FADialog
open={disable2FAOpen}

@ -566,6 +566,10 @@ var DefaultSettings = map[string]string{
"sso_scopes": "",
"sso_register_enabled": "1",
"sso_auto_redirect": "0",
"qq_connect_enabled": "0",
"qq_connect_app_id": "",
"qq_connect_app_secret": "",
"qq_connect_register_enabled": "1",
"download_cdn_routes": "",
"email_filter_mode": "0",
"email_filter_list": "",
@ -731,6 +735,7 @@ var RedactedSettings = map[string]struct{}{
"secret_key": {},
"oidc_signing_private_key": {},
"sso_client_secret": {},
"qq_connect_app_secret": {},
}
func init() {

@ -0,0 +1,113 @@
package inventory
import (
"context"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/ssobinding"
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
)
type (
SsoBindingClient interface {
TxOperator
// Get returns the binding for (provider, subject).
Get(ctx context.Context, provider, subject string) (*ent.SsoBinding, error)
// ListByUser returns all bindings owned by the given user.
ListByUser(ctx context.Context, userID int) ([]*ent.SsoBinding, error)
// Bind links (provider, subject) to a user. An existing binding for
// the same (user, provider) is moved to the new subject; a binding
// for the same (provider, subject) owned by a different user is
// treated as a conflict.
Bind(ctx context.Context, userID int, provider, subject string) (*ent.SsoBinding, error)
// Unbind removes the user's binding at the given provider. Returns
// nil when no binding exists.
Unbind(ctx context.Context, userID int, provider string) error
}
)
// SsoProvider enumerates the external sign-in providers that can appear in
// sso_binding rows.
const (
SsoProviderQQ = "qq"
)
var (
ErrSsoBindingConflict = &BindingConflictError{}
)
type BindingConflictError struct{}
func (e *BindingConflictError) Error() string {
return "external account is already linked to a different user"
}
func NewSsoBindingClient(client *ent.Client, dbType conf.DBType) SsoBindingClient {
return &ssoBindingClient{
client: client,
maxSQlParam: sqlParamLimit(dbType),
}
}
type ssoBindingClient struct {
maxSQlParam int
client *ent.Client
}
func (c *ssoBindingClient) SetClient(newClient *ent.Client) TxOperator {
return &ssoBindingClient{client: newClient, maxSQlParam: c.maxSQlParam}
}
func (c *ssoBindingClient) GetClient() *ent.Client {
return c.client
}
func (c *ssoBindingClient) Get(ctx context.Context, provider, subject string) (*ent.SsoBinding, error) {
return c.client.SsoBinding.Query().
Where(ssobinding.Provider(provider), ssobinding.Subject(subject)).
Only(ctx)
}
func (c *ssoBindingClient) ListByUser(ctx context.Context, userID int) ([]*ent.SsoBinding, error) {
return c.client.SsoBinding.Query().
Where(ssobinding.UserID(userID)).
All(ctx)
}
func (c *ssoBindingClient) Bind(ctx context.Context, userID int, provider, subject string) (*ent.SsoBinding, error) {
if existing, err := c.Get(ctx, provider, subject); err == nil {
if existing.UserID != userID {
return nil, ErrSsoBindingConflict
}
return existing, nil
} else if !ent.IsNotFound(err) {
return nil, err
}
// One binding per (user, provider): re-bind moves the row to the new
// subject instead of stacking a second account.
existing, err := c.client.SsoBinding.Query().
Where(ssobinding.UserID(userID), ssobinding.Provider(provider)).
Only(ctx)
if err == nil {
return c.client.SsoBinding.UpdateOne(existing).
SetSubject(subject).
Save(ctx)
}
if !ent.IsNotFound(err) {
return nil, err
}
return c.client.SsoBinding.Create().
SetUserID(userID).
SetProvider(provider).
SetSubject(subject).
Save(ctx)
}
func (c *ssoBindingClient) Unbind(ctx context.Context, userID int, provider string) error {
_, err := c.client.SsoBinding.Delete().
Where(ssobinding.UserID(userID), ssobinding.Provider(provider)).
Exec(ctx)
return err
}

@ -0,0 +1,80 @@
package inventory
import (
"context"
"fmt"
"testing"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/enttest"
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
"github.com/stretchr/testify/require"
)
func ssoBindingUser(t *testing.T, client *ent.Client, group *ent.Group, n int) *ent.User {
t.Helper()
return client.User.Create().
SetEmail(fmt.Sprintf("%s-%d@example.com", t.Name(), n)).
SetNick(fmt.Sprintf("u%d", n)).
SetGroup(group).
SaveX(context.Background())
}
func TestSsoBindingBindGetUnbind(t *testing.T) {
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
defer client.Close()
c := NewSsoBindingClient(client, conf.SQLiteDB)
ctx := context.Background()
group := client.Group.Create().SetName("g").SetPermissions(&boolset.BooleanSet{}).SaveX(ctx)
u1 := ssoBindingUser(t, client, group, 1)
u2 := ssoBindingUser(t, client, group, 2)
b, err := c.Bind(ctx, u1.ID, SsoProviderQQ, "openid-a")
require.NoError(t, err)
require.Equal(t, u1.ID, b.UserID)
require.Equal(t, "openid-a", b.Subject)
// Lookup by (provider, subject) resolves the owner.
got, err := c.Get(ctx, SsoProviderQQ, "openid-a")
require.NoError(t, err)
require.Equal(t, b.ID, got.ID)
// ListByUser returns only the caller's bindings.
_, err = c.Bind(ctx, u2.ID, SsoProviderQQ, "openid-b")
require.NoError(t, err)
mine, err := c.ListByUser(ctx, u1.ID)
require.NoError(t, err)
require.Len(t, mine, 1)
require.Equal(t, "openid-a", mine[0].Subject)
// Re-binding the same (user, provider) moves the row to the new subject.
moved, err := c.Bind(ctx, u1.ID, SsoProviderQQ, "openid-c")
require.NoError(t, err)
require.Equal(t, b.ID, moved.ID)
require.Equal(t, "openid-c", moved.Subject)
mine, err = c.ListByUser(ctx, u1.ID)
require.NoError(t, err)
require.Len(t, mine, 1)
// A subject owned by a different user conflicts.
_, err = c.Bind(ctx, u2.ID, SsoProviderQQ, "openid-c")
require.ErrorIs(t, err, ErrSsoBindingConflict)
// Re-binding the same subject to its owner is a no-op.
again, err := c.Bind(ctx, u1.ID, SsoProviderQQ, "openid-c")
require.NoError(t, err)
require.Equal(t, moved.ID, again.ID)
// Unbind removes only the caller's row and is idempotent.
require.NoError(t, c.Unbind(ctx, u1.ID, SsoProviderQQ))
_, err = c.Get(ctx, SsoProviderQQ, "openid-c")
require.Error(t, err)
require.NoError(t, c.Unbind(ctx, u1.ID, SsoProviderQQ))
// User 2's binding is untouched.
still, err := c.Get(ctx, SsoProviderQQ, "openid-b")
require.NoError(t, err)
require.Equal(t, u2.ID, still.UserID)
}

@ -251,6 +251,7 @@ type (
ExposeUserEmail(ctx context.Context) bool
// SSO returns the inbound single sign-on (OIDC) settings.
SSO(ctx context.Context) *SSO
QQConnect(ctx context.Context) *QQConnect
// EmailFilter returns the sign-up email restriction settings.
EmailFilter(ctx context.Context) *EmailFilter
// ShareDefaults returns the site-wide share defaults applied when a
@ -985,6 +986,15 @@ func (s *settingProvider) SSO(ctx context.Context) *SSO {
}
}
func (s *settingProvider) QQConnect(ctx context.Context) *QQConnect {
return &QQConnect{
Enabled: s.getBoolean(ctx, "qq_connect_enabled", false),
AppID: s.getString(ctx, "qq_connect_app_id", ""),
AppSecret: s.getString(ctx, "qq_connect_app_secret", ""),
RegisterEnabled: s.getBoolean(ctx, "qq_connect_register_enabled", true),
}
}
func (s *settingProvider) EmailFilter(ctx context.Context) *EmailFilter {
mode := EmailFilterMode(s.getInt(ctx, "email_filter_mode", 0))
if mode < EmailFilterDisabled || mode > EmailFilterBlacklist {

@ -85,6 +85,16 @@ type SSO struct {
AutoRedirect bool
}
// QQConnect holds the QQ互联 (connect.qq.com) OAuth2 application config.
// Unlike the generic OIDC consumer, QQ Connect has no discovery document or
// id_token; identity resolves through /oauth2.0/me openid.
type QQConnect struct {
Enabled bool
AppID string
AppSecret string
RegisterEnabled bool
}
type EmailFilterMode int
const (

@ -358,6 +358,30 @@ func UserSSOCallback(c *gin.Context) {
service.SSOCallback(c)
}
// UserQQLogin redirects the browser to the QQ Connect authorization page.
func UserQQLogin(c *gin.Context) {
service := ParametersFromContext[*user.QQLoginService](c, user.QQLoginParameterCtx{})
service.Login(c)
}
// UserQQCallback completes the QQ Connect flow and redirects either to the
// SPA ticket handoff or, for link mode, back to the security settings tab.
func UserQQCallback(c *gin.Context) {
service := ParametersFromContext[*user.QQCallbackService](c, user.QQCallbackParameterCtx{})
service.Callback(c)
}
// UserUnbindSso removes the caller's external-account binding at a provider.
func UserUnbindSso(c *gin.Context) {
service := ParametersFromContext[*user.SsoUnbindService](c, user.SsoUnbindParameterCtx{})
err := service.Delete(c)
if respondErr(c, err) {
return
}
c.JSON(200, serializer.Response{})
}
// UserSSOExchange trades the one-time ticket for a session token pair.
func UserSSOExchange(c *gin.Context) {
service := ParametersFromContext[*user.SSOExchangeService](c, user.SSOExchangeParameterCtx{})

@ -342,6 +342,19 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine {
)
}
// QQ Connect (non-OIDC OAuth2 provider)
qqRouter := session.Group("qq")
{
qqRouter.GET("login",
controllers.FromQuery[usersvc.QQLoginService](usersvc.QQLoginParameterCtx{}),
controllers.UserQQLogin,
)
qqRouter.GET("callback",
controllers.FromQuery[usersvc.QQCallbackService](usersvc.QQCallbackParameterCtx{}),
controllers.UserQQCallback,
)
}
oauthRouter := session.Group("oauth")
{
oauthRouter.GET("app/:app_id",
@ -1520,6 +1533,12 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine {
controllers.FromJSON[usersvc.RequestEmailChangeService](usersvc.RequestEmailChangeParamCtx{}),
controllers.UserRequestEmailChange,
)
// 解除外部账号绑定(QQ Connect 等)
setting.DELETE("sso_binding/:provider",
middleware.RequiredScopes(types.ScopeUserSecurityInfoWrite),
controllers.FromUri[usersvc.SsoUnbindService](usersvc.SsoUnbindParameterCtx{}),
controllers.UserUnbindSso,
)
}
// 积分与兑换

@ -52,6 +52,9 @@ func TestMasterRouteWiring(t *testing.T) {
"PUT /api/v4/file/upload",
"POST /api/v4/file/upload/:sessionId/:index",
"POST /api/v4/share/purchase/:id",
"GET /api/v4/session/qq/login",
"GET /api/v4/session/qq/callback",
"DELETE /api/v4/user/setting/sso_binding/:provider",
"GET /f/:id/:name",
}
for _, e := range expected {

@ -52,6 +52,7 @@ type SiteConfig struct {
SSOEnabled bool `json:"sso_enabled,omitempty"`
SSODisplayName string `json:"sso_display_name,omitempty"`
SSOAutoRedirect bool `json:"sso_auto_redirect,omitempty"`
QQConnectEnabled bool `json:"qq_connect_enabled,omitempty"`
// DownloadCDNRoutes exposes configured CDN mirror endpoints so clients
// can offer a download-route picker (#2987).
@ -124,6 +125,7 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
case "login":
legalDocs := settings.LegalDocuments(c)
sso := settings.SSO(c)
qq := settings.QQConnect(c)
return &SiteConfig{
LoginCaptcha: settings.LoginCaptchaEnabled(c),
RegCaptcha: settings.RegCaptchaEnabled(c),
@ -136,6 +138,7 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
SSOEnabled: sso.Enabled && sso.Issuer != "" && sso.ClientID != "",
SSODisplayName: sso.DisplayName,
SSOAutoRedirect: sso.AutoRedirect,
QQConnectEnabled: qq.Enabled && qq.AppID != "",
}, nil
case "explorer":
explorerSettings := settings.ExplorerFrontendSettings(c)

@ -0,0 +1,435 @@
package user
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/ent/user"
"github.com/cloudreve/Cloudreve/v4/inventory"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/activity"
"github.com/cloudreve/Cloudreve/v4/pkg/request"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
"github.com/gin-gonic/gin"
)
type (
// QQLoginParameterCtx marks the QQ Connect authorization start route.
QQLoginParameterCtx struct{}
// QQCallbackParameterCtx marks the QQ Connect redirect target.
QQCallbackParameterCtx struct{}
// SsoUnbindParameterCtx marks the external-binding removal route.
SsoUnbindParameterCtx struct{}
// QQLoginService starts the QQ Connect OAuth2 flow. `link=1` binds the
// QQ identity to the currently signed-in user instead of signing in.
QQLoginService struct {
Redirect string `form:"redirect"`
Link bool `form:"link"`
}
// QQCallbackService completes the QQ Connect flow.
QQCallbackService struct {
Code string `form:"code"`
State string `form:"state"`
}
// SsoUnbindService removes the caller's binding at a provider.
SsoUnbindService struct {
Provider string `uri:"provider" binding:"required"`
}
)
const (
qqAuthorizeEndpoint = "https://graph.qq.com/oauth2.0/authorize"
qqTokenEndpoint = "https://graph.qq.com/oauth2.0/token"
qqOpenIDEndpoint = "https://graph.qq.com/oauth2.0/me"
qqUserInfoEndpoint = "https://graph.qq.com/user/get_user_info"
// qqConnectMailDomain is the synthetic domain used for provisioned
// accounts; QQ exposes no email claim so a real address cannot exist.
qqConnectMailDomain = "connect.qq.local"
)
// qqOpenIDResponse is the JSONP payload of /oauth2.0/me.
type qqOpenIDResponse struct {
ClientID string `json:"client_id"`
OpenID string `json:"openid"`
}
// qqUserInfoResponse is the profile document from get_user_info.
type qqUserInfoResponse struct {
Ret int `json:"ret"`
Nickname string `json:"nickname"`
}
func validateQQConfig(qq *setting.QQConnect) error {
if !qq.Enabled || qq.AppID == "" || qq.AppSecret == "" {
return errors.New("qq connect not enabled or not configured")
}
return nil
}
func qqCallbackURL(settings setting.Provider, c *gin.Context) string {
return settings.SiteURL(c).ResolveReference(&url.URL{Path: "api/v4/session/qq/callback"}).String()
}
// QQLogin redirects the browser to the QQ authorization page.
func (service *QQLoginService) Login(c *gin.Context) {
dep := dependency.FromContext(c)
settings := dep.SettingProvider()
qq := settings.QQConnect(c)
if err := validateQQConfig(qq); err != nil {
redirectToSigninWithError(c, settings, "sso_not_configured")
return
}
state := ssoState{
Redirect: sanitizeSSORedirect(service.Redirect),
}
if service.Link {
u := inventory.UserFromContext(c)
if inventory.IsAnonymousUser(u) {
redirectToSigninWithError(c, settings, "sso_state_failed")
return
}
state.LinkUserID = u.ID
}
stateKey := ssoStateKey()
if err := dep.KV().Set(stateKey, state, ssoStateTTL); err != nil {
dep.Logger().Warning("Failed to persist QQ state: %s", err)
redirectToSigninWithError(c, settings, "sso_state_failed")
return
}
authorize, _ := url.Parse(qqAuthorizeEndpoint)
q := authorize.Query()
q.Set("response_type", "code")
q.Set("client_id", qq.AppID)
q.Set("redirect_uri", qqCallbackURL(settings, c))
q.Set("state", stateKey)
q.Set("scope", "get_user_info")
authorize.RawQuery = q.Encode()
c.Redirect(http.StatusFound, authorize.String())
}
// QQCallback exchanges the code, resolves the QQ openid, then either binds
// it to the linking user or signs the bound account in.
func (service *QQCallbackService) Callback(c *gin.Context) {
dep := dependency.FromContext(c)
settings := dep.SettingProvider()
fail := func(code string) {
redirectToSigninWithError(c, settings, code)
}
qq := settings.QQConnect(c)
if err := validateQQConfig(qq); err != nil {
fail("sso_not_configured")
return
}
if service.State == "" || service.Code == "" {
fail("sso_invalid_response")
return
}
rawState, ok := dep.KV().Get(service.State)
if !ok {
fail("sso_state_expired")
return
}
_ = dep.KV().Delete("", service.State)
state, ok := rawState.(ssoState)
if !ok {
fail("sso_state_expired")
return
}
// The endpoints are constants, but DNS resolution is not: reject a
// graph.qq.com that resolves to a private address before issuing
// credentialed requests.
for _, endpoint := range []string{qqTokenEndpoint, qqOpenIDEndpoint, qqUserInfoEndpoint} {
if err := request.ValidateExternalURL(c, endpoint, request.SSRFOptions{}); err != nil {
dep.Logger().Warning("QQ endpoint rejected by SSRF check: %s", err)
fail("sso_exchange_failed")
return
}
}
httpClient := dep.RequestClient()
token, err := qqExchangeCode(c, httpClient, service.Code, qqCallbackURL(settings, c), qq)
if err != nil {
dep.Logger().Warning("QQ token exchange failed: %s", err)
fail("sso_exchange_failed")
return
}
openID, err := qqFetchOpenID(c, httpClient, token, qq.AppID)
if err != nil {
dep.Logger().Warning("QQ openid request failed: %s", err)
fail("sso_token_invalid")
return
}
bindings := dep.SsoBindingClient()
// Link mode: attach the openid to the signed-in user and return to the
// security settings tab.
if state.LinkUserID != 0 {
if _, err := bindings.Bind(c, state.LinkUserID, inventory.SsoProviderQQ, openID); err != nil {
dep.Logger().Info("QQ link rejected: %s", err)
if errors.Is(err, inventory.ErrSsoBindingConflict) {
fail("sso_account_unavailable")
return
}
fail("sso_state_failed")
return
}
recordUserEvent(c, dep, state.LinkUserID, types.EventLinkAccount, map[string]any{"provider": inventory.SsoProviderQQ})
dest := settings.SiteURL(c).ResolveReference(&url.URL{Path: "settings", RawQuery: "tab=security"})
c.Redirect(http.StatusFound, dest.String())
return
}
var targetUser *ent.User
binding, err := bindings.Get(c, inventory.SsoProviderQQ, openID)
switch {
case err == nil:
ctx := context.WithValue(c, inventory.LoadUserGroup{}, true)
targetUser, err = dep.UserClient().GetByID(ctx, binding.UserID)
if err != nil {
dep.Logger().Warning("QQ binding resolved to missing user %d: %s", binding.UserID, err)
fail("sso_account_unavailable")
return
}
case ent.IsNotFound(err):
if !qq.RegisterEnabled {
dep.Logger().Info("QQ login rejected: provisioning disabled")
fail("sso_account_unavailable")
return
}
targetUser, err = qqProvisionUser(c, dep, httpClient, token, openID, qq)
if err != nil {
dep.Logger().Warning("QQ provisioning failed: %s", err)
fail("sso_account_unavailable")
return
}
if _, err := bindings.Bind(c, targetUser.ID, inventory.SsoProviderQQ, openID); err != nil {
dep.Logger().Warning("QQ binding failed: %s", err)
fail("sso_state_failed")
return
}
default:
dep.Logger().Warning("QQ binding lookup failed: %s", err)
fail("sso_state_failed")
return
}
if targetUser, err = dep.UserClient().LiftExpiredBan(c, targetUser); err != nil {
fail("sso_account_unavailable")
return
}
if err := checkUserStatus(c, targetUser); err != nil {
dep.Logger().Info("QQ login rejected: %s", err)
fail("sso_account_unavailable")
return
}
ticket := ssoTicketKey()
if err := dep.KV().Set(ticket, targetUser.ID, ssoTicketTTL); err != nil {
fail("sso_state_failed")
return
}
callback := settings.SiteURL(c).ResolveReference(&url.URL{Path: "callback/sso"})
q := callback.Query()
q.Set("ticket", ticket)
if state.Redirect != "" {
q.Set("redirect", state.Redirect)
}
callback.RawQuery = q.Encode()
c.Redirect(http.StatusFound, callback.String())
}
// Delete removes the caller's binding at the given provider. Removing the
// last viable sign-in method (no password, no passkey, no other binding)
// is refused so an SSO-provisioned account cannot lock itself out.
func (service *SsoUnbindService) Delete(c *gin.Context) error {
dep := dependency.FromContext(c)
u := inventory.UserFromContext(c)
if service.Provider != inventory.SsoProviderQQ {
return serializer.NewError(serializer.CodeParamErr, "Unknown provider", nil)
}
bindings := dep.SsoBindingClient()
if u.Password == "" {
passkeys, err := dep.UserClient().ListPasskeys(c, u.ID)
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to get user passkey", err)
}
others, err := bindings.ListByUser(c, u.ID)
if err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to get user linked accounts", err)
}
otherProviders := 0
for _, b := range others {
if b.Provider != service.Provider {
otherProviders++
}
}
if len(passkeys) == 0 && otherProviders == 0 {
return serializer.NewError(serializer.CodeParamErr, "Cannot unlink the only sign-in method", nil)
}
}
if err := bindings.Unbind(c, u.ID, service.Provider); err != nil {
return serializer.NewError(serializer.CodeDBError, "Failed to unlink account", err)
}
activity.Record(c, dep.SettingProvider(), dep.ActivityClient(), types.EventUnlinkAccount,
activity.Extra(map[string]any{"provider": service.Provider}))
return nil
}
// qqExchangeCode trades the authorization code for an access token. QQ
// answers with a form-encoded body (access_token=...&expires_in=...), not
// JSON.
func qqExchangeCode(c *gin.Context, client request.Client, code, redirectURI string, qq *setting.QQConnect) (string, error) {
form := url.Values{
"grant_type": {"authorization_code"},
"client_id": {qq.AppID},
"client_secret": {qq.AppSecret},
"code": {code},
"redirect_uri": {redirectURI},
}
resp, err := client.
Request(http.MethodPost, qqTokenEndpoint, strings.NewReader(form.Encode()),
request.WithContext(c),
request.WithTimeout(15*time.Second),
request.WithHeader(http.Header{"Content-Type": {"application/x-www-form-urlencoded"}}),
).
CheckHTTPResponse(http.StatusOK).
GetResponse()
if err != nil {
return "", fmt.Errorf("token request failed: %w", err)
}
return parseQQTokenResponse(resp)
}
// parseQQTokenResponse extracts the access token from QQ's form-encoded
// token reply. Error responses arrive as JSONP callback({"error":...})
// and fail the lookup naturally.
func parseQQTokenResponse(body string) (string, error) {
if values, err := url.ParseQuery(strings.TrimSpace(body)); err == nil {
if token := values.Get("access_token"); token != "" {
return token, nil
}
}
return "", errors.New("unexpected token response")
}
// qqFetchOpenID unwraps the JSONP response of /oauth2.0/me and verifies the
// echoed client_id to guard against token substitution.
func qqFetchOpenID(c *gin.Context, client request.Client, accessToken, appID string) (string, error) {
endpoint, _ := url.Parse(qqOpenIDEndpoint)
q := endpoint.Query()
q.Set("access_token", accessToken)
endpoint.RawQuery = q.Encode()
resp, err := client.
Request(http.MethodGet, endpoint.String(), nil,
request.WithContext(c),
request.WithTimeout(10*time.Second),
).
CheckHTTPResponse(http.StatusOK).
GetResponse()
if err != nil {
return "", fmt.Errorf("openid request failed: %w", err)
}
return parseQQOpenIDResponse(resp, appID)
}
// parseQQOpenIDResponse unwraps the JSONP reply of /oauth2.0/me and
// verifies the echoed client_id to guard against token substitution.
func parseQQOpenIDResponse(body, appID string) (string, error) {
body = strings.TrimSpace(body)
start := strings.Index(body, "(")
end := strings.LastIndex(body, ")")
if start < 0 || end <= start {
return "", errors.New("malformed openid response")
}
var payload qqOpenIDResponse
if err := json.Unmarshal([]byte(body[start+1:end]), &payload); err != nil {
return "", fmt.Errorf("malformed openid payload: %w", err)
}
if payload.ClientID != appID || payload.OpenID == "" {
return "", errors.New("openid client mismatch")
}
return payload.OpenID, nil
}
// qqProvisionUser creates the local account for a new QQ identity. QQ has
// no email claim, so a synthetic address under connect.qq.local is used;
// the nickname falls back to an openid suffix.
func qqProvisionUser(c *gin.Context, dep dependency.Dep, client request.Client, accessToken, openID string, qq *setting.QQConnect) (*ent.User, error) {
nick := ""
endpoint, _ := url.Parse(qqUserInfoEndpoint)
q := endpoint.Query()
q.Set("access_token", accessToken)
q.Set("oauth_consumer_key", qq.AppID)
q.Set("openid", openID)
endpoint.RawQuery = q.Encode()
if resp, err := client.
Request(http.MethodGet, endpoint.String(), nil,
request.WithContext(c),
request.WithTimeout(10*time.Second),
).
CheckHTTPResponse(http.StatusOK).
GetResponse(); err == nil {
var info qqUserInfoResponse
if err := json.Unmarshal([]byte(resp), &info); err == nil && info.Ret == 0 {
nick = strings.TrimSpace(info.Nickname)
}
} else {
dep.Logger().Warning("QQ userinfo request failed: %s", err)
}
if nick == "" {
nick = "QQ user " + openID[len(openID)-min(6, len(openID)):]
}
if len(nick) > 100 {
nick = nick[:100]
}
return dep.UserClient().Create(c, &inventory.NewUserArgs{
Email: fmt.Sprintf("qq_%s@%s", openID, qqConnectMailDomain),
Nick: nick,
Status: user.StatusActive,
GroupID: dep.SettingProvider().DefaultGroup(c),
})
}
// recordUserEvent logs an account event attributed to a user ID rather
// than the request actor (link-mode callbacks run for the linking user).
func recordUserEvent(c *gin.Context, dep dependency.Dep, uid int, event int, extra map[string]any) {
opts := []activity.Opt{activity.Actor(uid)}
if extra != nil {
opts = append(opts, activity.Extra(extra))
}
activity.Record(c, dep.SettingProvider(), dep.ActivityClient(), event, opts...)
}

@ -0,0 +1,49 @@
package user
import (
"testing"
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
"github.com/stretchr/testify/require"
)
func TestValidateQQConfig(t *testing.T) {
require.Error(t, validateQQConfig(&setting.QQConnect{}))
require.Error(t, validateQQConfig(&setting.QQConnect{Enabled: true}))
require.Error(t, validateQQConfig(&setting.QQConnect{Enabled: true, AppID: "id"}))
require.NoError(t, validateQQConfig(&setting.QQConnect{Enabled: true, AppID: "id", AppSecret: "secret"}))
}
func TestParseQQTokenResponse(t *testing.T) {
token, err := parseQQTokenResponse("access_token=ABC123&expires_in=7776000&refresh_token=DEF")
require.NoError(t, err)
require.Equal(t, "ABC123", token)
// JSONP error reply carries no access_token.
_, err = parseQQTokenResponse(`callback({"error":100016,"error_description":"app secret error"});`)
require.Error(t, err)
_, err = parseQQTokenResponse("")
require.Error(t, err)
_, err = parseQQTokenResponse("expires_in=7776000")
require.Error(t, err)
}
func TestParseQQOpenIDResponse(t *testing.T) {
openID, err := parseQQOpenIDResponse(`callback( {"client_id":"APPID","openid":"OPENIDXYZ"} );`, "APPID")
require.NoError(t, err)
require.Equal(t, "OPENIDXYZ", openID)
// A token minted for a different app must not be accepted.
_, err = parseQQOpenIDResponse(`callback( {"client_id":"OTHER","openid":"OPENIDXYZ"} );`, "APPID")
require.Error(t, err)
_, err = parseQQOpenIDResponse(`callback( {"client_id":"APPID"} );`, "APPID")
require.Error(t, err)
_, err = parseQQOpenIDResponse(`callback( not-json );`, "APPID")
require.Error(t, err)
_, err = parseQQOpenIDResponse(`{"client_id":"APPID","openid":"X"}`, "APPID")
require.Error(t, err)
_, err = parseQQOpenIDResponse("", "APPID")
require.Error(t, err)
}

@ -37,9 +37,17 @@ type UserSettings struct {
TrashRetention int `json:"trash_retention,omitempty"`
PreferredPolicy string `json:"preferred_policy,omitempty"`
OAuthGrants []OauthGrant `json:"oauth_grants,omitempty"`
LinkedAccounts []LinkedAccount `json:"linked_accounts,omitempty"`
}
func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser, grants []*ent.OAuthGrant) *UserSettings {
// LinkedAccount is an external identity bound to the local account
// (e.g. a QQ Connect openid). Subject is masked in the response.
type LinkedAccount struct {
Provider string `json:"provider"`
CreatedAt time.Time `json:"created_at"`
}
func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser, grants []*ent.OAuthGrant, bindings []*ent.SsoBinding) *UserSettings {
return &UserSettings{
VersionRetentionEnabled: u.Settings.VersionRetention,
VersionRetentionExt: u.Settings.VersionRetentionExt,
@ -57,6 +65,9 @@ func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Pa
OAuthGrants: lo.Map(grants, func(item *ent.OAuthGrant, index int) OauthGrant {
return BuildOauthGrant(item)
}),
LinkedAccounts: lo.Map(bindings, func(item *ent.SsoBinding, index int) LinkedAccount {
return LinkedAccount{Provider: item.Provider, CreatedAt: item.CreatedAt}
}),
}
}

@ -140,7 +140,12 @@ func GetUserSettings(c *gin.Context) (*UserSettings, error) {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user OAuth grants", err)
}
res := BuildUserSettings(u, passkeys, dep.UAParser(), grants)
bindings, err := dep.SsoBindingClient().ListByUser(c, u.ID)
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user linked accounts", err)
}
res := BuildUserSettings(u, passkeys, dep.UAParser(), grants, bindings)
if u.Settings.PreferredPolicy > 0 {
res.PreferredPolicy = hashid.EncodePolicyID(dep.HashIDEncoder(), u.Settings.PreferredPolicy)
}

@ -45,6 +45,9 @@ const (
type ssoState struct {
Nonce string
Redirect string
// LinkUserID is non-zero for account-link flows (QQ Connect): the
// resolved external identity binds to this user instead of signing in.
LinkUserID int
}
// SSOLoginService starts an inbound OIDC flow by redirecting the browser to

Loading…
Cancel
Save