From ce3d0a175fb9519d013e9e0bdbc3d22da9088fe7 Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Mon, 12 Dec 2022 14:40:52 +0100 Subject: [PATCH] fix(GODT-1896): Do not remove subscribed status for deleted mailboxes Ensure that we do not delete subscriptions entries for deleted mailboxes without an explicit unsubscribe event (RFC 3501 Section 6.3.9). If a new mailbox is created with the same name as previously deleted and subscribed mailbox, the subscription data is overwritten to point to the newly created mailbox. --- internal/db/ent/client.go | 97 ++++ internal/db/ent/config.go | 1 + internal/db/ent/ent.go | 2 + internal/db/ent/hook/hook.go | 13 + internal/db/ent/mailbox.go | 13 - internal/db/ent/mailbox/mailbox.go | 5 - internal/db/ent/mailbox/where.go | 21 - internal/db/ent/mailbox_create.go | 29 - internal/db/ent/mailbox_update.go | 42 -- internal/db/ent/migrate/schema.go | 32 +- internal/db/ent/mutation.go | 535 +++++++++++++++++-- internal/db/ent/predicate/predicate.go | 3 + internal/db/ent/runtime.go | 4 - internal/db/ent/schema/mailbox.go | 1 - internal/db/ent/schema/subscriptions.go | 30 ++ internal/db/ent/subscription.go | 122 +++++ internal/db/ent/subscription/subscription.go | 36 ++ internal/db/ent/subscription/where.go | 428 +++++++++++++++ internal/db/ent/subscription_create.go | 266 +++++++++ internal/db/ent/subscription_delete.go | 115 ++++ internal/db/ent/subscription_query.go | 526 ++++++++++++++++++ internal/db/ent/subscription_update.go | 392 ++++++++++++++ internal/db/ent/tx.go | 3 + internal/db/mailbox.go | 18 +- internal/db/subscription.go | 67 +++ internal/state/mailbox.go | 1 - internal/state/match.go | 51 +- internal/state/state.go | 84 ++- tests/lsub_test.go | 6 +- tests/unsubscribe_test.go | 32 ++ 30 files changed, 2768 insertions(+), 207 deletions(-) create mode 100644 internal/db/ent/schema/subscriptions.go create mode 100644 internal/db/ent/subscription.go create mode 100644 internal/db/ent/subscription/subscription.go create mode 100644 internal/db/ent/subscription/where.go create mode 100644 internal/db/ent/subscription_create.go create mode 100644 internal/db/ent/subscription_delete.go create mode 100644 internal/db/ent/subscription_query.go create mode 100644 internal/db/ent/subscription_update.go create mode 100644 internal/db/subscription.go diff --git a/internal/db/ent/client.go b/internal/db/ent/client.go index 78910e6b..cd73dbdd 100644 --- a/internal/db/ent/client.go +++ b/internal/db/ent/client.go @@ -17,6 +17,7 @@ import ( "github.com/ProtonMail/gluon/internal/db/ent/mailboxpermflag" "github.com/ProtonMail/gluon/internal/db/ent/message" "github.com/ProtonMail/gluon/internal/db/ent/messageflag" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" "github.com/ProtonMail/gluon/internal/db/ent/uid" "entgo.io/ent/dialect" @@ -41,6 +42,8 @@ type Client struct { Message *MessageClient // MessageFlag is the client for interacting with the MessageFlag builders. MessageFlag *MessageFlagClient + // Subscription is the client for interacting with the Subscription builders. + Subscription *SubscriptionClient // UID is the client for interacting with the UID builders. UID *UIDClient } @@ -62,6 +65,7 @@ func (c *Client) init() { c.MailboxPermFlag = NewMailboxPermFlagClient(c.config) c.Message = NewMessageClient(c.config) c.MessageFlag = NewMessageFlagClient(c.config) + c.Subscription = NewSubscriptionClient(c.config) c.UID = NewUIDClient(c.config) } @@ -102,6 +106,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { MailboxPermFlag: NewMailboxPermFlagClient(cfg), Message: NewMessageClient(cfg), MessageFlag: NewMessageFlagClient(cfg), + Subscription: NewSubscriptionClient(cfg), UID: NewUIDClient(cfg), }, nil } @@ -128,6 +133,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) MailboxPermFlag: NewMailboxPermFlagClient(cfg), Message: NewMessageClient(cfg), MessageFlag: NewMessageFlagClient(cfg), + Subscription: NewSubscriptionClient(cfg), UID: NewUIDClient(cfg), }, nil } @@ -163,6 +169,7 @@ func (c *Client) Use(hooks ...Hook) { c.MailboxPermFlag.Use(hooks...) c.Message.Use(hooks...) c.MessageFlag.Use(hooks...) + c.Subscription.Use(hooks...) c.UID.Use(hooks...) } @@ -818,6 +825,96 @@ func (c *MessageFlagClient) Hooks() []Hook { return c.hooks.MessageFlag } +// SubscriptionClient is a client for the Subscription schema. +type SubscriptionClient struct { + config +} + +// NewSubscriptionClient returns a client for the Subscription from the given config. +func NewSubscriptionClient(c config) *SubscriptionClient { + return &SubscriptionClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `subscription.Hooks(f(g(h())))`. +func (c *SubscriptionClient) Use(hooks ...Hook) { + c.hooks.Subscription = append(c.hooks.Subscription, hooks...) +} + +// Create returns a builder for creating a Subscription entity. +func (c *SubscriptionClient) Create() *SubscriptionCreate { + mutation := newSubscriptionMutation(c.config, OpCreate) + return &SubscriptionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Subscription entities. +func (c *SubscriptionClient) CreateBulk(builders ...*SubscriptionCreate) *SubscriptionCreateBulk { + return &SubscriptionCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Subscription. +func (c *SubscriptionClient) Update() *SubscriptionUpdate { + mutation := newSubscriptionMutation(c.config, OpUpdate) + return &SubscriptionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *SubscriptionClient) UpdateOne(s *Subscription) *SubscriptionUpdateOne { + mutation := newSubscriptionMutation(c.config, OpUpdateOne, withSubscription(s)) + return &SubscriptionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *SubscriptionClient) UpdateOneID(id int) *SubscriptionUpdateOne { + mutation := newSubscriptionMutation(c.config, OpUpdateOne, withSubscriptionID(id)) + return &SubscriptionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Subscription. +func (c *SubscriptionClient) Delete() *SubscriptionDelete { + mutation := newSubscriptionMutation(c.config, OpDelete) + return &SubscriptionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *SubscriptionClient) DeleteOne(s *Subscription) *SubscriptionDeleteOne { + return c.DeleteOneID(s.ID) +} + +// DeleteOne returns a builder for deleting the given entity by its id. +func (c *SubscriptionClient) DeleteOneID(id int) *SubscriptionDeleteOne { + builder := c.Delete().Where(subscription.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &SubscriptionDeleteOne{builder} +} + +// Query returns a query builder for Subscription. +func (c *SubscriptionClient) Query() *SubscriptionQuery { + return &SubscriptionQuery{ + config: c.config, + } +} + +// Get returns a Subscription entity by its id. +func (c *SubscriptionClient) Get(ctx context.Context, id int) (*Subscription, error) { + return c.Query().Where(subscription.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *SubscriptionClient) GetX(ctx context.Context, id int) *Subscription { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *SubscriptionClient) Hooks() []Hook { + return c.hooks.Subscription +} + // UIDClient is a client for the UID schema. type UIDClient struct { config diff --git a/internal/db/ent/config.go b/internal/db/ent/config.go index d4e33a83..e195e8b6 100644 --- a/internal/db/ent/config.go +++ b/internal/db/ent/config.go @@ -30,6 +30,7 @@ type hooks struct { MailboxPermFlag []ent.Hook Message []ent.Hook MessageFlag []ent.Hook + Subscription []ent.Hook UID []ent.Hook } diff --git a/internal/db/ent/ent.go b/internal/db/ent/ent.go index 61798873..d663d9e6 100644 --- a/internal/db/ent/ent.go +++ b/internal/db/ent/ent.go @@ -16,6 +16,7 @@ import ( "github.com/ProtonMail/gluon/internal/db/ent/mailboxpermflag" "github.com/ProtonMail/gluon/internal/db/ent/message" "github.com/ProtonMail/gluon/internal/db/ent/messageflag" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" "github.com/ProtonMail/gluon/internal/db/ent/uid" ) @@ -43,6 +44,7 @@ func columnChecker(table string) func(string) error { mailboxpermflag.Table: mailboxpermflag.ValidColumn, message.Table: message.ValidColumn, messageflag.Table: messageflag.ValidColumn, + subscription.Table: subscription.ValidColumn, uid.Table: uid.ValidColumn, } check, ok := checks[table] diff --git a/internal/db/ent/hook/hook.go b/internal/db/ent/hook/hook.go index 1980d43a..e9043e83 100644 --- a/internal/db/ent/hook/hook.go +++ b/internal/db/ent/hook/hook.go @@ -87,6 +87,19 @@ func (f MessageFlagFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, return f(ctx, mv) } +// The SubscriptionFunc type is an adapter to allow the use of ordinary +// function as Subscription mutator. +type SubscriptionFunc func(context.Context, *ent.SubscriptionMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f SubscriptionFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + mv, ok := m.(*ent.SubscriptionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SubscriptionMutation", m) + } + return f(ctx, mv) +} + // The UIDFunc type is an adapter to allow the use of ordinary // function as UID mutator. type UIDFunc func(context.Context, *ent.UIDMutation) (ent.Value, error) diff --git a/internal/db/ent/mailbox.go b/internal/db/ent/mailbox.go index b863a41b..bb55f675 100644 --- a/internal/db/ent/mailbox.go +++ b/internal/db/ent/mailbox.go @@ -24,8 +24,6 @@ type Mailbox struct { UIDNext imap.UID `json:"UIDNext,omitempty"` // UIDValidity holds the value of the "UIDValidity" field. UIDValidity imap.UID `json:"UIDValidity,omitempty"` - // Subscribed holds the value of the "Subscribed" field. - Subscribed bool `json:"Subscribed,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the MailboxQuery when eager-loading is set. Edges MailboxEdges `json:"edges"` @@ -87,8 +85,6 @@ func (*Mailbox) scanValues(columns []string) ([]interface{}, error) { values := make([]interface{}, len(columns)) for i := range columns { switch columns[i] { - case mailbox.FieldSubscribed: - values[i] = new(sql.NullBool) case mailbox.FieldID, mailbox.FieldUIDNext, mailbox.FieldUIDValidity: values[i] = new(sql.NullInt64) case mailbox.FieldRemoteID, mailbox.FieldName: @@ -138,12 +134,6 @@ func (m *Mailbox) assignValues(columns []string, values []interface{}) error { } else if value.Valid { m.UIDValidity = imap.UID(value.Int64) } - case mailbox.FieldSubscribed: - if value, ok := values[i].(*sql.NullBool); !ok { - return fmt.Errorf("unexpected type %T for field Subscribed", values[i]) - } else if value.Valid { - m.Subscribed = value.Bool - } } } return nil @@ -203,9 +193,6 @@ func (m *Mailbox) String() string { builder.WriteString(", ") builder.WriteString("UIDValidity=") builder.WriteString(fmt.Sprintf("%v", m.UIDValidity)) - builder.WriteString(", ") - builder.WriteString("Subscribed=") - builder.WriteString(fmt.Sprintf("%v", m.Subscribed)) builder.WriteByte(')') return builder.String() } diff --git a/internal/db/ent/mailbox/mailbox.go b/internal/db/ent/mailbox/mailbox.go index 57a8ab03..d44e363f 100644 --- a/internal/db/ent/mailbox/mailbox.go +++ b/internal/db/ent/mailbox/mailbox.go @@ -19,8 +19,6 @@ const ( FieldUIDNext = "uid_next" // FieldUIDValidity holds the string denoting the uidvalidity field in the database. FieldUIDValidity = "uid_validity" - // FieldSubscribed holds the string denoting the subscribed field in the database. - FieldSubscribed = "subscribed" // EdgeUIDs holds the string denoting the uids edge name in mutations. EdgeUIDs = "UIDs" // EdgeFlags holds the string denoting the flags edge name in mutations. @@ -68,7 +66,6 @@ var Columns = []string{ FieldName, FieldUIDNext, FieldUIDValidity, - FieldSubscribed, } // ValidColumn reports if the column name is valid (part of the table columns). @@ -86,6 +83,4 @@ var ( DefaultUIDNext imap.UID // DefaultUIDValidity holds the default value on creation for the "UIDValidity" field. DefaultUIDValidity imap.UID - // DefaultSubscribed holds the default value on creation for the "Subscribed" field. - DefaultSubscribed bool ) diff --git a/internal/db/ent/mailbox/where.go b/internal/db/ent/mailbox/where.go index 64940642..0fbe08e1 100644 --- a/internal/db/ent/mailbox/where.go +++ b/internal/db/ent/mailbox/where.go @@ -111,13 +111,6 @@ func UIDValidity(v imap.UID) predicate.Mailbox { }) } -// Subscribed applies equality check predicate on the "Subscribed" field. It's identical to SubscribedEQ. -func Subscribed(v bool) predicate.Mailbox { - return predicate.Mailbox(func(s *sql.Selector) { - s.Where(sql.EQ(s.C(FieldSubscribed), v)) - }) -} - // RemoteIDEQ applies the EQ predicate on the "RemoteID" field. func RemoteIDEQ(v imap.MailboxID) predicate.Mailbox { vc := string(v) @@ -481,20 +474,6 @@ func UIDValidityLTE(v imap.UID) predicate.Mailbox { }) } -// SubscribedEQ applies the EQ predicate on the "Subscribed" field. -func SubscribedEQ(v bool) predicate.Mailbox { - return predicate.Mailbox(func(s *sql.Selector) { - s.Where(sql.EQ(s.C(FieldSubscribed), v)) - }) -} - -// SubscribedNEQ applies the NEQ predicate on the "Subscribed" field. -func SubscribedNEQ(v bool) predicate.Mailbox { - return predicate.Mailbox(func(s *sql.Selector) { - s.Where(sql.NEQ(s.C(FieldSubscribed), v)) - }) -} - // HasUIDs applies the HasEdge predicate on the "UIDs" edge. func HasUIDs() predicate.Mailbox { return predicate.Mailbox(func(s *sql.Selector) { diff --git a/internal/db/ent/mailbox_create.go b/internal/db/ent/mailbox_create.go index b1fa61a3..ced2858f 100644 --- a/internal/db/ent/mailbox_create.go +++ b/internal/db/ent/mailbox_create.go @@ -72,20 +72,6 @@ func (mc *MailboxCreate) SetNillableUIDValidity(i *imap.UID) *MailboxCreate { return mc } -// SetSubscribed sets the "Subscribed" field. -func (mc *MailboxCreate) SetSubscribed(b bool) *MailboxCreate { - mc.mutation.SetSubscribed(b) - return mc -} - -// SetNillableSubscribed sets the "Subscribed" field if the given value is not nil. -func (mc *MailboxCreate) SetNillableSubscribed(b *bool) *MailboxCreate { - if b != nil { - mc.SetSubscribed(*b) - } - return mc -} - // SetID sets the "id" field. func (mc *MailboxCreate) SetID(imi imap.InternalMailboxID) *MailboxCreate { mc.mutation.SetID(imi) @@ -237,10 +223,6 @@ func (mc *MailboxCreate) defaults() { v := mailbox.DefaultUIDValidity mc.mutation.SetUIDValidity(v) } - if _, ok := mc.mutation.Subscribed(); !ok { - v := mailbox.DefaultSubscribed - mc.mutation.SetSubscribed(v) - } } // check runs all checks and user-defined validators on the builder. @@ -254,9 +236,6 @@ func (mc *MailboxCreate) check() error { if _, ok := mc.mutation.UIDValidity(); !ok { return &ValidationError{Name: "UIDValidity", err: errors.New(`ent: missing required field "Mailbox.UIDValidity"`)} } - if _, ok := mc.mutation.Subscribed(); !ok { - return &ValidationError{Name: "Subscribed", err: errors.New(`ent: missing required field "Mailbox.Subscribed"`)} - } return nil } @@ -322,14 +301,6 @@ func (mc *MailboxCreate) createSpec() (*Mailbox, *sqlgraph.CreateSpec) { }) _node.UIDValidity = value } - if value, ok := mc.mutation.Subscribed(); ok { - _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ - Type: field.TypeBool, - Value: value, - Column: mailbox.FieldSubscribed, - }) - _node.Subscribed = value - } if nodes := mc.mutation.UIDsIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/internal/db/ent/mailbox_update.go b/internal/db/ent/mailbox_update.go index 46ceca2a..8ddf66ac 100644 --- a/internal/db/ent/mailbox_update.go +++ b/internal/db/ent/mailbox_update.go @@ -100,20 +100,6 @@ func (mu *MailboxUpdate) AddUIDValidity(i imap.UID) *MailboxUpdate { return mu } -// SetSubscribed sets the "Subscribed" field. -func (mu *MailboxUpdate) SetSubscribed(b bool) *MailboxUpdate { - mu.mutation.SetSubscribed(b) - return mu -} - -// SetNillableSubscribed sets the "Subscribed" field if the given value is not nil. -func (mu *MailboxUpdate) SetNillableSubscribed(b *bool) *MailboxUpdate { - if b != nil { - mu.SetSubscribed(*b) - } - return mu -} - // AddUIDIDs adds the "UIDs" edge to the UID entity by IDs. func (mu *MailboxUpdate) AddUIDIDs(ids ...int) *MailboxUpdate { mu.mutation.AddUIDIDs(ids...) @@ -383,13 +369,6 @@ func (mu *MailboxUpdate) sqlSave(ctx context.Context) (n int, err error) { Column: mailbox.FieldUIDValidity, }) } - if value, ok := mu.mutation.Subscribed(); ok { - _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ - Type: field.TypeBool, - Value: value, - Column: mailbox.FieldSubscribed, - }) - } if mu.mutation.UIDsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -693,20 +672,6 @@ func (muo *MailboxUpdateOne) AddUIDValidity(i imap.UID) *MailboxUpdateOne { return muo } -// SetSubscribed sets the "Subscribed" field. -func (muo *MailboxUpdateOne) SetSubscribed(b bool) *MailboxUpdateOne { - muo.mutation.SetSubscribed(b) - return muo -} - -// SetNillableSubscribed sets the "Subscribed" field if the given value is not nil. -func (muo *MailboxUpdateOne) SetNillableSubscribed(b *bool) *MailboxUpdateOne { - if b != nil { - muo.SetSubscribed(*b) - } - return muo -} - // AddUIDIDs adds the "UIDs" edge to the UID entity by IDs. func (muo *MailboxUpdateOne) AddUIDIDs(ids ...int) *MailboxUpdateOne { muo.mutation.AddUIDIDs(ids...) @@ -1006,13 +971,6 @@ func (muo *MailboxUpdateOne) sqlSave(ctx context.Context) (_node *Mailbox, err e Column: mailbox.FieldUIDValidity, }) } - if value, ok := muo.mutation.Subscribed(); ok { - _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ - Type: field.TypeBool, - Value: value, - Column: mailbox.FieldSubscribed, - }) - } if muo.mutation.UIDsCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/internal/db/ent/migrate/schema.go b/internal/db/ent/migrate/schema.go index 545188bb..ad902b62 100644 --- a/internal/db/ent/migrate/schema.go +++ b/internal/db/ent/migrate/schema.go @@ -15,7 +15,6 @@ var ( {Name: "name", Type: field.TypeString, Unique: true}, {Name: "uid_next", Type: field.TypeUint32, Default: 1}, {Name: "uid_validity", Type: field.TypeUint32, Default: 1}, - {Name: "subscribed", Type: field.TypeBool, Default: true}, } // MailboxesTable holds the schema information for the "mailboxes" table. MailboxesTable = &schema.Table{ @@ -149,6 +148,36 @@ var ( }, }, } + // SubscriptionsColumns holds the columns for the "subscriptions" table. + SubscriptionsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "name", Type: field.TypeString, Unique: true}, + {Name: "mailbox_id", Type: field.TypeUint64, Unique: true}, + {Name: "remote_id", Type: field.TypeString, Unique: true, Nullable: true}, + } + // SubscriptionsTable holds the schema information for the "subscriptions" table. + SubscriptionsTable = &schema.Table{ + Name: "subscriptions", + Columns: SubscriptionsColumns, + PrimaryKey: []*schema.Column{SubscriptionsColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "subscription_mailbox_id", + Unique: false, + Columns: []*schema.Column{SubscriptionsColumns[2]}, + }, + { + Name: "subscription_remote_id", + Unique: false, + Columns: []*schema.Column{SubscriptionsColumns[3]}, + }, + { + Name: "subscription_name", + Unique: false, + Columns: []*schema.Column{SubscriptionsColumns[1]}, + }, + }, + } // UIDsColumns holds the columns for the "ui_ds" table. UIDsColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, @@ -193,6 +222,7 @@ var ( MailboxPermFlagsTable, MessagesTable, MessageFlagsTable, + SubscriptionsTable, UIDsTable, } ) diff --git a/internal/db/ent/mutation.go b/internal/db/ent/mutation.go index c5dfcc30..eff0b6ff 100644 --- a/internal/db/ent/mutation.go +++ b/internal/db/ent/mutation.go @@ -17,6 +17,7 @@ import ( "github.com/ProtonMail/gluon/internal/db/ent/message" "github.com/ProtonMail/gluon/internal/db/ent/messageflag" "github.com/ProtonMail/gluon/internal/db/ent/predicate" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" "github.com/ProtonMail/gluon/internal/db/ent/uid" "entgo.io/ent" @@ -37,6 +38,7 @@ const ( TypeMailboxPermFlag = "MailboxPermFlag" TypeMessage = "Message" TypeMessageFlag = "MessageFlag" + TypeSubscription = "Subscription" TypeUID = "UID" ) @@ -52,7 +54,6 @@ type MailboxMutation struct { add_UIDNext *imap.UID _UIDValidity *imap.UID add_UIDValidity *imap.UID - _Subscribed *bool clearedFields map[string]struct{} _UIDs map[int]struct{} removed_UIDs map[int]struct{} @@ -372,42 +373,6 @@ func (m *MailboxMutation) ResetUIDValidity() { m.add_UIDValidity = nil } -// SetSubscribed sets the "Subscribed" field. -func (m *MailboxMutation) SetSubscribed(b bool) { - m._Subscribed = &b -} - -// Subscribed returns the value of the "Subscribed" field in the mutation. -func (m *MailboxMutation) Subscribed() (r bool, exists bool) { - v := m._Subscribed - if v == nil { - return - } - return *v, true -} - -// OldSubscribed returns the old "Subscribed" field's value of the Mailbox entity. -// If the Mailbox 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 *MailboxMutation) OldSubscribed(ctx context.Context) (v bool, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldSubscribed is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldSubscribed requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldSubscribed: %w", err) - } - return oldValue.Subscribed, nil -} - -// ResetSubscribed resets all changes to the "Subscribed" field. -func (m *MailboxMutation) ResetSubscribed() { - m._Subscribed = nil -} - // AddUIDIDs adds the "UIDs" edge to the UID entity by ids. func (m *MailboxMutation) AddUIDIDs(ids ...int) { if m._UIDs == nil { @@ -643,7 +608,7 @@ func (m *MailboxMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *MailboxMutation) Fields() []string { - fields := make([]string, 0, 5) + fields := make([]string, 0, 4) if m._RemoteID != nil { fields = append(fields, mailbox.FieldRemoteID) } @@ -656,9 +621,6 @@ func (m *MailboxMutation) Fields() []string { if m._UIDValidity != nil { fields = append(fields, mailbox.FieldUIDValidity) } - if m._Subscribed != nil { - fields = append(fields, mailbox.FieldSubscribed) - } return fields } @@ -675,8 +637,6 @@ func (m *MailboxMutation) Field(name string) (ent.Value, bool) { return m.UIDNext() case mailbox.FieldUIDValidity: return m.UIDValidity() - case mailbox.FieldSubscribed: - return m.Subscribed() } return nil, false } @@ -694,8 +654,6 @@ func (m *MailboxMutation) OldField(ctx context.Context, name string) (ent.Value, return m.OldUIDNext(ctx) case mailbox.FieldUIDValidity: return m.OldUIDValidity(ctx) - case mailbox.FieldSubscribed: - return m.OldSubscribed(ctx) } return nil, fmt.Errorf("unknown Mailbox field %s", name) } @@ -733,13 +691,6 @@ func (m *MailboxMutation) SetField(name string, value ent.Value) error { } m.SetUIDValidity(v) return nil - case mailbox.FieldSubscribed: - v, ok := value.(bool) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetSubscribed(v) - return nil } return fmt.Errorf("unknown Mailbox field %s", name) } @@ -837,9 +788,6 @@ func (m *MailboxMutation) ResetField(name string) error { case mailbox.FieldUIDValidity: m.ResetUIDValidity() return nil - case mailbox.FieldSubscribed: - m.ResetSubscribed() - return nil } return fmt.Errorf("unknown Mailbox field %s", name) } @@ -3194,6 +3142,483 @@ func (m *MessageFlagMutation) ResetEdge(name string) error { return fmt.Errorf("unknown MessageFlag edge %s", name) } +// SubscriptionMutation represents an operation that mutates the Subscription nodes in the graph. +type SubscriptionMutation struct { + config + op Op + typ string + id *int + _Name *string + _MailboxID *imap.InternalMailboxID + add_MailboxID *imap.InternalMailboxID + _RemoteID *imap.MailboxID + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*Subscription, error) + predicates []predicate.Subscription +} + +var _ ent.Mutation = (*SubscriptionMutation)(nil) + +// subscriptionOption allows management of the mutation configuration using functional options. +type subscriptionOption func(*SubscriptionMutation) + +// newSubscriptionMutation creates new mutation for the Subscription entity. +func newSubscriptionMutation(c config, op Op, opts ...subscriptionOption) *SubscriptionMutation { + m := &SubscriptionMutation{ + config: c, + op: op, + typ: TypeSubscription, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withSubscriptionID sets the ID field of the mutation. +func withSubscriptionID(id int) subscriptionOption { + return func(m *SubscriptionMutation) { + var ( + err error + once sync.Once + value *Subscription + ) + m.oldValue = func(ctx context.Context) (*Subscription, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Subscription.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withSubscription sets the old Subscription of the mutation. +func withSubscription(node *Subscription) subscriptionOption { + return func(m *SubscriptionMutation) { + m.oldValue = func(context.Context) (*Subscription, 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 SubscriptionMutation) 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 SubscriptionMutation) 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 *SubscriptionMutation) 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 *SubscriptionMutation) 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().Subscription.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetName sets the "Name" field. +func (m *SubscriptionMutation) SetName(s string) { + m._Name = &s +} + +// Name returns the value of the "Name" field in the mutation. +func (m *SubscriptionMutation) Name() (r string, exists bool) { + v := m._Name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "Name" field's value of the Subscription entity. +// If the Subscription 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 *SubscriptionMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "Name" field. +func (m *SubscriptionMutation) ResetName() { + m._Name = nil +} + +// SetMailboxID sets the "MailboxID" field. +func (m *SubscriptionMutation) SetMailboxID(imi imap.InternalMailboxID) { + m._MailboxID = &imi + m.add_MailboxID = nil +} + +// MailboxID returns the value of the "MailboxID" field in the mutation. +func (m *SubscriptionMutation) MailboxID() (r imap.InternalMailboxID, exists bool) { + v := m._MailboxID + if v == nil { + return + } + return *v, true +} + +// OldMailboxID returns the old "MailboxID" field's value of the Subscription entity. +// If the Subscription 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 *SubscriptionMutation) OldMailboxID(ctx context.Context) (v imap.InternalMailboxID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMailboxID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMailboxID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMailboxID: %w", err) + } + return oldValue.MailboxID, nil +} + +// AddMailboxID adds imi to the "MailboxID" field. +func (m *SubscriptionMutation) AddMailboxID(imi imap.InternalMailboxID) { + if m.add_MailboxID != nil { + *m.add_MailboxID += imi + } else { + m.add_MailboxID = &imi + } +} + +// AddedMailboxID returns the value that was added to the "MailboxID" field in this mutation. +func (m *SubscriptionMutation) AddedMailboxID() (r imap.InternalMailboxID, exists bool) { + v := m.add_MailboxID + if v == nil { + return + } + return *v, true +} + +// ResetMailboxID resets all changes to the "MailboxID" field. +func (m *SubscriptionMutation) ResetMailboxID() { + m._MailboxID = nil + m.add_MailboxID = nil +} + +// SetRemoteID sets the "RemoteID" field. +func (m *SubscriptionMutation) SetRemoteID(ii imap.MailboxID) { + m._RemoteID = &ii +} + +// RemoteID returns the value of the "RemoteID" field in the mutation. +func (m *SubscriptionMutation) RemoteID() (r imap.MailboxID, exists bool) { + v := m._RemoteID + if v == nil { + return + } + return *v, true +} + +// OldRemoteID returns the old "RemoteID" field's value of the Subscription entity. +// If the Subscription 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 *SubscriptionMutation) OldRemoteID(ctx context.Context) (v imap.MailboxID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRemoteID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRemoteID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRemoteID: %w", err) + } + return oldValue.RemoteID, nil +} + +// ClearRemoteID clears the value of the "RemoteID" field. +func (m *SubscriptionMutation) ClearRemoteID() { + m._RemoteID = nil + m.clearedFields[subscription.FieldRemoteID] = struct{}{} +} + +// RemoteIDCleared returns if the "RemoteID" field was cleared in this mutation. +func (m *SubscriptionMutation) RemoteIDCleared() bool { + _, ok := m.clearedFields[subscription.FieldRemoteID] + return ok +} + +// ResetRemoteID resets all changes to the "RemoteID" field. +func (m *SubscriptionMutation) ResetRemoteID() { + m._RemoteID = nil + delete(m.clearedFields, subscription.FieldRemoteID) +} + +// Where appends a list predicates to the SubscriptionMutation builder. +func (m *SubscriptionMutation) Where(ps ...predicate.Subscription) { + m.predicates = append(m.predicates, ps...) +} + +// Op returns the operation name. +func (m *SubscriptionMutation) Op() Op { + return m.op +} + +// Type returns the node type of this mutation (Subscription). +func (m *SubscriptionMutation) 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 *SubscriptionMutation) Fields() []string { + fields := make([]string, 0, 3) + if m._Name != nil { + fields = append(fields, subscription.FieldName) + } + if m._MailboxID != nil { + fields = append(fields, subscription.FieldMailboxID) + } + if m._RemoteID != nil { + fields = append(fields, subscription.FieldRemoteID) + } + 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 *SubscriptionMutation) Field(name string) (ent.Value, bool) { + switch name { + case subscription.FieldName: + return m.Name() + case subscription.FieldMailboxID: + return m.MailboxID() + case subscription.FieldRemoteID: + return m.RemoteID() + } + 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 *SubscriptionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case subscription.FieldName: + return m.OldName(ctx) + case subscription.FieldMailboxID: + return m.OldMailboxID(ctx) + case subscription.FieldRemoteID: + return m.OldRemoteID(ctx) + } + return nil, fmt.Errorf("unknown Subscription 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 *SubscriptionMutation) SetField(name string, value ent.Value) error { + switch name { + case subscription.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case subscription.FieldMailboxID: + v, ok := value.(imap.InternalMailboxID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMailboxID(v) + return nil + case subscription.FieldRemoteID: + v, ok := value.(imap.MailboxID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRemoteID(v) + return nil + } + return fmt.Errorf("unknown Subscription field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *SubscriptionMutation) AddedFields() []string { + var fields []string + if m.add_MailboxID != nil { + fields = append(fields, subscription.FieldMailboxID) + } + 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 *SubscriptionMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case subscription.FieldMailboxID: + return m.AddedMailboxID() + } + 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 *SubscriptionMutation) AddField(name string, value ent.Value) error { + switch name { + case subscription.FieldMailboxID: + v, ok := value.(imap.InternalMailboxID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddMailboxID(v) + return nil + } + return fmt.Errorf("unknown Subscription numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *SubscriptionMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(subscription.FieldRemoteID) { + fields = append(fields, subscription.FieldRemoteID) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *SubscriptionMutation) 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 *SubscriptionMutation) ClearField(name string) error { + switch name { + case subscription.FieldRemoteID: + m.ClearRemoteID() + return nil + } + return fmt.Errorf("unknown Subscription 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 *SubscriptionMutation) ResetField(name string) error { + switch name { + case subscription.FieldName: + m.ResetName() + return nil + case subscription.FieldMailboxID: + m.ResetMailboxID() + return nil + case subscription.FieldRemoteID: + m.ResetRemoteID() + return nil + } + return fmt.Errorf("unknown Subscription field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *SubscriptionMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *SubscriptionMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *SubscriptionMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *SubscriptionMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *SubscriptionMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *SubscriptionMutation) EdgeCleared(name string) bool { + 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 *SubscriptionMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown Subscription 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 *SubscriptionMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown Subscription edge %s", name) +} + // UIDMutation represents an operation that mutates the UID nodes in the graph. type UIDMutation struct { config diff --git a/internal/db/ent/predicate/predicate.go b/internal/db/ent/predicate/predicate.go index cddae75f..8fe178f2 100644 --- a/internal/db/ent/predicate/predicate.go +++ b/internal/db/ent/predicate/predicate.go @@ -24,5 +24,8 @@ type Message func(*sql.Selector) // MessageFlag is the predicate function for messageflag builders. type MessageFlag func(*sql.Selector) +// Subscription is the predicate function for subscription builders. +type Subscription func(*sql.Selector) + // UID is the predicate function for uid builders. type UID func(*sql.Selector) diff --git a/internal/db/ent/runtime.go b/internal/db/ent/runtime.go index a36a489b..8b0fdb72 100644 --- a/internal/db/ent/runtime.go +++ b/internal/db/ent/runtime.go @@ -24,10 +24,6 @@ func init() { mailboxDescUIDValidity := mailboxFields[4].Descriptor() // mailbox.DefaultUIDValidity holds the default value on creation for the UIDValidity field. mailbox.DefaultUIDValidity = imap.UID(mailboxDescUIDValidity.Default.(uint32)) - // mailboxDescSubscribed is the schema descriptor for Subscribed field. - mailboxDescSubscribed := mailboxFields[5].Descriptor() - // mailbox.DefaultSubscribed holds the default value on creation for the Subscribed field. - mailbox.DefaultSubscribed = mailboxDescSubscribed.Default.(bool) messageFields := schema.Message{}.Fields() _ = messageFields // messageDescDeleted is the schema descriptor for Deleted field. diff --git a/internal/db/ent/schema/mailbox.go b/internal/db/ent/schema/mailbox.go index e8c14663..68b4133f 100644 --- a/internal/db/ent/schema/mailbox.go +++ b/internal/db/ent/schema/mailbox.go @@ -22,7 +22,6 @@ func (Mailbox) Fields() []ent.Field { field.String("Name").Unique(), field.Uint32("UIDNext").Default(1).GoType(imap.UID(0)), field.Uint32("UIDValidity").Default(1).GoType(imap.UID(0)), - field.Bool("Subscribed").Default(true), } } diff --git a/internal/db/ent/schema/subscriptions.go b/internal/db/ent/schema/subscriptions.go new file mode 100644 index 00000000..b6633168 --- /dev/null +++ b/internal/db/ent/schema/subscriptions.go @@ -0,0 +1,30 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + "github.com/ProtonMail/gluon/imap" +) + +// Subscription holds the schema definition for the Subscription entity. +type Subscription struct { + ent.Schema +} + +// Fields of the Mailbox. +func (Subscription) Fields() []ent.Field { + return []ent.Field{ + field.String("Name").Unique(), + field.Uint64("MailboxID").GoType(imap.InternalMailboxID(0)).Unique(), + field.String("RemoteID").Optional().Unique().GoType(imap.MailboxID("")), + } +} + +func (Subscription) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("MailboxID"), + index.Fields("RemoteID"), + index.Fields("Name"), + } +} diff --git a/internal/db/ent/subscription.go b/internal/db/ent/subscription.go new file mode 100644 index 00000000..f0a43d29 --- /dev/null +++ b/internal/db/ent/subscription.go @@ -0,0 +1,122 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent/dialect/sql" + "github.com/ProtonMail/gluon/imap" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" +) + +// Subscription is the model entity for the Subscription schema. +type Subscription struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // Name holds the value of the "Name" field. + Name string `json:"Name,omitempty"` + // MailboxID holds the value of the "MailboxID" field. + MailboxID imap.InternalMailboxID `json:"MailboxID,omitempty"` + // RemoteID holds the value of the "RemoteID" field. + RemoteID imap.MailboxID `json:"RemoteID,omitempty"` +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Subscription) scanValues(columns []string) ([]interface{}, error) { + values := make([]interface{}, len(columns)) + for i := range columns { + switch columns[i] { + case subscription.FieldID, subscription.FieldMailboxID: + values[i] = new(sql.NullInt64) + case subscription.FieldName, subscription.FieldRemoteID: + values[i] = new(sql.NullString) + default: + return nil, fmt.Errorf("unexpected column %q for type Subscription", columns[i]) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Subscription fields. +func (s *Subscription) assignValues(columns []string, values []interface{}) 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 subscription.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + s.ID = int(value.Int64) + case subscription.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field Name", values[i]) + } else if value.Valid { + s.Name = value.String + } + case subscription.FieldMailboxID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field MailboxID", values[i]) + } else if value.Valid { + s.MailboxID = imap.InternalMailboxID(value.Int64) + } + case subscription.FieldRemoteID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field RemoteID", values[i]) + } else if value.Valid { + s.RemoteID = imap.MailboxID(value.String) + } + } + } + return nil +} + +// Update returns a builder for updating this Subscription. +// Note that you need to call Subscription.Unwrap() before calling this method if this Subscription +// was returned from a transaction, and the transaction was committed or rolled back. +func (s *Subscription) Update() *SubscriptionUpdateOne { + return (&SubscriptionClient{config: s.config}).UpdateOne(s) +} + +// Unwrap unwraps the Subscription 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 (s *Subscription) Unwrap() *Subscription { + _tx, ok := s.config.driver.(*txDriver) + if !ok { + panic("ent: Subscription is not a transactional entity") + } + s.config.driver = _tx.drv + return s +} + +// String implements the fmt.Stringer. +func (s *Subscription) String() string { + var builder strings.Builder + builder.WriteString("Subscription(") + builder.WriteString(fmt.Sprintf("id=%v, ", s.ID)) + builder.WriteString("Name=") + builder.WriteString(s.Name) + builder.WriteString(", ") + builder.WriteString("MailboxID=") + builder.WriteString(fmt.Sprintf("%v", s.MailboxID)) + builder.WriteString(", ") + builder.WriteString("RemoteID=") + builder.WriteString(fmt.Sprintf("%v", s.RemoteID)) + builder.WriteByte(')') + return builder.String() +} + +// Subscriptions is a parsable slice of Subscription. +type Subscriptions []*Subscription + +func (s Subscriptions) config(cfg config) { + for _i := range s { + s[_i].config = cfg + } +} diff --git a/internal/db/ent/subscription/subscription.go b/internal/db/ent/subscription/subscription.go new file mode 100644 index 00000000..8075b1fc --- /dev/null +++ b/internal/db/ent/subscription/subscription.go @@ -0,0 +1,36 @@ +// Code generated by ent, DO NOT EDIT. + +package subscription + +const ( + // Label holds the string label denoting the subscription type in the database. + Label = "subscription" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldMailboxID holds the string denoting the mailboxid field in the database. + FieldMailboxID = "mailbox_id" + // FieldRemoteID holds the string denoting the remoteid field in the database. + FieldRemoteID = "remote_id" + // Table holds the table name of the subscription in the database. + Table = "subscriptions" +) + +// Columns holds all SQL columns for subscription fields. +var Columns = []string{ + FieldID, + FieldName, + FieldMailboxID, + FieldRemoteID, +} + +// 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 +} diff --git a/internal/db/ent/subscription/where.go b/internal/db/ent/subscription/where.go new file mode 100644 index 00000000..ec80b929 --- /dev/null +++ b/internal/db/ent/subscription/where.go @@ -0,0 +1,428 @@ +// Code generated by ent, DO NOT EDIT. + +package subscription + +import ( + "entgo.io/ent/dialect/sql" + "github.com/ProtonMail/gluon/imap" + "github.com/ProtonMail/gluon/internal/db/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldID), id)) + }) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + v := make([]interface{}, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.In(s.C(FieldID), v...)) + }) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + v := make([]interface{}, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.NotIn(s.C(FieldID), v...)) + }) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldID), id)) + }) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldID), id)) + }) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldID), id)) + }) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldID), id)) + }) +} + +// Name applies equality check predicate on the "Name" field. It's identical to NameEQ. +func Name(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldName), v)) + }) +} + +// MailboxID applies equality check predicate on the "MailboxID" field. It's identical to MailboxIDEQ. +func MailboxID(v imap.InternalMailboxID) predicate.Subscription { + vc := uint64(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldMailboxID), vc)) + }) +} + +// RemoteID applies equality check predicate on the "RemoteID" field. It's identical to RemoteIDEQ. +func RemoteID(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldRemoteID), vc)) + }) +} + +// NameEQ applies the EQ predicate on the "Name" field. +func NameEQ(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldName), v)) + }) +} + +// NameNEQ applies the NEQ predicate on the "Name" field. +func NameNEQ(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldName), v)) + }) +} + +// NameIn applies the In predicate on the "Name" field. +func NameIn(vs ...string) predicate.Subscription { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.In(s.C(FieldName), v...)) + }) +} + +// NameNotIn applies the NotIn predicate on the "Name" field. +func NameNotIn(vs ...string) predicate.Subscription { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NotIn(s.C(FieldName), v...)) + }) +} + +// NameGT applies the GT predicate on the "Name" field. +func NameGT(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldName), v)) + }) +} + +// NameGTE applies the GTE predicate on the "Name" field. +func NameGTE(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldName), v)) + }) +} + +// NameLT applies the LT predicate on the "Name" field. +func NameLT(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldName), v)) + }) +} + +// NameLTE applies the LTE predicate on the "Name" field. +func NameLTE(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldName), v)) + }) +} + +// NameContains applies the Contains predicate on the "Name" field. +func NameContains(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.Contains(s.C(FieldName), v)) + }) +} + +// NameHasPrefix applies the HasPrefix predicate on the "Name" field. +func NameHasPrefix(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.HasPrefix(s.C(FieldName), v)) + }) +} + +// NameHasSuffix applies the HasSuffix predicate on the "Name" field. +func NameHasSuffix(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.HasSuffix(s.C(FieldName), v)) + }) +} + +// NameEqualFold applies the EqualFold predicate on the "Name" field. +func NameEqualFold(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EqualFold(s.C(FieldName), v)) + }) +} + +// NameContainsFold applies the ContainsFold predicate on the "Name" field. +func NameContainsFold(v string) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.ContainsFold(s.C(FieldName), v)) + }) +} + +// MailboxIDEQ applies the EQ predicate on the "MailboxID" field. +func MailboxIDEQ(v imap.InternalMailboxID) predicate.Subscription { + vc := uint64(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldMailboxID), vc)) + }) +} + +// MailboxIDNEQ applies the NEQ predicate on the "MailboxID" field. +func MailboxIDNEQ(v imap.InternalMailboxID) predicate.Subscription { + vc := uint64(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldMailboxID), vc)) + }) +} + +// MailboxIDIn applies the In predicate on the "MailboxID" field. +func MailboxIDIn(vs ...imap.InternalMailboxID) predicate.Subscription { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = uint64(vs[i]) + } + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.In(s.C(FieldMailboxID), v...)) + }) +} + +// MailboxIDNotIn applies the NotIn predicate on the "MailboxID" field. +func MailboxIDNotIn(vs ...imap.InternalMailboxID) predicate.Subscription { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = uint64(vs[i]) + } + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NotIn(s.C(FieldMailboxID), v...)) + }) +} + +// MailboxIDGT applies the GT predicate on the "MailboxID" field. +func MailboxIDGT(v imap.InternalMailboxID) predicate.Subscription { + vc := uint64(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldMailboxID), vc)) + }) +} + +// MailboxIDGTE applies the GTE predicate on the "MailboxID" field. +func MailboxIDGTE(v imap.InternalMailboxID) predicate.Subscription { + vc := uint64(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldMailboxID), vc)) + }) +} + +// MailboxIDLT applies the LT predicate on the "MailboxID" field. +func MailboxIDLT(v imap.InternalMailboxID) predicate.Subscription { + vc := uint64(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldMailboxID), vc)) + }) +} + +// MailboxIDLTE applies the LTE predicate on the "MailboxID" field. +func MailboxIDLTE(v imap.InternalMailboxID) predicate.Subscription { + vc := uint64(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldMailboxID), vc)) + }) +} + +// RemoteIDEQ applies the EQ predicate on the "RemoteID" field. +func RemoteIDEQ(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDNEQ applies the NEQ predicate on the "RemoteID" field. +func RemoteIDNEQ(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDIn applies the In predicate on the "RemoteID" field. +func RemoteIDIn(vs ...imap.MailboxID) predicate.Subscription { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = string(vs[i]) + } + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.In(s.C(FieldRemoteID), v...)) + }) +} + +// RemoteIDNotIn applies the NotIn predicate on the "RemoteID" field. +func RemoteIDNotIn(vs ...imap.MailboxID) predicate.Subscription { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = string(vs[i]) + } + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NotIn(s.C(FieldRemoteID), v...)) + }) +} + +// RemoteIDGT applies the GT predicate on the "RemoteID" field. +func RemoteIDGT(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDGTE applies the GTE predicate on the "RemoteID" field. +func RemoteIDGTE(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDLT applies the LT predicate on the "RemoteID" field. +func RemoteIDLT(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDLTE applies the LTE predicate on the "RemoteID" field. +func RemoteIDLTE(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDContains applies the Contains predicate on the "RemoteID" field. +func RemoteIDContains(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.Contains(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDHasPrefix applies the HasPrefix predicate on the "RemoteID" field. +func RemoteIDHasPrefix(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.HasPrefix(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDHasSuffix applies the HasSuffix predicate on the "RemoteID" field. +func RemoteIDHasSuffix(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.HasSuffix(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDIsNil applies the IsNil predicate on the "RemoteID" field. +func RemoteIDIsNil() predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.IsNull(s.C(FieldRemoteID))) + }) +} + +// RemoteIDNotNil applies the NotNil predicate on the "RemoteID" field. +func RemoteIDNotNil() predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.NotNull(s.C(FieldRemoteID))) + }) +} + +// RemoteIDEqualFold applies the EqualFold predicate on the "RemoteID" field. +func RemoteIDEqualFold(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.EqualFold(s.C(FieldRemoteID), vc)) + }) +} + +// RemoteIDContainsFold applies the ContainsFold predicate on the "RemoteID" field. +func RemoteIDContainsFold(v imap.MailboxID) predicate.Subscription { + vc := string(v) + return predicate.Subscription(func(s *sql.Selector) { + s.Where(sql.ContainsFold(s.C(FieldRemoteID), vc)) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Subscription) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Subscription) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Subscription) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/internal/db/ent/subscription_create.go b/internal/db/ent/subscription_create.go new file mode 100644 index 00000000..bfed9387 --- /dev/null +++ b/internal/db/ent/subscription_create.go @@ -0,0 +1,266 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/ProtonMail/gluon/imap" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" +) + +// SubscriptionCreate is the builder for creating a Subscription entity. +type SubscriptionCreate struct { + config + mutation *SubscriptionMutation + hooks []Hook +} + +// SetName sets the "Name" field. +func (sc *SubscriptionCreate) SetName(s string) *SubscriptionCreate { + sc.mutation.SetName(s) + return sc +} + +// SetMailboxID sets the "MailboxID" field. +func (sc *SubscriptionCreate) SetMailboxID(imi imap.InternalMailboxID) *SubscriptionCreate { + sc.mutation.SetMailboxID(imi) + return sc +} + +// SetRemoteID sets the "RemoteID" field. +func (sc *SubscriptionCreate) SetRemoteID(ii imap.MailboxID) *SubscriptionCreate { + sc.mutation.SetRemoteID(ii) + return sc +} + +// SetNillableRemoteID sets the "RemoteID" field if the given value is not nil. +func (sc *SubscriptionCreate) SetNillableRemoteID(ii *imap.MailboxID) *SubscriptionCreate { + if ii != nil { + sc.SetRemoteID(*ii) + } + return sc +} + +// Mutation returns the SubscriptionMutation object of the builder. +func (sc *SubscriptionCreate) Mutation() *SubscriptionMutation { + return sc.mutation +} + +// Save creates the Subscription in the database. +func (sc *SubscriptionCreate) Save(ctx context.Context) (*Subscription, error) { + var ( + err error + node *Subscription + ) + if len(sc.hooks) == 0 { + if err = sc.check(); err != nil { + return nil, err + } + node, err = sc.sqlSave(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SubscriptionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err = sc.check(); err != nil { + return nil, err + } + sc.mutation = mutation + if node, err = sc.sqlSave(ctx); err != nil { + return nil, err + } + mutation.id = &node.ID + mutation.done = true + return node, err + }) + for i := len(sc.hooks) - 1; i >= 0; i-- { + if sc.hooks[i] == nil { + return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = sc.hooks[i](mut) + } + v, err := mut.Mutate(ctx, sc.mutation) + if err != nil { + return nil, err + } + nv, ok := v.(*Subscription) + if !ok { + return nil, fmt.Errorf("unexpected node type %T returned from SubscriptionMutation", v) + } + node = nv + } + return node, err +} + +// SaveX calls Save and panics if Save returns an error. +func (sc *SubscriptionCreate) SaveX(ctx context.Context) *Subscription { + v, err := sc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (sc *SubscriptionCreate) Exec(ctx context.Context) error { + _, err := sc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (sc *SubscriptionCreate) ExecX(ctx context.Context) { + if err := sc.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (sc *SubscriptionCreate) check() error { + if _, ok := sc.mutation.Name(); !ok { + return &ValidationError{Name: "Name", err: errors.New(`ent: missing required field "Subscription.Name"`)} + } + if _, ok := sc.mutation.MailboxID(); !ok { + return &ValidationError{Name: "MailboxID", err: errors.New(`ent: missing required field "Subscription.MailboxID"`)} + } + return nil +} + +func (sc *SubscriptionCreate) sqlSave(ctx context.Context) (*Subscription, error) { + _node, _spec := sc.createSpec() + if err := sqlgraph.CreateNode(ctx, sc.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) + return _node, nil +} + +func (sc *SubscriptionCreate) createSpec() (*Subscription, *sqlgraph.CreateSpec) { + var ( + _node = &Subscription{config: sc.config} + _spec = &sqlgraph.CreateSpec{ + Table: subscription.Table, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: subscription.FieldID, + }, + } + ) + if value, ok := sc.mutation.Name(); ok { + _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: subscription.FieldName, + }) + _node.Name = value + } + if value, ok := sc.mutation.MailboxID(); ok { + _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ + Type: field.TypeUint64, + Value: value, + Column: subscription.FieldMailboxID, + }) + _node.MailboxID = value + } + if value, ok := sc.mutation.RemoteID(); ok { + _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: subscription.FieldRemoteID, + }) + _node.RemoteID = value + } + return _node, _spec +} + +// SubscriptionCreateBulk is the builder for creating many Subscription entities in bulk. +type SubscriptionCreateBulk struct { + config + builders []*SubscriptionCreate +} + +// Save creates the Subscription entities in the database. +func (scb *SubscriptionCreateBulk) Save(ctx context.Context) ([]*Subscription, error) { + specs := make([]*sqlgraph.CreateSpec, len(scb.builders)) + nodes := make([]*Subscription, len(scb.builders)) + mutators := make([]Mutator, len(scb.builders)) + for i := range scb.builders { + func(i int, root context.Context) { + builder := scb.builders[i] + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SubscriptionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + nodes[i], specs[i] = builder.createSpec() + var err error + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, scb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, scb.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, scb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (scb *SubscriptionCreateBulk) SaveX(ctx context.Context) []*Subscription { + v, err := scb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (scb *SubscriptionCreateBulk) Exec(ctx context.Context) error { + _, err := scb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (scb *SubscriptionCreateBulk) ExecX(ctx context.Context) { + if err := scb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/internal/db/ent/subscription_delete.go b/internal/db/ent/subscription_delete.go new file mode 100644 index 00000000..404206b3 --- /dev/null +++ b/internal/db/ent/subscription_delete.go @@ -0,0 +1,115 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/ProtonMail/gluon/internal/db/ent/predicate" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" +) + +// SubscriptionDelete is the builder for deleting a Subscription entity. +type SubscriptionDelete struct { + config + hooks []Hook + mutation *SubscriptionMutation +} + +// Where appends a list predicates to the SubscriptionDelete builder. +func (sd *SubscriptionDelete) Where(ps ...predicate.Subscription) *SubscriptionDelete { + sd.mutation.Where(ps...) + return sd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (sd *SubscriptionDelete) Exec(ctx context.Context) (int, error) { + var ( + err error + affected int + ) + if len(sd.hooks) == 0 { + affected, err = sd.sqlExec(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SubscriptionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + sd.mutation = mutation + affected, err = sd.sqlExec(ctx) + mutation.done = true + return affected, err + }) + for i := len(sd.hooks) - 1; i >= 0; i-- { + if sd.hooks[i] == nil { + return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = sd.hooks[i](mut) + } + if _, err := mut.Mutate(ctx, sd.mutation); err != nil { + return 0, err + } + } + return affected, err +} + +// ExecX is like Exec, but panics if an error occurs. +func (sd *SubscriptionDelete) ExecX(ctx context.Context) int { + n, err := sd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (sd *SubscriptionDelete) sqlExec(ctx context.Context) (int, error) { + _spec := &sqlgraph.DeleteSpec{ + Node: &sqlgraph.NodeSpec{ + Table: subscription.Table, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: subscription.FieldID, + }, + }, + } + if ps := sd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, sd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return affected, err +} + +// SubscriptionDeleteOne is the builder for deleting a single Subscription entity. +type SubscriptionDeleteOne struct { + sd *SubscriptionDelete +} + +// Exec executes the deletion query. +func (sdo *SubscriptionDeleteOne) Exec(ctx context.Context) error { + n, err := sdo.sd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{subscription.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (sdo *SubscriptionDeleteOne) ExecX(ctx context.Context) { + sdo.sd.ExecX(ctx) +} diff --git a/internal/db/ent/subscription_query.go b/internal/db/ent/subscription_query.go new file mode 100644 index 00000000..30f522ba --- /dev/null +++ b/internal/db/ent/subscription_query.go @@ -0,0 +1,526 @@ +// 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/ProtonMail/gluon/internal/db/ent/predicate" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" +) + +// SubscriptionQuery is the builder for querying Subscription entities. +type SubscriptionQuery struct { + config + limit *int + offset *int + unique *bool + order []OrderFunc + fields []string + predicates []predicate.Subscription + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the SubscriptionQuery builder. +func (sq *SubscriptionQuery) Where(ps ...predicate.Subscription) *SubscriptionQuery { + sq.predicates = append(sq.predicates, ps...) + return sq +} + +// Limit adds a limit step to the query. +func (sq *SubscriptionQuery) Limit(limit int) *SubscriptionQuery { + sq.limit = &limit + return sq +} + +// Offset adds an offset step to the query. +func (sq *SubscriptionQuery) Offset(offset int) *SubscriptionQuery { + sq.offset = &offset + return sq +} + +// 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 (sq *SubscriptionQuery) Unique(unique bool) *SubscriptionQuery { + sq.unique = &unique + return sq +} + +// Order adds an order step to the query. +func (sq *SubscriptionQuery) Order(o ...OrderFunc) *SubscriptionQuery { + sq.order = append(sq.order, o...) + return sq +} + +// First returns the first Subscription entity from the query. +// Returns a *NotFoundError when no Subscription was found. +func (sq *SubscriptionQuery) First(ctx context.Context) (*Subscription, error) { + nodes, err := sq.Limit(1).All(ctx) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{subscription.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (sq *SubscriptionQuery) FirstX(ctx context.Context) *Subscription { + node, err := sq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Subscription ID from the query. +// Returns a *NotFoundError when no Subscription ID was found. +func (sq *SubscriptionQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = sq.Limit(1).IDs(ctx); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{subscription.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (sq *SubscriptionQuery) FirstIDX(ctx context.Context) int { + id, err := sq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Subscription entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Subscription entity is found. +// Returns a *NotFoundError when no Subscription entities are found. +func (sq *SubscriptionQuery) Only(ctx context.Context) (*Subscription, error) { + nodes, err := sq.Limit(2).All(ctx) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{subscription.Label} + default: + return nil, &NotSingularError{subscription.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (sq *SubscriptionQuery) OnlyX(ctx context.Context) *Subscription { + node, err := sq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Subscription ID in the query. +// Returns a *NotSingularError when more than one Subscription ID is found. +// Returns a *NotFoundError when no entities are found. +func (sq *SubscriptionQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = sq.Limit(2).IDs(ctx); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{subscription.Label} + default: + err = &NotSingularError{subscription.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (sq *SubscriptionQuery) OnlyIDX(ctx context.Context) int { + id, err := sq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Subscriptions. +func (sq *SubscriptionQuery) All(ctx context.Context) ([]*Subscription, error) { + if err := sq.prepareQuery(ctx); err != nil { + return nil, err + } + return sq.sqlAll(ctx) +} + +// AllX is like All, but panics if an error occurs. +func (sq *SubscriptionQuery) AllX(ctx context.Context) []*Subscription { + nodes, err := sq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Subscription IDs. +func (sq *SubscriptionQuery) IDs(ctx context.Context) ([]int, error) { + var ids []int + if err := sq.Select(subscription.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (sq *SubscriptionQuery) IDsX(ctx context.Context) []int { + ids, err := sq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (sq *SubscriptionQuery) Count(ctx context.Context) (int, error) { + if err := sq.prepareQuery(ctx); err != nil { + return 0, err + } + return sq.sqlCount(ctx) +} + +// CountX is like Count, but panics if an error occurs. +func (sq *SubscriptionQuery) CountX(ctx context.Context) int { + count, err := sq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (sq *SubscriptionQuery) Exist(ctx context.Context) (bool, error) { + if err := sq.prepareQuery(ctx); err != nil { + return false, err + } + return sq.sqlExist(ctx) +} + +// ExistX is like Exist, but panics if an error occurs. +func (sq *SubscriptionQuery) ExistX(ctx context.Context) bool { + exist, err := sq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the SubscriptionQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (sq *SubscriptionQuery) Clone() *SubscriptionQuery { + if sq == nil { + return nil + } + return &SubscriptionQuery{ + config: sq.config, + limit: sq.limit, + offset: sq.offset, + order: append([]OrderFunc{}, sq.order...), + predicates: append([]predicate.Subscription{}, sq.predicates...), + // clone intermediate query. + sql: sq.sql.Clone(), + path: sq.path, + unique: sq.unique, + } +} + +// 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 { +// Name string `json:"Name,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Subscription.Query(). +// GroupBy(subscription.FieldName). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (sq *SubscriptionQuery) GroupBy(field string, fields ...string) *SubscriptionGroupBy { + grbuild := &SubscriptionGroupBy{config: sq.config} + grbuild.fields = append([]string{field}, fields...) + grbuild.path = func(ctx context.Context) (prev *sql.Selector, err error) { + if err := sq.prepareQuery(ctx); err != nil { + return nil, err + } + return sq.sqlQuery(ctx), nil + } + grbuild.label = subscription.Label + grbuild.flds, grbuild.scan = &grbuild.fields, 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 { +// Name string `json:"Name,omitempty"` +// } +// +// client.Subscription.Query(). +// Select(subscription.FieldName). +// Scan(ctx, &v) +func (sq *SubscriptionQuery) Select(fields ...string) *SubscriptionSelect { + sq.fields = append(sq.fields, fields...) + selbuild := &SubscriptionSelect{SubscriptionQuery: sq} + selbuild.label = subscription.Label + selbuild.flds, selbuild.scan = &sq.fields, selbuild.Scan + return selbuild +} + +func (sq *SubscriptionQuery) prepareQuery(ctx context.Context) error { + for _, f := range sq.fields { + if !subscription.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if sq.path != nil { + prev, err := sq.path(ctx) + if err != nil { + return err + } + sq.sql = prev + } + return nil +} + +func (sq *SubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Subscription, error) { + var ( + nodes = []*Subscription{} + _spec = sq.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]interface{}, error) { + return (*Subscription).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []interface{}) error { + node := &Subscription{config: sq.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, sq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (sq *SubscriptionQuery) sqlCount(ctx context.Context) (int, error) { + _spec := sq.querySpec() + _spec.Node.Columns = sq.fields + if len(sq.fields) > 0 { + _spec.Unique = sq.unique != nil && *sq.unique + } + return sqlgraph.CountNodes(ctx, sq.driver, _spec) +} + +func (sq *SubscriptionQuery) sqlExist(ctx context.Context) (bool, error) { + n, err := sq.sqlCount(ctx) + if err != nil { + return false, fmt.Errorf("ent: check existence: %w", err) + } + return n > 0, nil +} + +func (sq *SubscriptionQuery) querySpec() *sqlgraph.QuerySpec { + _spec := &sqlgraph.QuerySpec{ + Node: &sqlgraph.NodeSpec{ + Table: subscription.Table, + Columns: subscription.Columns, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: subscription.FieldID, + }, + }, + From: sq.sql, + Unique: true, + } + if unique := sq.unique; unique != nil { + _spec.Unique = *unique + } + if fields := sq.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, subscription.FieldID) + for i := range fields { + if fields[i] != subscription.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := sq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := sq.limit; limit != nil { + _spec.Limit = *limit + } + if offset := sq.offset; offset != nil { + _spec.Offset = *offset + } + if ps := sq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (sq *SubscriptionQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(sq.driver.Dialect()) + t1 := builder.Table(subscription.Table) + columns := sq.fields + if len(columns) == 0 { + columns = subscription.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if sq.sql != nil { + selector = sq.sql + selector.Select(selector.Columns(columns...)...) + } + if sq.unique != nil && *sq.unique { + selector.Distinct() + } + for _, p := range sq.predicates { + p(selector) + } + for _, p := range sq.order { + p(selector) + } + if offset := sq.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 := sq.limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// SubscriptionGroupBy is the group-by builder for Subscription entities. +type SubscriptionGroupBy struct { + config + selector + fields []string + fns []AggregateFunc + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (sgb *SubscriptionGroupBy) Aggregate(fns ...AggregateFunc) *SubscriptionGroupBy { + sgb.fns = append(sgb.fns, fns...) + return sgb +} + +// Scan applies the group-by query and scans the result into the given value. +func (sgb *SubscriptionGroupBy) Scan(ctx context.Context, v interface{}) error { + query, err := sgb.path(ctx) + if err != nil { + return err + } + sgb.sql = query + return sgb.sqlScan(ctx, v) +} + +func (sgb *SubscriptionGroupBy) sqlScan(ctx context.Context, v interface{}) error { + for _, f := range sgb.fields { + if !subscription.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)} + } + } + selector := sgb.sqlQuery() + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := sgb.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +func (sgb *SubscriptionGroupBy) sqlQuery() *sql.Selector { + selector := sgb.sql.Select() + aggregation := make([]string, 0, len(sgb.fns)) + for _, fn := range sgb.fns { + aggregation = append(aggregation, fn(selector)) + } + // If no columns were selected in a custom aggregation function, the default + // selection is the fields used for "group-by", and the aggregation functions. + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(sgb.fields)+len(sgb.fns)) + for _, f := range sgb.fields { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + return selector.GroupBy(selector.Columns(sgb.fields...)...) +} + +// SubscriptionSelect is the builder for selecting fields of Subscription entities. +type SubscriptionSelect struct { + *SubscriptionQuery + selector + // intermediate query (i.e. traversal path). + sql *sql.Selector +} + +// Scan applies the selector query and scans the result into the given value. +func (ss *SubscriptionSelect) Scan(ctx context.Context, v interface{}) error { + if err := ss.prepareQuery(ctx); err != nil { + return err + } + ss.sql = ss.SubscriptionQuery.sqlQuery(ctx) + return ss.sqlScan(ctx, v) +} + +func (ss *SubscriptionSelect) sqlScan(ctx context.Context, v interface{}) error { + rows := &sql.Rows{} + query, args := ss.sql.Query() + if err := ss.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/internal/db/ent/subscription_update.go b/internal/db/ent/subscription_update.go new file mode 100644 index 00000000..79e835f2 --- /dev/null +++ b/internal/db/ent/subscription_update.go @@ -0,0 +1,392 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/ProtonMail/gluon/imap" + "github.com/ProtonMail/gluon/internal/db/ent/predicate" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" +) + +// SubscriptionUpdate is the builder for updating Subscription entities. +type SubscriptionUpdate struct { + config + hooks []Hook + mutation *SubscriptionMutation +} + +// Where appends a list predicates to the SubscriptionUpdate builder. +func (su *SubscriptionUpdate) Where(ps ...predicate.Subscription) *SubscriptionUpdate { + su.mutation.Where(ps...) + return su +} + +// SetName sets the "Name" field. +func (su *SubscriptionUpdate) SetName(s string) *SubscriptionUpdate { + su.mutation.SetName(s) + return su +} + +// SetMailboxID sets the "MailboxID" field. +func (su *SubscriptionUpdate) SetMailboxID(imi imap.InternalMailboxID) *SubscriptionUpdate { + su.mutation.ResetMailboxID() + su.mutation.SetMailboxID(imi) + return su +} + +// AddMailboxID adds imi to the "MailboxID" field. +func (su *SubscriptionUpdate) AddMailboxID(imi imap.InternalMailboxID) *SubscriptionUpdate { + su.mutation.AddMailboxID(imi) + return su +} + +// SetRemoteID sets the "RemoteID" field. +func (su *SubscriptionUpdate) SetRemoteID(ii imap.MailboxID) *SubscriptionUpdate { + su.mutation.SetRemoteID(ii) + return su +} + +// SetNillableRemoteID sets the "RemoteID" field if the given value is not nil. +func (su *SubscriptionUpdate) SetNillableRemoteID(ii *imap.MailboxID) *SubscriptionUpdate { + if ii != nil { + su.SetRemoteID(*ii) + } + return su +} + +// ClearRemoteID clears the value of the "RemoteID" field. +func (su *SubscriptionUpdate) ClearRemoteID() *SubscriptionUpdate { + su.mutation.ClearRemoteID() + return su +} + +// Mutation returns the SubscriptionMutation object of the builder. +func (su *SubscriptionUpdate) Mutation() *SubscriptionMutation { + return su.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (su *SubscriptionUpdate) Save(ctx context.Context) (int, error) { + var ( + err error + affected int + ) + if len(su.hooks) == 0 { + affected, err = su.sqlSave(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SubscriptionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + su.mutation = mutation + affected, err = su.sqlSave(ctx) + mutation.done = true + return affected, err + }) + for i := len(su.hooks) - 1; i >= 0; i-- { + if su.hooks[i] == nil { + return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = su.hooks[i](mut) + } + if _, err := mut.Mutate(ctx, su.mutation); err != nil { + return 0, err + } + } + return affected, err +} + +// SaveX is like Save, but panics if an error occurs. +func (su *SubscriptionUpdate) SaveX(ctx context.Context) int { + affected, err := su.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (su *SubscriptionUpdate) Exec(ctx context.Context) error { + _, err := su.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (su *SubscriptionUpdate) ExecX(ctx context.Context) { + if err := su.Exec(ctx); err != nil { + panic(err) + } +} + +func (su *SubscriptionUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := &sqlgraph.UpdateSpec{ + Node: &sqlgraph.NodeSpec{ + Table: subscription.Table, + Columns: subscription.Columns, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: subscription.FieldID, + }, + }, + } + if ps := su.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := su.mutation.Name(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: subscription.FieldName, + }) + } + if value, ok := su.mutation.MailboxID(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeUint64, + Value: value, + Column: subscription.FieldMailboxID, + }) + } + if value, ok := su.mutation.AddedMailboxID(); ok { + _spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{ + Type: field.TypeUint64, + Value: value, + Column: subscription.FieldMailboxID, + }) + } + if value, ok := su.mutation.RemoteID(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: subscription.FieldRemoteID, + }) + } + if su.mutation.RemoteIDCleared() { + _spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Column: subscription.FieldRemoteID, + }) + } + if n, err = sqlgraph.UpdateNodes(ctx, su.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{subscription.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + return n, nil +} + +// SubscriptionUpdateOne is the builder for updating a single Subscription entity. +type SubscriptionUpdateOne struct { + config + fields []string + hooks []Hook + mutation *SubscriptionMutation +} + +// SetName sets the "Name" field. +func (suo *SubscriptionUpdateOne) SetName(s string) *SubscriptionUpdateOne { + suo.mutation.SetName(s) + return suo +} + +// SetMailboxID sets the "MailboxID" field. +func (suo *SubscriptionUpdateOne) SetMailboxID(imi imap.InternalMailboxID) *SubscriptionUpdateOne { + suo.mutation.ResetMailboxID() + suo.mutation.SetMailboxID(imi) + return suo +} + +// AddMailboxID adds imi to the "MailboxID" field. +func (suo *SubscriptionUpdateOne) AddMailboxID(imi imap.InternalMailboxID) *SubscriptionUpdateOne { + suo.mutation.AddMailboxID(imi) + return suo +} + +// SetRemoteID sets the "RemoteID" field. +func (suo *SubscriptionUpdateOne) SetRemoteID(ii imap.MailboxID) *SubscriptionUpdateOne { + suo.mutation.SetRemoteID(ii) + return suo +} + +// SetNillableRemoteID sets the "RemoteID" field if the given value is not nil. +func (suo *SubscriptionUpdateOne) SetNillableRemoteID(ii *imap.MailboxID) *SubscriptionUpdateOne { + if ii != nil { + suo.SetRemoteID(*ii) + } + return suo +} + +// ClearRemoteID clears the value of the "RemoteID" field. +func (suo *SubscriptionUpdateOne) ClearRemoteID() *SubscriptionUpdateOne { + suo.mutation.ClearRemoteID() + return suo +} + +// Mutation returns the SubscriptionMutation object of the builder. +func (suo *SubscriptionUpdateOne) Mutation() *SubscriptionMutation { + return suo.mutation +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (suo *SubscriptionUpdateOne) Select(field string, fields ...string) *SubscriptionUpdateOne { + suo.fields = append([]string{field}, fields...) + return suo +} + +// Save executes the query and returns the updated Subscription entity. +func (suo *SubscriptionUpdateOne) Save(ctx context.Context) (*Subscription, error) { + var ( + err error + node *Subscription + ) + if len(suo.hooks) == 0 { + node, err = suo.sqlSave(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SubscriptionMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + suo.mutation = mutation + node, err = suo.sqlSave(ctx) + mutation.done = true + return node, err + }) + for i := len(suo.hooks) - 1; i >= 0; i-- { + if suo.hooks[i] == nil { + return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = suo.hooks[i](mut) + } + v, err := mut.Mutate(ctx, suo.mutation) + if err != nil { + return nil, err + } + nv, ok := v.(*Subscription) + if !ok { + return nil, fmt.Errorf("unexpected node type %T returned from SubscriptionMutation", v) + } + node = nv + } + return node, err +} + +// SaveX is like Save, but panics if an error occurs. +func (suo *SubscriptionUpdateOne) SaveX(ctx context.Context) *Subscription { + node, err := suo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (suo *SubscriptionUpdateOne) Exec(ctx context.Context) error { + _, err := suo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (suo *SubscriptionUpdateOne) ExecX(ctx context.Context) { + if err := suo.Exec(ctx); err != nil { + panic(err) + } +} + +func (suo *SubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *Subscription, err error) { + _spec := &sqlgraph.UpdateSpec{ + Node: &sqlgraph.NodeSpec{ + Table: subscription.Table, + Columns: subscription.Columns, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: subscription.FieldID, + }, + }, + } + id, ok := suo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Subscription.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := suo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, subscription.FieldID) + for _, f := range fields { + if !subscription.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != subscription.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := suo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := suo.mutation.Name(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: subscription.FieldName, + }) + } + if value, ok := suo.mutation.MailboxID(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeUint64, + Value: value, + Column: subscription.FieldMailboxID, + }) + } + if value, ok := suo.mutation.AddedMailboxID(); ok { + _spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{ + Type: field.TypeUint64, + Value: value, + Column: subscription.FieldMailboxID, + }) + } + if value, ok := suo.mutation.RemoteID(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: subscription.FieldRemoteID, + }) + } + if suo.mutation.RemoteIDCleared() { + _spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Column: subscription.FieldRemoteID, + }) + } + _node = &Subscription{config: suo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, suo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{subscription.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + return _node, nil +} diff --git a/internal/db/ent/tx.go b/internal/db/ent/tx.go index a9a1e170..81fe468f 100644 --- a/internal/db/ent/tx.go +++ b/internal/db/ent/tx.go @@ -24,6 +24,8 @@ type Tx struct { Message *MessageClient // MessageFlag is the client for interacting with the MessageFlag builders. MessageFlag *MessageFlagClient + // Subscription is the client for interacting with the Subscription builders. + Subscription *SubscriptionClient // UID is the client for interacting with the UID builders. UID *UIDClient @@ -167,6 +169,7 @@ func (tx *Tx) init() { tx.MailboxPermFlag = NewMailboxPermFlagClient(tx.config) tx.Message = NewMessageClient(tx.config) tx.MessageFlag = NewMessageFlagClient(tx.config) + tx.Subscription = NewSubscriptionClient(tx.config) tx.UID = NewUIDClient(tx.config) } diff --git a/internal/db/mailbox.go b/internal/db/mailbox.go index 1a74acec..cee8e4b5 100644 --- a/internal/db/mailbox.go +++ b/internal/db/mailbox.go @@ -3,6 +3,7 @@ package db import ( "context" "fmt" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" "strings" "entgo.io/ent/dialect/sql" @@ -59,7 +60,18 @@ func CreateMailbox( create = create.SetRemoteID(mboxID) } - return create.Save(ctx) + mbox, err := create.Save(ctx) + if err != nil { + return nil, err + } + + if mboxID != ids.GluonInternalRecoveryMailboxRemoteID { + if err := AddSubscription(ctx, tx, mbox.Name, ids.NewMailboxIDPair(mbox)); err != nil { + return nil, err + } + } + + return mbox, nil } func MailboxExistsWithID(ctx context.Context, client *ent.Client, mboxID imap.InternalMailboxID) (bool, error) { @@ -91,6 +103,10 @@ func RenameMailboxWithRemoteID(ctx context.Context, tx *ent.Tx, mboxID imap.Mail return err } + if _, err := tx.Subscription.Update().Where(subscription.RemoteID(mboxID)).SetName(name).Save(ctx); err != nil { + return err + } + return nil } diff --git a/internal/db/subscription.go b/internal/db/subscription.go new file mode 100644 index 00000000..8afce83d --- /dev/null +++ b/internal/db/subscription.go @@ -0,0 +1,67 @@ +package db + +import ( + "context" + "github.com/ProtonMail/gluon/imap" + "github.com/ProtonMail/gluon/internal/db/ent" + "github.com/ProtonMail/gluon/internal/db/ent/subscription" + "github.com/ProtonMail/gluon/internal/ids" +) + +func AddSubscription(ctx context.Context, tx *ent.Tx, mboxName string, mboxID ids.MailboxIDPair) error { + count, err := tx.Subscription.Update().Where(subscription.NameEqualFold(mboxName)).SetMailboxID(mboxID.InternalID).SetRemoteID(mboxID.RemoteID).Save(ctx) + if err != nil { + return err + } + + if count == 0 { + if _, err := tx.Subscription.Create().SetMailboxID(mboxID.InternalID).SetRemoteID(mboxID.RemoteID).SetName(mboxName).Save(ctx); err != nil { + return err + } + } + + return nil +} + +func DeleteSubscriptionWithName(ctx context.Context, tx *ent.Tx, mboxName string) (int, error) { + return tx.Subscription.Delete().Where(subscription.NameEqualFold(mboxName)).Exec(ctx) +} + +func DeleteSubscriptionWithMboxID(ctx context.Context, tx *ent.Tx, mboxID imap.InternalMailboxID) (int, error) { + return tx.Subscription.Delete().Where(subscription.MailboxID(mboxID)).Exec(ctx) +} + +func GetSubscription(ctx context.Context, client *ent.Client, mboxName string) (*ent.Subscription, error) { + return client.Subscription.Query().Where(subscription.NameEqualFold(mboxName)).Only(ctx) +} + +func GetSubscriptionWithMBoxID(ctx context.Context, client *ent.Client, mboxID imap.InternalMailboxID) (*ent.Subscription, error) { + return client.Subscription.Query().Where(subscription.MailboxID(mboxID)).Only(ctx) +} + +func GetSubscriptionSet(ctx context.Context, client *ent.Client) (map[imap.InternalMailboxID]*ent.Subscription, error) { + const QueryLimit = 16000 + + subscriptions := make(map[imap.InternalMailboxID]*ent.Subscription) + + for i := 0; ; i += QueryLimit { + result, err := client.Subscription.Query(). + Limit(QueryLimit). + Offset(i). + All(ctx) + if err != nil { + return nil, err + } + + resultLen := len(result) + if resultLen == 0 { + break + } + + for _, v := range result { + subscriptions[v.MailboxID] = v + } + } + + return subscriptions, nil +} diff --git a/internal/state/mailbox.go b/internal/state/mailbox.go index fed7c5d4..03214553 100644 --- a/internal/state/mailbox.go +++ b/internal/state/mailbox.go @@ -42,7 +42,6 @@ func newMailbox(mbox *ent.Mailbox, state *State, snap *snapshot) *Mailbox { return &Mailbox{ id: ids.NewMailboxIDPair(mbox), name: mbox.Name, - subscribed: mbox.Subscribed, uidValidity: mbox.UIDValidity, uidNext: mbox.UIDNext, diff --git a/internal/state/match.go b/internal/state/match.go index 76c3a20f..81848efe 100644 --- a/internal/state/match.go +++ b/internal/state/match.go @@ -18,16 +18,23 @@ type Match struct { Atts imap.FlagSet } +type matchMailbox struct { + Name string + Subscribed bool + // EntMBox should be set to nil if there is no such value. + EntMBox *ent.Mailbox +} + func getMatches( ctx context.Context, client *ent.Client, - allMailboxes []*ent.Mailbox, + allMailboxes []matchMailbox, ref, pattern, delimiter string, subscribed bool, ) (map[string]Match, error) { matches := make(map[string]Match) - mailboxes := make(map[string]*ent.Mailbox) + mailboxes := make(map[string]matchMailbox) for _, mbox := range allMailboxes { mailboxes[mbox.Name] = mbox } @@ -53,7 +60,7 @@ func getMatches( mbox, mailboxExists := mailboxes[matchedName] match, isMatch, err := prepareMatch( - ctx, client, matchedName, mbox, + ctx, client, matchedName, &mbox, pattern, delimiter, mboxName == matchedName, mailboxExists, subscribed, ) @@ -74,7 +81,7 @@ func prepareMatch( ctx context.Context, client *ent.Client, matchedName string, - mbox *ent.Mailbox, + mbox *matchMailbox, pattern, delimiter string, isNotSuperior, mailboxExists, onlySubscribed bool, ) (Match, bool, error) { @@ -95,22 +102,30 @@ func prepareMatch( }, true, nil } - atts := imap.NewFlagSetFromSlice(xslices.Map( - mbox.Edges.Attributes, - func(flag *ent.MailboxAttr) string { - return flag.Value - }, - )) - - recent, err := db.GetMailboxRecentCount(ctx, client, mbox) - if err != nil { - return Match{}, false, err - } + var ( + atts imap.FlagSet + ) + + if mbox.EntMBox != nil { + atts = imap.NewFlagSetFromSlice(xslices.Map( + mbox.EntMBox.Edges.Attributes, + func(flag *ent.MailboxAttr) string { + return flag.Value + }, + )) + + recent, err := db.GetMailboxRecentCount(ctx, client, mbox.EntMBox) + if err != nil { + return Match{}, false, err + } - if recent > 0 { - atts.AddToSelf(imap.AttrMarked) + if recent > 0 { + atts.AddToSelf(imap.AttrMarked) + } else { + atts.AddToSelf(imap.AttrUnmarked) + } } else { - atts.AddToSelf(imap.AttrUnmarked) + atts = imap.NewFlagSet(imap.AttrNoSelect) } return Match{ diff --git a/internal/state/state.go b/internal/state/state.go index 23657c82..34afd800 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -98,7 +98,47 @@ func (state *State) List(ctx context.Context, ref, pattern string, subscribed bo return state.user.GetRemote().IsMailboxVisible(ctx, mailbox.RemoteID) }) - matches, err := getMatches(ctx, client, mailboxes, ref, pattern, state.delimiter, subscribed) + var subscriptions map[imap.InternalMailboxID]*ent.Subscription + + if subscribed { + subscriptions, err = db.GetSubscriptionSet(ctx, client) + if err != nil { + return err + } + } + + // Convert existing mailboxes over to match format. + matchMailboxes := make([]matchMailbox, 0, len(mailboxes)) + for _, mbox := range mailboxes { + var subscribed bool + if _, ok := subscriptions[mbox.ID]; ok { + subscribed = true + delete(subscriptions, mbox.ID) + } + + matchMailboxes = append(matchMailboxes, matchMailbox{ + Name: mbox.Name, + Subscribed: subscribed, + EntMBox: mbox, + }) + } + + if subscribed { + // Insert any remaining mailboxes that have been deleted but are still subscribed. + for _, s := range subscriptions { + if !state.user.GetRemote().IsMailboxVisible(ctx, s.RemoteID) { + continue + } + + matchMailboxes = append(matchMailboxes, matchMailbox{ + Name: s.Name, + Subscribed: true, + EntMBox: nil, + }) + } + } + + matches, err := getMatches(ctx, client, matchMailboxes, ref, pattern, state.delimiter, subscribed) if err != nil { return err } @@ -346,31 +386,53 @@ func (state *State) Rename(ctx context.Context, oldName, newName string) error { func (state *State) Subscribe(ctx context.Context, name string) error { mbox, err := db.ReadResult(ctx, state.db(), func(ctx context.Context, client *ent.Client) (*ent.Mailbox, error) { - return db.GetMailboxByName(ctx, client, name) + mbox, err := db.GetMailboxByName(ctx, client, name) + if err != nil { + return nil, ErrNoSuchMailbox + } + + if _, err := db.GetSubscriptionWithMBoxID(ctx, client, mbox.ID); err != nil { + if ent.IsNotFound(err) { + return mbox, nil + } + + return nil, ErrNoSuchMailbox + } + + return nil, ErrAlreadySubscribed }) if err != nil { - return ErrNoSuchMailbox - } else if mbox.Subscribed { - return ErrAlreadySubscribed + return err } return state.db().Write(ctx, func(ctx context.Context, tx *ent.Tx) error { - return mbox.Update().SetSubscribed(true).Exec(ctx) + return db.AddSubscription(ctx, tx, mbox.Name, ids.NewMailboxIDPair(mbox)) }) } func (state *State) Unsubscribe(ctx context.Context, name string) error { - mbox, err := db.ReadResult(ctx, state.db(), func(ctx context.Context, client *ent.Client) (*ent.Mailbox, error) { - return db.GetMailboxByName(ctx, client, name) + mboxExists, err := db.ReadResult(ctx, state.db(), func(ctx context.Context, client *ent.Client) (bool, error) { + return db.MailboxExistsWithName(ctx, client, name) }) if err != nil { return ErrNoSuchMailbox - } else if !mbox.Subscribed { - return ErrAlreadyUnsubscribed } return state.db().Write(ctx, func(ctx context.Context, tx *ent.Tx) error { - return mbox.Update().SetSubscribed(false).Exec(ctx) + count, err := db.DeleteSubscriptionWithName(ctx, tx, name) + if err != nil { + return err + } + + if count == 0 { + if mboxExists { + return ErrAlreadyUnsubscribed + } else { + return ErrNoSuchMailbox + } + } + + return nil }) } diff --git a/tests/lsub_test.go b/tests/lsub_test.go index b0a508a5..47a8ea22 100644 --- a/tests/lsub_test.go +++ b/tests/lsub_test.go @@ -126,11 +126,7 @@ func TestLsubSubscribedNotExisting(t *testing.T) { c.C(`tag DELETE foo`).OK(`tag`) c.C(`A002 LSUB "" "foo"`) - // TODO GODT-1896: The server MUST NOT unilaterally remove an existing mailbox name - // from the subscription list even if a mailbox by that name no - // longer exists. - // - // c.S(`* LSUB (\Noselect) "." "foo"`) + c.S(`* LSUB (\Noselect) "." "foo"`) c.OK(`A002`) }) } diff --git a/tests/unsubscribe_test.go b/tests/unsubscribe_test.go index 90a3732d..73b4aa72 100644 --- a/tests/unsubscribe_test.go +++ b/tests/unsubscribe_test.go @@ -19,3 +19,35 @@ func TestUnsubscribe(t *testing.T) { c.S("A006 NO not subscribed to this mailbox") }) } + +func TestUnsubscribeAfterMailboxDeleted(t *testing.T) { + runOneToOneTestWithAuth(t, defaultServerOptions(t, withDelimiter(".")), func(c *testConnection, _ *testSession) { + c.C("A002 CREATE #news.comp.mail.mime") + c.S("A002 OK CREATE") + + c.C("A006 DELETE #news.comp.mail.mime") + c.S("A006 OK DELETE") + + c.C("A007 SUBSCRIBE #news.comp.mail.mime") + c.S("A007 NO no such mailbox") + + c.C("A008 UNSUBSCRIBE #news.comp.mail.mime") + c.S("A008 OK UNSUBSCRIBE") + }) +} + +func TestUnsubscribeAfterMailboxRenamedDeleted(t *testing.T) { + runOneToOneTestWithAuth(t, defaultServerOptions(t, withDelimiter(".")), func(c *testConnection, _ *testSession) { + c.C("A002 CREATE mailbox") + c.S("A002 OK CREATE") + + c.C("A002 RENAME mailbox mailbox2") + c.S("A002 OK RENAME") + + c.C("A006 DELETE mailbox2") + c.S("A006 OK DELETE") + + c.C("A008 UNSUBSCRIBE mailbox2") + c.S("A008 OK UNSUBSCRIBE") + }) +}