From a7ee8ea5953559961055036268a259d6787c2a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20D=C3=ADaz=20Marco?= Date: Sat, 28 Oct 2023 11:52:59 +0200 Subject: [PATCH] Add Stripe integration. --- .env.example | 4 + go.mod | 1 + go.sum | 6 + internal/api/resolvers/billing.go | 74 +++ internal/api/resolvers/person.go | 14 + internal/api/resolvers/resolver.go | 4 + .../resolvers/resolvers_test/billing_test.go | 126 ++++++ .../api/resolvers/resolvers_test/main_test.go | 9 + .../resolvers/resolvers_test/person_test.go | 7 + internal/api/schema/billing.graphqls | 15 + internal/api/schema/person.graphqls | 2 + internal/billing/billing_test/main.go | 36 ++ internal/billing/checkout.go | 34 ++ internal/billing/customer.go | 71 +++ internal/billing/main.go | 29 ++ internal/billing/portal.go | 24 + internal/config/main.go | 4 + internal/data/schema/person.go | 9 + internal/generated/api/exec.go | 425 +++++++++++++++++- internal/generated/api/model.go | 8 + internal/generated/container/container.go | 186 ++++++-- internal/generated/container/defs.go | 75 +++- internal/generated/data/entql.go | 12 + .../generated/data/factories/factories.go | 3 + internal/generated/data/gql_collection.go | 10 + internal/generated/data/gql_where_input.go | 72 +++ internal/generated/data/migrate/schema.go | 2 + internal/generated/data/mutation.go | 129 +++++- internal/generated/data/person.go | 29 +- internal/generated/data/person/person.go | 18 + internal/generated/data/person/where.go | 95 ++++ internal/generated/data/person_create.go | 43 ++ internal/generated/data/person_query.go | 8 +- internal/generated/data/person_update.go | 86 ++++ internal/generated/data/runtime/runtime.go | 26 +- internal/services/biller.go | 20 + internal/services/provider/main.go | 1 + internal/services/resolver.go | 3 + 38 files changed, 1650 insertions(+), 70 deletions(-) create mode 100644 internal/api/resolvers/billing.go create mode 100644 internal/api/resolvers/resolvers_test/billing_test.go create mode 100644 internal/api/schema/billing.graphqls create mode 100644 internal/billing/billing_test/main.go create mode 100644 internal/billing/checkout.go create mode 100644 internal/billing/customer.go create mode 100644 internal/billing/main.go create mode 100644 internal/billing/portal.go create mode 100755 internal/services/biller.go diff --git a/.env.example b/.env.example index a6ba125..4fd7d74 100755 --- a/.env.example +++ b/.env.example @@ -38,3 +38,7 @@ FRONT_PASSWORD_AUTHORIZATION_PATH=recover ORG_NAME='Associació Valenciana pel Transport Públic' ORG_LOGO=https://cdn.avptp.org/brand/imagotype.png + +STRIPE_API_SECRET= +STRIPE_ENDPOINT_SECRET= +STRIPE_PRICE_ID=price_1O69HOIe11Eg2JMUZNFN0xhc diff --git a/go.mod b/go.mod index 9246a29..0933558 100755 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/sarulabs/dingo/v4 v4.2.0 github.com/stoewer/go-strcase v1.3.0 github.com/stretchr/testify v1.8.4 + github.com/stripe/stripe-go/v76 v76.1.0 github.com/vektah/gqlparser/v2 v2.5.10 golang.org/x/net v0.17.0 golang.org/x/text v0.13.0 diff --git a/go.sum b/go.sum index f1586d1..672ff26 100644 --- a/go.sum +++ b/go.sum @@ -165,11 +165,14 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stripe/stripe-go/v76 v76.1.0 h1:TvpIuqJRBw2DmTecXz1DRq3KXChxy4YJ2iUu0r6c7uY= +github.com/stripe/stripe-go/v76 v76.1.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 h1:L0rPdfzq43+NV8rfIx2kA4iSSLRj2jN5ijYHoeXRwvQ= @@ -207,6 +210,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -223,6 +227,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190225065934-cc5685c2db12/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -239,6 +244,7 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= diff --git a/internal/api/resolvers/billing.go b/internal/api/resolvers/billing.go new file mode 100644 index 0000000..3587957 --- /dev/null +++ b/internal/api/resolvers/billing.go @@ -0,0 +1,74 @@ +package resolvers + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.39 + +import ( + "context" + + "github.com/avptp/brain/internal/api/reporting" + "github.com/avptp/brain/internal/generated/api" + "github.com/avptp/brain/internal/generated/data" + "github.com/avptp/brain/internal/transport/request" +) + +// CreateBillingCheckoutSession is the resolver for the createBillingCheckoutSession field. +func (r *mutationResolver) CreateBillingCheckoutSession(ctx context.Context) (*api.CreateBillingCheckoutSessionPayload, error) { + // Ensure that the mutation is authenticated + viewer := request.ViewerFromCtx(ctx) + + if viewer == nil { + return nil, reporting.ErrUnauthenticated + } + + // Ensure that biller is ready for this person + d := data.FromContext(ctx) // transactional data client for mutations + err := r.biller.PreparePerson(ctx, d, viewer) + + if err != nil { + return nil, err + } + + // Create checkout session + url, err := r.biller.CreateCheckoutSession(viewer) + + if err != nil { + return nil, err + } + + // Return payload + return &api.CreateBillingCheckoutSessionPayload{ + CheckoutSessionURL: url, + }, nil +} + +// CreateBillingPortalSession is the resolver for the createBillingPortalSession field. +func (r *mutationResolver) CreateBillingPortalSession(ctx context.Context) (*api.CreateBillingPortalSessionPayload, error) { + // Ensure that the mutation is authenticated + viewer := request.ViewerFromCtx(ctx) + + if viewer == nil { + return nil, reporting.ErrUnauthenticated + } + + // Ensure that biller is ready for this person + d := data.FromContext(ctx) // transactional data client for mutations + err := r.biller.PreparePerson(ctx, d, viewer) + + if err != nil { + return nil, err + } + + // Create portal session + url, err := r.biller.CreatePortalSession(viewer) + + if err != nil { + return nil, err + } + + // Return payload + return &api.CreateBillingPortalSessionPayload{ + PortalSessionURL: url, + }, nil +} diff --git a/internal/api/resolvers/person.go b/internal/api/resolvers/person.go index bcbf0fc..fb1c3f6 100755 --- a/internal/api/resolvers/person.go +++ b/internal/api/resolvers/person.go @@ -31,6 +31,7 @@ func (r *mutationResolver) CreatePerson(ctx context.Context, input api.CreatePer d := data.FromContext(ctx) // transactional data client for mutations allowCtx := privacy.DecisionContext(ctx, privacy.Allow) + // Create person create := d.Person. Create(). SetEmail(input.Email). @@ -49,6 +50,7 @@ func (r *mutationResolver) CreatePerson(ctx context.Context, input api.CreatePer return nil, err } + // Create authorization a, err := d.Authorization. Create(). SetPersonID(p.ID). @@ -59,6 +61,7 @@ func (r *mutationResolver) CreatePerson(ctx context.Context, input api.CreatePer return nil, err } + // Send welcome message err = r.messenger.Send(&templates.Welcome{ Link: fmt.Sprintf( "%s/%s/%s", @@ -73,6 +76,7 @@ func (r *mutationResolver) CreatePerson(ctx context.Context, input api.CreatePer return nil, err } + // Return payload return &api.CreatePersonPayload{ Person: p, }, nil @@ -81,6 +85,8 @@ func (r *mutationResolver) CreatePerson(ctx context.Context, input api.CreatePer // UpdatePerson is the resolver for the updatePerson field. func (r *mutationResolver) UpdatePerson(ctx context.Context, input api.UpdatePersonInput) (*api.UpdatePersonPayload, error) { d := data.FromContext(ctx) // transactional data client for mutations + + // Update person update := d.Person.UpdateOneID(input.ID) if input.Email.IsSet() { @@ -177,6 +183,14 @@ func (r *mutationResolver) UpdatePerson(ctx context.Context, input api.UpdatePer return nil, err } + // Sync with biller + err = r.biller.SyncPerson(person) + + if err != nil { + return nil, err + } + + // Return payload return &api.UpdatePersonPayload{ Person: person, }, nil diff --git a/internal/api/resolvers/resolver.go b/internal/api/resolvers/resolver.go index 70e781c..56a9b7b 100644 --- a/internal/api/resolvers/resolver.go +++ b/internal/api/resolvers/resolver.go @@ -2,6 +2,7 @@ package resolvers import ( "github.com/avptp/brain/internal/auth" + "github.com/avptp/brain/internal/billing" "github.com/avptp/brain/internal/config" "github.com/avptp/brain/internal/generated/data" "github.com/avptp/brain/internal/messaging" @@ -9,6 +10,7 @@ import ( ) type Resolver struct { + biller billing.Biller captcha auth.Captcha cfg *config.Config data *data.Client @@ -17,6 +19,7 @@ type Resolver struct { } func NewResolver( + biller billing.Biller, captcha auth.Captcha, cfg *config.Config, data *data.Client, @@ -24,6 +27,7 @@ func NewResolver( messenger messaging.Messenger, ) *Resolver { return &Resolver{ + biller, captcha, cfg, data, diff --git a/internal/api/resolvers/resolvers_test/billing_test.go b/internal/api/resolvers/resolvers_test/billing_test.go new file mode 100644 index 0000000..9fd9f85 --- /dev/null +++ b/internal/api/resolvers/resolvers_test/billing_test.go @@ -0,0 +1,126 @@ +package resolvers_test + +import ( + "github.com/avptp/brain/internal/api/reporting" + "github.com/avptp/brain/internal/generated/data" + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/mock" +) + +func (t *TestSuite) TestBilling() { + const createCheckoutSessionMutation = ` + mutation() { + createBillingCheckoutSession() { + checkoutSessionUrl + } + } + ` + + type createCheckoutSession struct { + CreateBillingCheckoutSession struct { + CheckoutSessionURL string + } + } + + t.Run("create_checkout_session", func() { + authenticated, _, _, _, _ := t.authenticate() + + id := gofakeit.LetterN(16) + + t.biller.On( + "PreparePerson", + mock.Anything, + mock.IsType(&data.Client{}), + mock.IsType(&data.Person{}), + ).Return(nil).Once() + + t.biller.On( + "CreateCheckoutSession", + mock.IsType(&data.Person{}), + ).Return( + id, + nil, + ).Once() + + var response createCheckoutSession + err := t.api.Post( + createCheckoutSessionMutation, + &response, + authenticated, + ) + + t.biller.AssertExpectations(t.T()) + + t.NoError(err) + t.Equal(id, response.CreateBillingCheckoutSession.CheckoutSessionURL) + }) + + t.Run("create_checkout_session_without_authentication", func() { + var response createCheckoutSession + err := t.api.Post( + createCheckoutSessionMutation, + &response, + ) + + t.NotNil(err) + t.ErrorContains(err, reporting.ErrUnauthenticated.Message) + }) + + const createPortalSessionMutation = ` + mutation() { + createBillingPortalSession() { + portalSessionUrl + } + } + ` + + type createPortalSession struct { + CreateBillingPortalSession struct { + PortalSessionURL string + } + } + + t.Run("create_portal_session", func() { + authenticated, _, _, _, _ := t.authenticate() + + id := gofakeit.LetterN(16) + + t.biller.On( + "PreparePerson", + mock.Anything, + mock.IsType(&data.Client{}), + mock.IsType(&data.Person{}), + ).Return(nil).Once() + + t.biller.On( + "CreatePortalSession", + mock.IsType(&data.Person{}), + ).Return( + id, + nil, + ).Once() + + var response createPortalSession + err := t.api.Post( + createPortalSessionMutation, + &response, + authenticated, + ) + + t.biller.AssertExpectations(t.T()) + + t.NoError(err) + t.Equal(id, response.CreateBillingPortalSession.PortalSessionURL) + }) + + t.Run("create_portal_session_without_authentication", func() { + var response createPortalSession + err := t.api.Post( + createPortalSessionMutation, + &response, + ) + + t.NotNil(err) + t.ErrorContains(err, reporting.ErrUnauthenticated.Message) + }) +} diff --git a/internal/api/resolvers/resolvers_test/main_test.go b/internal/api/resolvers/resolvers_test/main_test.go index 8345ead..8ddd364 100755 --- a/internal/api/resolvers/resolvers_test/main_test.go +++ b/internal/api/resolvers/resolvers_test/main_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/avptp/brain/internal/auth/auth_test" + "github.com/avptp/brain/internal/billing/billing_test" "github.com/avptp/brain/internal/config" "github.com/avptp/brain/internal/generated/container" "github.com/avptp/brain/internal/generated/data" @@ -55,6 +56,7 @@ type TestSuite struct { suite.Suite ctn *container.Container + biller *billing_test.MockedBiller captcha *auth_test.MockedCaptcha cfg *config.Config data *data.Client @@ -73,6 +75,13 @@ func (t *TestSuite) SetupSuite() { panic(err) // unrecoverable situation } + t.biller = &billing_test.MockedBiller{} + err = builder.Set(services.Biller, t.biller) + + if err != nil { + panic(err) // unrecoverable situation + } + t.captcha = &auth_test.MockedCaptcha{} err = builder.Set(services.Captcha, t.captcha) diff --git a/internal/api/resolvers/resolvers_test/person_test.go b/internal/api/resolvers/resolvers_test/person_test.go index 26323fd..efc4805 100755 --- a/internal/api/resolvers/resolvers_test/person_test.go +++ b/internal/api/resolvers/resolvers_test/person_test.go @@ -251,6 +251,11 @@ func (t *TestSuite) TestPerson() { input := t.factory.Person().Fields + t.biller.On( + "SyncPerson", + mock.IsType(&data.Person{}), + ).Return(nil).Once() + var response update err := t.api.Post( updateMutation, @@ -271,6 +276,8 @@ func (t *TestSuite) TestPerson() { client.Var("country", input.Country), ) + t.biller.AssertExpectations(t.T()) + t.NoError(err) t.Equal(p.ID, t.toUUID(response.UpdatePerson.Person.ID)) diff --git a/internal/api/schema/billing.graphqls b/internal/api/schema/billing.graphqls new file mode 100644 index 0000000..de6999c --- /dev/null +++ b/internal/api/schema/billing.graphqls @@ -0,0 +1,15 @@ +extend type Mutation { + createBillingCheckoutSession: CreateBillingCheckoutSessionPayload +} + +type CreateBillingCheckoutSessionPayload { + checkoutSessionUrl: String! +} + +extend type Mutation { + createBillingPortalSession: CreateBillingPortalSessionPayload +} + +type CreateBillingPortalSessionPayload { + portalSessionUrl: String! +} diff --git a/internal/api/schema/person.graphqls b/internal/api/schema/person.graphqls index 411da22..dbaf803 100644 --- a/internal/api/schema/person.graphqls +++ b/internal/api/schema/person.graphqls @@ -18,6 +18,8 @@ type Person { city: String country: String + subscribed: Boolean! + createdAt: Time! updatedAt: Time! diff --git a/internal/billing/billing_test/main.go b/internal/billing/billing_test/main.go new file mode 100644 index 0000000..a69e09d --- /dev/null +++ b/internal/billing/billing_test/main.go @@ -0,0 +1,36 @@ +package billing_test + +import ( + "context" + + "github.com/avptp/brain/internal/generated/data" + "github.com/stretchr/testify/mock" +) + +type MockedBiller struct { + mock.Mock +} + +func (m *MockedBiller) PreparePerson(ctx context.Context, d *data.Client, p *data.Person) error { + args := m.Called(ctx, d, p) + + return args.Error(0) +} + +func (m *MockedBiller) SyncPerson(p *data.Person) error { + args := m.Called(p) + + return args.Error(0) +} + +func (m *MockedBiller) CreateCheckoutSession(p *data.Person) (string, error) { + args := m.Called(p) + + return args.String(0), args.Error(1) +} + +func (m *MockedBiller) CreatePortalSession(p *data.Person) (string, error) { + args := m.Called(p) + + return args.String(0), args.Error(1) +} diff --git a/internal/billing/checkout.go b/internal/billing/checkout.go new file mode 100644 index 0000000..29aaa96 --- /dev/null +++ b/internal/billing/checkout.go @@ -0,0 +1,34 @@ +package billing + +import ( + "fmt" + + "github.com/avptp/brain/internal/generated/data" + "github.com/stripe/stripe-go/v76" + session "github.com/stripe/stripe-go/v76/checkout/session" +) + +func (b *StripeBiller) CreateCheckoutSession(p *data.Person) (string, error) { + s, err := session.New(&stripe.CheckoutSessionParams{ + Customer: p.StripeID, + Mode: stripe.String("subscription"), + LineItems: []*stripe.CheckoutSessionLineItemParams{ + { + Price: stripe.String(b.cfg.StripePriceID), + Quantity: stripe.Int64(1), + }, + }, + SuccessURL: stripe.String( + fmt.Sprintf("%s/billing/success", b.cfg.FrontUrl), + ), + CancelURL: stripe.String( + fmt.Sprintf("%s/billing/cancel", b.cfg.FrontUrl), + ), + }) + + if err != nil { + return "", err + } + + return s.URL, nil +} diff --git a/internal/billing/customer.go b/internal/billing/customer.go new file mode 100644 index 0000000..9039b9a --- /dev/null +++ b/internal/billing/customer.go @@ -0,0 +1,71 @@ +package billing + +import ( + "context" + + "github.com/avptp/brain/internal/generated/data" + "github.com/avptp/brain/internal/generated/data/privacy" + + "github.com/stripe/stripe-go/v76" + "github.com/stripe/stripe-go/v76/customer" +) + +// data.Client is not a struct property because function needs to receive a transactional one +func (b *StripeBiller) PreparePerson(ctx context.Context, d *data.Client, p *data.Person) error { + if p.StripeID != nil { + return nil + } + + c, err := customer.New( + customerParams(p), + ) + + if err != nil { + return err + } + + allowCtx := privacy.DecisionContext(ctx, privacy.Allow) + + _, err = d.Person. + UpdateOneID(p.ID). + SetStripeID(c.ID). + Save(allowCtx) + + return err +} + +func (b *StripeBiller) SyncPerson(p *data.Person) error { + if p.StripeID == nil { + return nil + } + + _, err := customer.Update( + *p.StripeID, + customerParams(p), + ) + + return err +} + +func customerParams(p *data.Person) *stripe.CustomerParams { + return &stripe.CustomerParams{ + Email: &p.Email, + Phone: p.Phone, + TaxIDData: []*stripe.CustomerTaxIDDataParams{ + { + Type: stripe.String("es_cif"), + Value: &p.TaxID, + }, + }, + Name: stripe.String(p.FullName()), + PreferredLocales: []*string{ + &p.Language, + }, + Address: &stripe.AddressParams{ + Line1: p.Address, + PostalCode: p.PostalCode, + City: p.City, + Country: p.Country, + }, + } +} diff --git a/internal/billing/main.go b/internal/billing/main.go new file mode 100644 index 0000000..cdc0882 --- /dev/null +++ b/internal/billing/main.go @@ -0,0 +1,29 @@ +package billing + +import ( + "context" + + "github.com/avptp/brain/internal/config" + "github.com/avptp/brain/internal/generated/data" + "github.com/stripe/stripe-go/v76" +) + +type Biller interface { + // data.Client is not a struct property because function needs to receive a transactional one + PreparePerson(context.Context, *data.Client, *data.Person) error + SyncPerson(*data.Person) error + CreateCheckoutSession(*data.Person) (string, error) + CreatePortalSession(*data.Person) (string, error) +} + +type StripeBiller struct { + cfg *config.Config +} + +func NewBiller(cfg *config.Config) Biller { + stripe.Key = cfg.StripeApiSecret + + return &StripeBiller{ + cfg, + } +} diff --git a/internal/billing/portal.go b/internal/billing/portal.go new file mode 100644 index 0000000..88f27d9 --- /dev/null +++ b/internal/billing/portal.go @@ -0,0 +1,24 @@ +package billing + +import ( + "fmt" + + "github.com/avptp/brain/internal/generated/data" + "github.com/stripe/stripe-go/v76" + session "github.com/stripe/stripe-go/v76/billingportal/session" +) + +func (b *StripeBiller) CreatePortalSession(p *data.Person) (string, error) { + s, err := session.New(&stripe.BillingPortalSessionParams{ + Customer: p.StripeID, + ReturnURL: stripe.String( + fmt.Sprintf("%s/billing", b.cfg.FrontUrl), + ), + }) + + if err != nil { + return "", err + } + + return s.URL, nil +} diff --git a/internal/config/main.go b/internal/config/main.go index cd332f9..82231ad 100755 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -41,4 +41,8 @@ type Config struct { OrgName string `env:"ORG_NAME"` OrgLogo string `env:"ORG_LOGO"` + + StripeApiSecret string `env:"STRIPE_API_SECRET"` + StripeEndpointSecret string `env:"STRIPE_ENDPOINT_SECRET"` + StripePriceID string `env:"STRIPE_PRICE_ID"` } diff --git a/internal/data/schema/person.go b/internal/data/schema/person.go index b2f57c4..f9bfccd 100644 --- a/internal/data/schema/person.go +++ b/internal/data/schema/person.go @@ -28,6 +28,13 @@ func (Person) Fields() []ent.Field { Default: "gen_random_ulid()", }, ), + field.String("stripe_id"). + SchemaType(map[string]string{ + dialect.Postgres: "string(255)", + }). + Optional(). + Nillable(). + Unique(), field.String("email"). SchemaType(map[string]string{ dialect.Postgres: "string(254)", @@ -142,6 +149,8 @@ func (Person) Fields() []ent.Field { Optional(). Nillable(). StructTag(`fake:"{countryabr}"`), + field.Bool("subscribed"). + Default(false), field.Time("created_at"). SchemaType(map[string]string{ dialect.Postgres: "timestamp", diff --git a/internal/generated/api/exec.go b/internal/generated/api/exec.go index 1d39c1d..89d0411 100644 --- a/internal/generated/api/exec.go +++ b/internal/generated/api/exec.go @@ -94,6 +94,14 @@ type ComplexityRoot struct { Token func(childComplexity int) int } + CreateBillingCheckoutSessionPayload struct { + CheckoutSessionURL func(childComplexity int) int + } + + CreateBillingPortalSessionPayload struct { + PortalSessionURL func(childComplexity int) int + } + CreateEmailAuthorizationPayload struct { Authorization func(childComplexity int) int } @@ -115,16 +123,18 @@ type ComplexityRoot struct { } Mutation struct { - ApplyEmailAuthorization func(childComplexity int, input ApplyEmailAuthorizationInput) int - ApplyPasswordAuthorization func(childComplexity int, input ApplyPasswordAuthorizationInput) int - CreateAuthentication func(childComplexity int, input CreateAuthenticationInput) int - CreateEmailAuthorization func(childComplexity int, input CreateEmailAuthorizationInput) int - CreatePasswordAuthorization func(childComplexity int, input CreatePasswordAuthorizationInput) int - CreatePerson func(childComplexity int, input CreatePersonInput) int - DeleteAuthentication func(childComplexity int, input DeleteAuthenticationInput) int - DeletePerson func(childComplexity int, input DeletePersonInput) int - UpdatePerson func(childComplexity int, input UpdatePersonInput) int - UpdatePersonPassword func(childComplexity int, input UpdatePersonPasswordInput) int + ApplyEmailAuthorization func(childComplexity int, input ApplyEmailAuthorizationInput) int + ApplyPasswordAuthorization func(childComplexity int, input ApplyPasswordAuthorizationInput) int + CreateAuthentication func(childComplexity int, input CreateAuthenticationInput) int + CreateBillingCheckoutSession func(childComplexity int) int + CreateBillingPortalSession func(childComplexity int) int + CreateEmailAuthorization func(childComplexity int, input CreateEmailAuthorizationInput) int + CreatePasswordAuthorization func(childComplexity int, input CreatePasswordAuthorizationInput) int + CreatePerson func(childComplexity int, input CreatePersonInput) int + DeleteAuthentication func(childComplexity int, input DeleteAuthenticationInput) int + DeletePerson func(childComplexity int, input DeletePersonInput) int + UpdatePerson func(childComplexity int, input UpdatePersonInput) int + UpdatePersonPassword func(childComplexity int, input UpdatePersonPasswordInput) int } PageInfo struct { @@ -150,6 +160,7 @@ type ComplexityRoot struct { LastName func(childComplexity int) int Phone func(childComplexity int) int PostalCode func(childComplexity int) int + Subscribed func(childComplexity int) int TaxID func(childComplexity int) int UpdatedAt func(childComplexity int) int } @@ -174,6 +185,8 @@ type MutationResolver interface { ApplyEmailAuthorization(ctx context.Context, input ApplyEmailAuthorizationInput) (*ApplyEmailAuthorizationPayload, error) CreatePasswordAuthorization(ctx context.Context, input CreatePasswordAuthorizationInput) (*CreatePasswordAuthorizationPayload, error) ApplyPasswordAuthorization(ctx context.Context, input ApplyPasswordAuthorizationInput) (*ApplyPasswordAuthorizationPayload, error) + CreateBillingCheckoutSession(ctx context.Context) (*CreateBillingCheckoutSessionPayload, error) + CreateBillingPortalSession(ctx context.Context) (*CreateBillingPortalSessionPayload, error) CreatePerson(ctx context.Context, input CreatePersonInput) (*CreatePersonPayload, error) UpdatePerson(ctx context.Context, input UpdatePersonInput) (*UpdatePersonPayload, error) UpdatePersonPassword(ctx context.Context, input UpdatePersonPasswordInput) (*UpdatePersonPasswordPayload, error) @@ -345,6 +358,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CreateAuthenticationPayload.Token(childComplexity), true + case "CreateBillingCheckoutSessionPayload.checkoutSessionUrl": + if e.complexity.CreateBillingCheckoutSessionPayload.CheckoutSessionURL == nil { + break + } + + return e.complexity.CreateBillingCheckoutSessionPayload.CheckoutSessionURL(childComplexity), true + + case "CreateBillingPortalSessionPayload.portalSessionUrl": + if e.complexity.CreateBillingPortalSessionPayload.PortalSessionURL == nil { + break + } + + return e.complexity.CreateBillingPortalSessionPayload.PortalSessionURL(childComplexity), true + case "CreateEmailAuthorizationPayload.authorization": if e.complexity.CreateEmailAuthorizationPayload.Authorization == nil { break @@ -416,6 +443,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.CreateAuthentication(childComplexity, args["input"].(CreateAuthenticationInput)), true + case "Mutation.createBillingCheckoutSession": + if e.complexity.Mutation.CreateBillingCheckoutSession == nil { + break + } + + return e.complexity.Mutation.CreateBillingCheckoutSession(childComplexity), true + + case "Mutation.createBillingPortalSession": + if e.complexity.Mutation.CreateBillingPortalSession == nil { + break + } + + return e.complexity.Mutation.CreateBillingPortalSession(childComplexity), true + case "Mutation.createEmailAuthorization": if e.complexity.Mutation.CreateEmailAuthorization == nil { break @@ -638,6 +679,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Person.PostalCode(childComplexity), true + case "Person.subscribed": + if e.complexity.Person.Subscribed == nil { + break + } + + return e.complexity.Person.Subscribed(childComplexity), true + case "Person.taxId": if e.complexity.Person.TaxID == nil { break @@ -899,6 +947,22 @@ input ApplyPasswordAuthorizationInput { type ApplyPasswordAuthorizationPayload { authorizationId: ID! } +`, BuiltIn: false}, + {Name: "../../api/schema/billing.graphqls", Input: `extend type Mutation { + createBillingCheckoutSession: CreateBillingCheckoutSessionPayload +} + +type CreateBillingCheckoutSessionPayload { + checkoutSessionUrl: String! +} + +extend type Mutation { + createBillingPortalSession: CreateBillingPortalSessionPayload +} + +type CreateBillingPortalSessionPayload { + portalSessionUrl: String! +} `, BuiltIn: false}, {Name: "../../api/schema/main.graphqls", Input: `type Query @@ -938,6 +1002,8 @@ type PageInfo { city: String country: String + subscribed: Boolean! + createdAt: Time! updatedAt: Time! @@ -1701,6 +1767,8 @@ func (ec *executionContext) fieldContext_Authentication_person(ctx context.Conte return ec.fieldContext_Person_city(ctx, field) case "country": return ec.fieldContext_Person_country(ctx, field) + case "subscribed": + return ec.fieldContext_Person_subscribed(ctx, field) case "createdAt": return ec.fieldContext_Person_createdAt(ctx, field) case "updatedAt": @@ -2203,6 +2271,8 @@ func (ec *executionContext) fieldContext_Authorization_person(ctx context.Contex return ec.fieldContext_Person_city(ctx, field) case "country": return ec.fieldContext_Person_country(ctx, field) + case "subscribed": + return ec.fieldContext_Person_subscribed(ctx, field) case "createdAt": return ec.fieldContext_Person_createdAt(ctx, field) case "updatedAt": @@ -2260,6 +2330,94 @@ func (ec *executionContext) fieldContext_CreateAuthenticationPayload_token(ctx c return fc, nil } +func (ec *executionContext) _CreateBillingCheckoutSessionPayload_checkoutSessionUrl(ctx context.Context, field graphql.CollectedField, obj *CreateBillingCheckoutSessionPayload) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CreateBillingCheckoutSessionPayload_checkoutSessionUrl(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CheckoutSessionURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CreateBillingCheckoutSessionPayload_checkoutSessionUrl(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CreateBillingCheckoutSessionPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _CreateBillingPortalSessionPayload_portalSessionUrl(ctx context.Context, field graphql.CollectedField, obj *CreateBillingPortalSessionPayload) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CreateBillingPortalSessionPayload_portalSessionUrl(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.PortalSessionURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CreateBillingPortalSessionPayload_portalSessionUrl(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CreateBillingPortalSessionPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _CreateEmailAuthorizationPayload_authorization(ctx context.Context, field graphql.CollectedField, obj *CreateEmailAuthorizationPayload) (ret graphql.Marshaler) { fc, err := ec.fieldContext_CreateEmailAuthorizationPayload_authorization(ctx, field) if err != nil { @@ -2427,6 +2585,8 @@ func (ec *executionContext) fieldContext_CreatePersonPayload_person(ctx context. return ec.fieldContext_Person_city(ctx, field) case "country": return ec.fieldContext_Person_country(ctx, field) + case "subscribed": + return ec.fieldContext_Person_subscribed(ctx, field) case "createdAt": return ec.fieldContext_Person_createdAt(ctx, field) case "updatedAt": @@ -2864,6 +3024,96 @@ func (ec *executionContext) fieldContext_Mutation_applyPasswordAuthorization(ctx return fc, nil } +func (ec *executionContext) _Mutation_createBillingCheckoutSession(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createBillingCheckoutSession(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateBillingCheckoutSession(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*CreateBillingCheckoutSessionPayload) + fc.Result = res + return ec.marshalOCreateBillingCheckoutSessionPayload2ᚖgithubᚗcomᚋavptpᚋbrainᚋinternalᚋgeneratedᚋapiᚐCreateBillingCheckoutSessionPayload(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createBillingCheckoutSession(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "checkoutSessionUrl": + return ec.fieldContext_CreateBillingCheckoutSessionPayload_checkoutSessionUrl(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CreateBillingCheckoutSessionPayload", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Mutation_createBillingPortalSession(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createBillingPortalSession(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateBillingPortalSession(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*CreateBillingPortalSessionPayload) + fc.Result = res + return ec.marshalOCreateBillingPortalSessionPayload2ᚖgithubᚗcomᚋavptpᚋbrainᚋinternalᚋgeneratedᚋapiᚐCreateBillingPortalSessionPayload(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createBillingPortalSession(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "portalSessionUrl": + return ec.fieldContext_CreateBillingPortalSessionPayload_portalSessionUrl(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CreateBillingPortalSessionPayload", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Mutation_createPerson(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_createPerson(ctx, field) if err != nil { @@ -3847,6 +4097,50 @@ func (ec *executionContext) fieldContext_Person_country(ctx context.Context, fie return fc, nil } +func (ec *executionContext) _Person_subscribed(ctx context.Context, field graphql.CollectedField, obj *data.Person) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Person_subscribed(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Subscribed, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Person_subscribed(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Person", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Person_createdAt(ctx context.Context, field graphql.CollectedField, obj *data.Person) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Person_createdAt(ctx, field) if err != nil { @@ -4062,6 +4356,8 @@ func (ec *executionContext) fieldContext_Query_viewer(ctx context.Context, field return ec.fieldContext_Person_city(ctx, field) case "country": return ec.fieldContext_Person_country(ctx, field) + case "subscribed": + return ec.fieldContext_Person_subscribed(ctx, field) case "createdAt": return ec.fieldContext_Person_createdAt(ctx, field) case "updatedAt": @@ -4271,6 +4567,8 @@ func (ec *executionContext) fieldContext_UpdatePersonPasswordPayload_person(ctx return ec.fieldContext_Person_city(ctx, field) case "country": return ec.fieldContext_Person_country(ctx, field) + case "subscribed": + return ec.fieldContext_Person_subscribed(ctx, field) case "createdAt": return ec.fieldContext_Person_createdAt(ctx, field) case "updatedAt": @@ -4351,6 +4649,8 @@ func (ec *executionContext) fieldContext_UpdatePersonPayload_person(ctx context. return ec.fieldContext_Person_city(ctx, field) case "country": return ec.fieldContext_Person_country(ctx, field) + case "subscribed": + return ec.fieldContext_Person_subscribed(ctx, field) case "createdAt": return ec.fieldContext_Person_createdAt(ctx, field) case "updatedAt": @@ -7063,6 +7363,84 @@ func (ec *executionContext) _CreateAuthenticationPayload(ctx context.Context, se return out } +var createBillingCheckoutSessionPayloadImplementors = []string{"CreateBillingCheckoutSessionPayload"} + +func (ec *executionContext) _CreateBillingCheckoutSessionPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateBillingCheckoutSessionPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, createBillingCheckoutSessionPayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CreateBillingCheckoutSessionPayload") + case "checkoutSessionUrl": + out.Values[i] = ec._CreateBillingCheckoutSessionPayload_checkoutSessionUrl(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var createBillingPortalSessionPayloadImplementors = []string{"CreateBillingPortalSessionPayload"} + +func (ec *executionContext) _CreateBillingPortalSessionPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateBillingPortalSessionPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, createBillingPortalSessionPayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CreateBillingPortalSessionPayload") + case "portalSessionUrl": + out.Values[i] = ec._CreateBillingPortalSessionPayload_portalSessionUrl(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var createEmailAuthorizationPayloadImplementors = []string{"CreateEmailAuthorizationPayload"} func (ec *executionContext) _CreateEmailAuthorizationPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateEmailAuthorizationPayload) graphql.Marshaler { @@ -7301,6 +7679,14 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_applyPasswordAuthorization(ctx, field) }) + case "createBillingCheckoutSession": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createBillingCheckoutSession(ctx, field) + }) + case "createBillingPortalSession": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createBillingPortalSession(ctx, field) + }) case "createPerson": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_createPerson(ctx, field) @@ -7442,6 +7828,11 @@ func (ec *executionContext) _Person(ctx context.Context, sel ast.SelectionSet, o out.Values[i] = ec._Person_city(ctx, field, obj) case "country": out.Values[i] = ec._Person_country(ctx, field, obj) + case "subscribed": + out.Values[i] = ec._Person_subscribed(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "createdAt": out.Values[i] = ec._Person_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -8534,6 +8925,20 @@ func (ec *executionContext) marshalOCreateAuthenticationPayload2ᚖgithubᚗcom return ec._CreateAuthenticationPayload(ctx, sel, v) } +func (ec *executionContext) marshalOCreateBillingCheckoutSessionPayload2ᚖgithubᚗcomᚋavptpᚋbrainᚋinternalᚋgeneratedᚋapiᚐCreateBillingCheckoutSessionPayload(ctx context.Context, sel ast.SelectionSet, v *CreateBillingCheckoutSessionPayload) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._CreateBillingCheckoutSessionPayload(ctx, sel, v) +} + +func (ec *executionContext) marshalOCreateBillingPortalSessionPayload2ᚖgithubᚗcomᚋavptpᚋbrainᚋinternalᚋgeneratedᚋapiᚐCreateBillingPortalSessionPayload(ctx context.Context, sel ast.SelectionSet, v *CreateBillingPortalSessionPayload) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._CreateBillingPortalSessionPayload(ctx, sel, v) +} + func (ec *executionContext) marshalOCreateEmailAuthorizationPayload2ᚖgithubᚗcomᚋavptpᚋbrainᚋinternalᚋgeneratedᚋapiᚐCreateEmailAuthorizationPayload(ctx context.Context, sel ast.SelectionSet, v *CreateEmailAuthorizationPayload) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/internal/generated/api/model.go b/internal/generated/api/model.go index 40612c4..f3fc47a 100644 --- a/internal/generated/api/model.go +++ b/internal/generated/api/model.go @@ -37,6 +37,14 @@ type CreateAuthenticationPayload struct { Token string `json:"token"` } +type CreateBillingCheckoutSessionPayload struct { + CheckoutSessionURL string `json:"checkoutSessionUrl"` +} + +type CreateBillingPortalSessionPayload struct { + PortalSessionURL string `json:"portalSessionUrl"` +} + type CreateEmailAuthorizationInput struct { PersonID uuid.UUID `json:"personId"` } diff --git a/internal/generated/container/container.go b/internal/generated/container/container.go index 937bf42..b541c57 100644 --- a/internal/generated/container/container.go +++ b/internal/generated/container/container.go @@ -14,6 +14,7 @@ import ( resolvers "github.com/avptp/brain/internal/api/resolvers" auth "github.com/avptp/brain/internal/auth" + billing "github.com/avptp/brain/internal/billing" config "github.com/avptp/brain/internal/config" data "github.com/avptp/brain/internal/generated/data" messaging "github.com/avptp/brain/internal/messaging" @@ -227,6 +228,136 @@ func (c *Container) IsClosed() bool { return c.ctn.IsClosed() } +// SafeGetBiller retrieves the "biller" object from the app scope. +// +// --------------------------------------------- +// +// name: "biller" +// type: billing.Biller +// scope: "app" +// build: func +// params: +// - "0": Service(*config.Config) ["config"] +// unshared: false +// close: false +// +// --------------------------------------------- +// +// If the object can not be retrieved, it returns an error. +func (c *Container) SafeGetBiller() (billing.Biller, error) { + i, err := c.ctn.SafeGet("biller") + if err != nil { + var eo billing.Biller + return eo, err + } + o, ok := i.(billing.Biller) + if !ok { + return o, errors.New("could get 'biller' because the object could not be cast to billing.Biller") + } + return o, nil +} + +// GetBiller retrieves the "biller" object from the app scope. +// +// --------------------------------------------- +// +// name: "biller" +// type: billing.Biller +// scope: "app" +// build: func +// params: +// - "0": Service(*config.Config) ["config"] +// unshared: false +// close: false +// +// --------------------------------------------- +// +// If the object can not be retrieved, it panics. +func (c *Container) GetBiller() billing.Biller { + o, err := c.SafeGetBiller() + if err != nil { + panic(err) + } + return o +} + +// UnscopedSafeGetBiller retrieves the "biller" object from the app scope. +// +// --------------------------------------------- +// +// name: "biller" +// type: billing.Biller +// scope: "app" +// build: func +// params: +// - "0": Service(*config.Config) ["config"] +// unshared: false +// close: false +// +// --------------------------------------------- +// +// This method can be called even if app is a sub-scope of the container. +// If the object can not be retrieved, it returns an error. +func (c *Container) UnscopedSafeGetBiller() (billing.Biller, error) { + i, err := c.ctn.UnscopedSafeGet("biller") + if err != nil { + var eo billing.Biller + return eo, err + } + o, ok := i.(billing.Biller) + if !ok { + return o, errors.New("could get 'biller' because the object could not be cast to billing.Biller") + } + return o, nil +} + +// UnscopedGetBiller retrieves the "biller" object from the app scope. +// +// --------------------------------------------- +// +// name: "biller" +// type: billing.Biller +// scope: "app" +// build: func +// params: +// - "0": Service(*config.Config) ["config"] +// unshared: false +// close: false +// +// --------------------------------------------- +// +// This method can be called even if app is a sub-scope of the container. +// If the object can not be retrieved, it panics. +func (c *Container) UnscopedGetBiller() billing.Biller { + o, err := c.UnscopedSafeGetBiller() + if err != nil { + panic(err) + } + return o +} + +// Biller retrieves the "biller" object from the app scope. +// +// --------------------------------------------- +// +// name: "biller" +// type: billing.Biller +// scope: "app" +// build: func +// params: +// - "0": Service(*config.Config) ["config"] +// unshared: false +// close: false +// +// --------------------------------------------- +// +// It tries to find the container with the C method and the given interface. +// If the container can be retrieved, it calls the GetBiller method. +// If the container can not be retrieved, it panics. +func Biller(i interface{}) billing.Biller { + return C(i).GetBiller() +} + // SafeGetCaptcha retrieves the "captcha" object from the app scope. // // --------------------------------------------- @@ -1411,11 +1542,12 @@ func Redis(i interface{}) *v.Client { // scope: "app" // build: func // params: -// - "0": Service(auth.Captcha) ["captcha"] -// - "1": Service(*config.Config) ["config"] -// - "2": Service(*data.Client) ["data"] -// - "3": Service(*v1.Limiter) ["limiter"] -// - "4": Service(messaging.Messenger) ["messenger"] +// - "0": Service(billing.Biller) ["biller"] +// - "1": Service(auth.Captcha) ["captcha"] +// - "2": Service(*config.Config) ["config"] +// - "3": Service(*data.Client) ["data"] +// - "4": Service(*v1.Limiter) ["limiter"] +// - "5": Service(messaging.Messenger) ["messenger"] // unshared: false // close: false // @@ -1444,11 +1576,12 @@ func (c *Container) SafeGetResolver() (*resolvers.Resolver, error) { // scope: "app" // build: func // params: -// - "0": Service(auth.Captcha) ["captcha"] -// - "1": Service(*config.Config) ["config"] -// - "2": Service(*data.Client) ["data"] -// - "3": Service(*v1.Limiter) ["limiter"] -// - "4": Service(messaging.Messenger) ["messenger"] +// - "0": Service(billing.Biller) ["biller"] +// - "1": Service(auth.Captcha) ["captcha"] +// - "2": Service(*config.Config) ["config"] +// - "3": Service(*data.Client) ["data"] +// - "4": Service(*v1.Limiter) ["limiter"] +// - "5": Service(messaging.Messenger) ["messenger"] // unshared: false // close: false // @@ -1472,11 +1605,12 @@ func (c *Container) GetResolver() *resolvers.Resolver { // scope: "app" // build: func // params: -// - "0": Service(auth.Captcha) ["captcha"] -// - "1": Service(*config.Config) ["config"] -// - "2": Service(*data.Client) ["data"] -// - "3": Service(*v1.Limiter) ["limiter"] -// - "4": Service(messaging.Messenger) ["messenger"] +// - "0": Service(billing.Biller) ["biller"] +// - "1": Service(auth.Captcha) ["captcha"] +// - "2": Service(*config.Config) ["config"] +// - "3": Service(*data.Client) ["data"] +// - "4": Service(*v1.Limiter) ["limiter"] +// - "5": Service(messaging.Messenger) ["messenger"] // unshared: false // close: false // @@ -1506,11 +1640,12 @@ func (c *Container) UnscopedSafeGetResolver() (*resolvers.Resolver, error) { // scope: "app" // build: func // params: -// - "0": Service(auth.Captcha) ["captcha"] -// - "1": Service(*config.Config) ["config"] -// - "2": Service(*data.Client) ["data"] -// - "3": Service(*v1.Limiter) ["limiter"] -// - "4": Service(messaging.Messenger) ["messenger"] +// - "0": Service(billing.Biller) ["biller"] +// - "1": Service(auth.Captcha) ["captcha"] +// - "2": Service(*config.Config) ["config"] +// - "3": Service(*data.Client) ["data"] +// - "4": Service(*v1.Limiter) ["limiter"] +// - "5": Service(messaging.Messenger) ["messenger"] // unshared: false // close: false // @@ -1535,11 +1670,12 @@ func (c *Container) UnscopedGetResolver() *resolvers.Resolver { // scope: "app" // build: func // params: -// - "0": Service(auth.Captcha) ["captcha"] -// - "1": Service(*config.Config) ["config"] -// - "2": Service(*data.Client) ["data"] -// - "3": Service(*v1.Limiter) ["limiter"] -// - "4": Service(messaging.Messenger) ["messenger"] +// - "0": Service(billing.Biller) ["biller"] +// - "1": Service(auth.Captcha) ["captcha"] +// - "2": Service(*config.Config) ["config"] +// - "3": Service(*data.Client) ["data"] +// - "4": Service(*v1.Limiter) ["limiter"] +// - "5": Service(messaging.Messenger) ["messenger"] // unshared: false // close: false // diff --git a/internal/generated/container/defs.go b/internal/generated/container/defs.go index 06adf39..a2d0174 100644 --- a/internal/generated/container/defs.go +++ b/internal/generated/container/defs.go @@ -10,6 +10,7 @@ import ( resolvers "github.com/avptp/brain/internal/api/resolvers" auth "github.com/avptp/brain/internal/auth" + billing "github.com/avptp/brain/internal/billing" config "github.com/avptp/brain/internal/config" data "github.com/avptp/brain/internal/generated/data" messaging "github.com/avptp/brain/internal/messaging" @@ -23,6 +24,34 @@ import ( func getDiDefs(provider dingo.Provider) []di.Def { return []di.Def{ + { + Name: "biller", + Scope: "app", + Build: func(ctn di.Container) (interface{}, error) { + d, err := provider.Get("biller") + if err != nil { + var eo billing.Biller + return eo, err + } + pi0, err := ctn.SafeGet("config") + if err != nil { + var eo billing.Biller + return eo, err + } + p0, ok := pi0.(*config.Config) + if !ok { + var eo billing.Biller + return eo, errors.New("could not cast parameter 0 to *config.Config") + } + b, ok := d.Build.(func(*config.Config) (billing.Biller, error)) + if !ok { + var eo billing.Biller + return eo, errors.New("could not cast build function to func(*config.Config) (billing.Biller, error)") + } + return b(p0) + }, + Unshared: false, + }, { Name: "captcha", Scope: "app", @@ -309,62 +338,72 @@ func getDiDefs(provider dingo.Provider) []di.Def { var eo *resolvers.Resolver return eo, err } - pi0, err := ctn.SafeGet("captcha") + pi0, err := ctn.SafeGet("biller") + if err != nil { + var eo *resolvers.Resolver + return eo, err + } + p0, ok := pi0.(billing.Biller) + if !ok { + var eo *resolvers.Resolver + return eo, errors.New("could not cast parameter 0 to billing.Biller") + } + pi1, err := ctn.SafeGet("captcha") if err != nil { var eo *resolvers.Resolver return eo, err } - p0, ok := pi0.(auth.Captcha) + p1, ok := pi1.(auth.Captcha) if !ok { var eo *resolvers.Resolver - return eo, errors.New("could not cast parameter 0 to auth.Captcha") + return eo, errors.New("could not cast parameter 1 to auth.Captcha") } - pi1, err := ctn.SafeGet("config") + pi2, err := ctn.SafeGet("config") if err != nil { var eo *resolvers.Resolver return eo, err } - p1, ok := pi1.(*config.Config) + p2, ok := pi2.(*config.Config) if !ok { var eo *resolvers.Resolver - return eo, errors.New("could not cast parameter 1 to *config.Config") + return eo, errors.New("could not cast parameter 2 to *config.Config") } - pi2, err := ctn.SafeGet("data") + pi3, err := ctn.SafeGet("data") if err != nil { var eo *resolvers.Resolver return eo, err } - p2, ok := pi2.(*data.Client) + p3, ok := pi3.(*data.Client) if !ok { var eo *resolvers.Resolver - return eo, errors.New("could not cast parameter 2 to *data.Client") + return eo, errors.New("could not cast parameter 3 to *data.Client") } - pi3, err := ctn.SafeGet("limiter") + pi4, err := ctn.SafeGet("limiter") if err != nil { var eo *resolvers.Resolver return eo, err } - p3, ok := pi3.(*v1.Limiter) + p4, ok := pi4.(*v1.Limiter) if !ok { var eo *resolvers.Resolver - return eo, errors.New("could not cast parameter 3 to *v1.Limiter") + return eo, errors.New("could not cast parameter 4 to *v1.Limiter") } - pi4, err := ctn.SafeGet("messenger") + pi5, err := ctn.SafeGet("messenger") if err != nil { var eo *resolvers.Resolver return eo, err } - p4, ok := pi4.(messaging.Messenger) + p5, ok := pi5.(messaging.Messenger) if !ok { var eo *resolvers.Resolver - return eo, errors.New("could not cast parameter 4 to messaging.Messenger") + return eo, errors.New("could not cast parameter 5 to messaging.Messenger") } - b, ok := d.Build.(func(auth.Captcha, *config.Config, *data.Client, *v1.Limiter, messaging.Messenger) (*resolvers.Resolver, error)) + b, ok := d.Build.(func(billing.Biller, auth.Captcha, *config.Config, *data.Client, *v1.Limiter, messaging.Messenger) (*resolvers.Resolver, error)) if !ok { var eo *resolvers.Resolver - return eo, errors.New("could not cast build function to func(auth.Captcha, *config.Config, *data.Client, *v1.Limiter, messaging.Messenger) (*resolvers.Resolver, error)") + return eo, errors.New("could not cast build function to func(billing.Biller, auth.Captcha, *config.Config, *data.Client, *v1.Limiter, messaging.Messenger) (*resolvers.Resolver, error)") } - return b(p0, p1, p2, p3, p4) + return b(p0, p1, p2, p3, p4, p5) }, Unshared: false, }, diff --git a/internal/generated/data/entql.go b/internal/generated/data/entql.go index a42d69c..3e8ba7c 100755 --- a/internal/generated/data/entql.go +++ b/internal/generated/data/entql.go @@ -64,6 +64,7 @@ var schemaGraph = func() *sqlgraph.Schema { }, Type: "Person", Fields: map[string]*sqlgraph.FieldSpec{ + person.FieldStripeID: {Type: field.TypeString, Column: person.FieldStripeID}, person.FieldEmail: {Type: field.TypeString, Column: person.FieldEmail}, person.FieldEmailVerifiedAt: {Type: field.TypeTime, Column: person.FieldEmailVerifiedAt}, person.FieldPhone: {Type: field.TypeString, Column: person.FieldPhone}, @@ -78,6 +79,7 @@ var schemaGraph = func() *sqlgraph.Schema { person.FieldPostalCode: {Type: field.TypeString, Column: person.FieldPostalCode}, person.FieldCity: {Type: field.TypeString, Column: person.FieldCity}, person.FieldCountry: {Type: field.TypeString, Column: person.FieldCountry}, + person.FieldSubscribed: {Type: field.TypeBool, Column: person.FieldSubscribed}, person.FieldCreatedAt: {Type: field.TypeTime, Column: person.FieldCreatedAt}, person.FieldUpdatedAt: {Type: field.TypeTime, Column: person.FieldUpdatedAt}, }, @@ -337,6 +339,11 @@ func (f *PersonFilter) WhereID(p entql.ValueP) { f.Where(p.Field(person.FieldID)) } +// WhereStripeID applies the entql string predicate on the stripe_id field. +func (f *PersonFilter) WhereStripeID(p entql.StringP) { + f.Where(p.Field(person.FieldStripeID)) +} + // WhereEmail applies the entql string predicate on the email field. func (f *PersonFilter) WhereEmail(p entql.StringP) { f.Where(p.Field(person.FieldEmail)) @@ -407,6 +414,11 @@ func (f *PersonFilter) WhereCountry(p entql.StringP) { f.Where(p.Field(person.FieldCountry)) } +// WhereSubscribed applies the entql bool predicate on the subscribed field. +func (f *PersonFilter) WhereSubscribed(p entql.BoolP) { + f.Where(p.Field(person.FieldSubscribed)) +} + // WhereCreatedAt applies the entql time.Time predicate on the created_at field. func (f *PersonFilter) WhereCreatedAt(p entql.TimeP) { f.Where(p.Field(person.FieldCreatedAt)) diff --git a/internal/generated/data/factories/factories.go b/internal/generated/data/factories/factories.go index 1d68873..5db59ed 100644 --- a/internal/generated/data/factories/factories.go +++ b/internal/generated/data/factories/factories.go @@ -129,6 +129,7 @@ type PersonFactory struct { } type PersonFields struct { + StripeID *string `json:"stripe_id,omitempty"` Email string `json:"email,omitempty" fake:"{email}"` EmailVerifiedAt *time.Time `json:"email_verified_at,omitempty"` Phone *string `json:"phone,omitempty" fake:"{phone_e164}"` @@ -143,6 +144,7 @@ type PersonFields struct { PostalCode *string `json:"postal_code,omitempty" fake:"{zip}"` City *string `json:"city,omitempty" fake:"{city}"` Country *string `json:"country,omitempty" fake:"{countryabr}"` + Subscribed bool `json:"subscribed,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` } @@ -156,6 +158,7 @@ func (bf *Factory) Person() *PersonFactory { f.builder = f.data.Person. Create(). + SetNillableStripeID(f.Fields.StripeID). SetEmail(f.Fields.Email). SetNillableEmailVerifiedAt(f.Fields.EmailVerifiedAt). SetNillablePhone(f.Fields.Phone). diff --git a/internal/generated/data/gql_collection.go b/internal/generated/data/gql_collection.go index 2c837f7..cc772d0 100755 --- a/internal/generated/data/gql_collection.go +++ b/internal/generated/data/gql_collection.go @@ -259,6 +259,11 @@ func (pe *PersonQuery) collectField(ctx context.Context, opCtx *graphql.Operatio pe.WithNamedAuthorizations(alias, func(wq *AuthorizationQuery) { *wq = *query }) + case "stripeID": + if _, ok := fieldSeen[person.FieldStripeID]; !ok { + selectedFields = append(selectedFields, person.FieldStripeID) + fieldSeen[person.FieldStripeID] = struct{}{} + } case "email": if _, ok := fieldSeen[person.FieldEmail]; !ok { selectedFields = append(selectedFields, person.FieldEmail) @@ -324,6 +329,11 @@ func (pe *PersonQuery) collectField(ctx context.Context, opCtx *graphql.Operatio selectedFields = append(selectedFields, person.FieldCountry) fieldSeen[person.FieldCountry] = struct{}{} } + case "subscribed": + if _, ok := fieldSeen[person.FieldSubscribed]; !ok { + selectedFields = append(selectedFields, person.FieldSubscribed) + fieldSeen[person.FieldSubscribed] = struct{}{} + } case "createdAt": if _, ok := fieldSeen[person.FieldCreatedAt]; !ok { selectedFields = append(selectedFields, person.FieldCreatedAt) diff --git a/internal/generated/data/gql_where_input.go b/internal/generated/data/gql_where_input.go index dfedcc3..9a96ef6 100755 --- a/internal/generated/data/gql_where_input.go +++ b/internal/generated/data/gql_where_input.go @@ -587,6 +587,23 @@ type PersonWhereInput struct { IDLT *uuid.UUID `json:"idLT,omitempty"` IDLTE *uuid.UUID `json:"idLTE,omitempty"` + // "stripe_id" field predicates. + StripeID *string `json:"stripeID,omitempty"` + StripeIDNEQ *string `json:"stripeIDNEQ,omitempty"` + StripeIDIn []string `json:"stripeIDIn,omitempty"` + StripeIDNotIn []string `json:"stripeIDNotIn,omitempty"` + StripeIDGT *string `json:"stripeIDGT,omitempty"` + StripeIDGTE *string `json:"stripeIDGTE,omitempty"` + StripeIDLT *string `json:"stripeIDLT,omitempty"` + StripeIDLTE *string `json:"stripeIDLTE,omitempty"` + StripeIDContains *string `json:"stripeIDContains,omitempty"` + StripeIDHasPrefix *string `json:"stripeIDHasPrefix,omitempty"` + StripeIDHasSuffix *string `json:"stripeIDHasSuffix,omitempty"` + StripeIDIsNil bool `json:"stripeIDIsNil,omitempty"` + StripeIDNotNil bool `json:"stripeIDNotNil,omitempty"` + StripeIDEqualFold *string `json:"stripeIDEqualFold,omitempty"` + StripeIDContainsFold *string `json:"stripeIDContainsFold,omitempty"` + // "email" field predicates. Email *string `json:"email,omitempty"` EmailNEQ *string `json:"emailNEQ,omitempty"` @@ -796,6 +813,10 @@ type PersonWhereInput struct { CountryEqualFold *string `json:"countryEqualFold,omitempty"` CountryContainsFold *string `json:"countryContainsFold,omitempty"` + // "subscribed" field predicates. + Subscribed *bool `json:"subscribed,omitempty"` + SubscribedNEQ *bool `json:"subscribedNEQ,omitempty"` + // "created_at" field predicates. CreatedAt *time.Time `json:"createdAt,omitempty"` CreatedAtNEQ *time.Time `json:"createdAtNEQ,omitempty"` @@ -920,6 +941,51 @@ func (i *PersonWhereInput) P() (predicate.Person, error) { if i.IDLTE != nil { predicates = append(predicates, person.IDLTE(*i.IDLTE)) } + if i.StripeID != nil { + predicates = append(predicates, person.StripeIDEQ(*i.StripeID)) + } + if i.StripeIDNEQ != nil { + predicates = append(predicates, person.StripeIDNEQ(*i.StripeIDNEQ)) + } + if len(i.StripeIDIn) > 0 { + predicates = append(predicates, person.StripeIDIn(i.StripeIDIn...)) + } + if len(i.StripeIDNotIn) > 0 { + predicates = append(predicates, person.StripeIDNotIn(i.StripeIDNotIn...)) + } + if i.StripeIDGT != nil { + predicates = append(predicates, person.StripeIDGT(*i.StripeIDGT)) + } + if i.StripeIDGTE != nil { + predicates = append(predicates, person.StripeIDGTE(*i.StripeIDGTE)) + } + if i.StripeIDLT != nil { + predicates = append(predicates, person.StripeIDLT(*i.StripeIDLT)) + } + if i.StripeIDLTE != nil { + predicates = append(predicates, person.StripeIDLTE(*i.StripeIDLTE)) + } + if i.StripeIDContains != nil { + predicates = append(predicates, person.StripeIDContains(*i.StripeIDContains)) + } + if i.StripeIDHasPrefix != nil { + predicates = append(predicates, person.StripeIDHasPrefix(*i.StripeIDHasPrefix)) + } + if i.StripeIDHasSuffix != nil { + predicates = append(predicates, person.StripeIDHasSuffix(*i.StripeIDHasSuffix)) + } + if i.StripeIDIsNil { + predicates = append(predicates, person.StripeIDIsNil()) + } + if i.StripeIDNotNil { + predicates = append(predicates, person.StripeIDNotNil()) + } + if i.StripeIDEqualFold != nil { + predicates = append(predicates, person.StripeIDEqualFold(*i.StripeIDEqualFold)) + } + if i.StripeIDContainsFold != nil { + predicates = append(predicates, person.StripeIDContainsFold(*i.StripeIDContainsFold)) + } if i.Email != nil { predicates = append(predicates, person.EmailEQ(*i.Email)) } @@ -1463,6 +1529,12 @@ func (i *PersonWhereInput) P() (predicate.Person, error) { if i.CountryContainsFold != nil { predicates = append(predicates, person.CountryContainsFold(*i.CountryContainsFold)) } + if i.Subscribed != nil { + predicates = append(predicates, person.SubscribedEQ(*i.Subscribed)) + } + if i.SubscribedNEQ != nil { + predicates = append(predicates, person.SubscribedNEQ(*i.SubscribedNEQ)) + } if i.CreatedAt != nil { predicates = append(predicates, person.CreatedAtEQ(*i.CreatedAt)) } diff --git a/internal/generated/data/migrate/schema.go b/internal/generated/data/migrate/schema.go index 86eeb42..8db29da 100755 --- a/internal/generated/data/migrate/schema.go +++ b/internal/generated/data/migrate/schema.go @@ -64,6 +64,7 @@ var ( // PersonsColumns holds the columns for the "persons" table. PersonsColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID, Default: "gen_random_ulid()"}, + {Name: "stripe_id", Type: field.TypeString, Unique: true, Nullable: true, SchemaType: map[string]string{"postgres": "string(255)"}}, {Name: "email", Type: field.TypeString, Unique: true, Size: 254, SchemaType: map[string]string{"postgres": "string(254)"}}, {Name: "email_verified_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamp"}}, {Name: "phone", Type: field.TypeString, Unique: true, Nullable: true, Size: 16, SchemaType: map[string]string{"postgres": "string(16)"}}, @@ -78,6 +79,7 @@ var ( {Name: "postal_code", Type: field.TypeString, Nullable: true, Size: 10, SchemaType: map[string]string{"postgres": "string(10)"}}, {Name: "city", Type: field.TypeString, Nullable: true, Size: 58, SchemaType: map[string]string{"postgres": "string(58)"}}, {Name: "country", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "char(2)"}}, + {Name: "subscribed", Type: field.TypeBool, Default: false}, {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamp"}}, {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamp"}}, } diff --git a/internal/generated/data/mutation.go b/internal/generated/data/mutation.go index b145b86..f78b641 100755 --- a/internal/generated/data/mutation.go +++ b/internal/generated/data/mutation.go @@ -1242,6 +1242,7 @@ type PersonMutation struct { op Op typ string id *uuid.UUID + stripe_id *string email *string email_verified_at *time.Time phone *string @@ -1256,6 +1257,7 @@ type PersonMutation struct { postal_code *string city *string country *string + subscribed *bool created_at *time.Time updated_at *time.Time clearedFields map[string]struct{} @@ -1374,6 +1376,55 @@ func (m *PersonMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { } } +// SetStripeID sets the "stripe_id" field. +func (m *PersonMutation) SetStripeID(s string) { + m.stripe_id = &s +} + +// StripeID returns the value of the "stripe_id" field in the mutation. +func (m *PersonMutation) StripeID() (r string, exists bool) { + v := m.stripe_id + if v == nil { + return + } + return *v, true +} + +// OldStripeID returns the old "stripe_id" field's value of the Person entity. +// If the Person 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 *PersonMutation) OldStripeID(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStripeID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStripeID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStripeID: %w", err) + } + return oldValue.StripeID, nil +} + +// ClearStripeID clears the value of the "stripe_id" field. +func (m *PersonMutation) ClearStripeID() { + m.stripe_id = nil + m.clearedFields[person.FieldStripeID] = struct{}{} +} + +// StripeIDCleared returns if the "stripe_id" field was cleared in this mutation. +func (m *PersonMutation) StripeIDCleared() bool { + _, ok := m.clearedFields[person.FieldStripeID] + return ok +} + +// ResetStripeID resets all changes to the "stripe_id" field. +func (m *PersonMutation) ResetStripeID() { + m.stripe_id = nil + delete(m.clearedFields, person.FieldStripeID) +} + // SetEmail sets the "email" field. func (m *PersonMutation) SetEmail(s string) { m.email = &s @@ -1995,6 +2046,42 @@ func (m *PersonMutation) ResetCountry() { delete(m.clearedFields, person.FieldCountry) } +// SetSubscribed sets the "subscribed" field. +func (m *PersonMutation) SetSubscribed(b bool) { + m.subscribed = &b +} + +// Subscribed returns the value of the "subscribed" field in the mutation. +func (m *PersonMutation) 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 Person entity. +// If the Person 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 *PersonMutation) 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 *PersonMutation) ResetSubscribed() { + m.subscribed = nil +} + // SetCreatedAt sets the "created_at" field. func (m *PersonMutation) SetCreatedAt(t time.Time) { m.created_at = &t @@ -2209,7 +2296,10 @@ func (m *PersonMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *PersonMutation) Fields() []string { - fields := make([]string, 0, 16) + fields := make([]string, 0, 18) + if m.stripe_id != nil { + fields = append(fields, person.FieldStripeID) + } if m.email != nil { fields = append(fields, person.FieldEmail) } @@ -2252,6 +2342,9 @@ func (m *PersonMutation) Fields() []string { if m.country != nil { fields = append(fields, person.FieldCountry) } + if m.subscribed != nil { + fields = append(fields, person.FieldSubscribed) + } if m.created_at != nil { fields = append(fields, person.FieldCreatedAt) } @@ -2266,6 +2359,8 @@ func (m *PersonMutation) Fields() []string { // schema. func (m *PersonMutation) Field(name string) (ent.Value, bool) { switch name { + case person.FieldStripeID: + return m.StripeID() case person.FieldEmail: return m.Email() case person.FieldEmailVerifiedAt: @@ -2294,6 +2389,8 @@ func (m *PersonMutation) Field(name string) (ent.Value, bool) { return m.City() case person.FieldCountry: return m.Country() + case person.FieldSubscribed: + return m.Subscribed() case person.FieldCreatedAt: return m.CreatedAt() case person.FieldUpdatedAt: @@ -2307,6 +2404,8 @@ func (m *PersonMutation) Field(name string) (ent.Value, bool) { // database failed. func (m *PersonMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { + case person.FieldStripeID: + return m.OldStripeID(ctx) case person.FieldEmail: return m.OldEmail(ctx) case person.FieldEmailVerifiedAt: @@ -2335,6 +2434,8 @@ func (m *PersonMutation) OldField(ctx context.Context, name string) (ent.Value, return m.OldCity(ctx) case person.FieldCountry: return m.OldCountry(ctx) + case person.FieldSubscribed: + return m.OldSubscribed(ctx) case person.FieldCreatedAt: return m.OldCreatedAt(ctx) case person.FieldUpdatedAt: @@ -2348,6 +2449,13 @@ func (m *PersonMutation) OldField(ctx context.Context, name string) (ent.Value, // type. func (m *PersonMutation) SetField(name string, value ent.Value) error { switch name { + case person.FieldStripeID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStripeID(v) + return nil case person.FieldEmail: v, ok := value.(string) if !ok { @@ -2446,6 +2554,13 @@ func (m *PersonMutation) SetField(name string, value ent.Value) error { } m.SetCountry(v) return nil + case person.FieldSubscribed: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSubscribed(v) + return nil case person.FieldCreatedAt: v, ok := value.(time.Time) if !ok { @@ -2490,6 +2605,9 @@ func (m *PersonMutation) AddField(name string, value ent.Value) error { // mutation. func (m *PersonMutation) ClearedFields() []string { var fields []string + if m.FieldCleared(person.FieldStripeID) { + fields = append(fields, person.FieldStripeID) + } if m.FieldCleared(person.FieldEmailVerifiedAt) { fields = append(fields, person.FieldEmailVerifiedAt) } @@ -2531,6 +2649,9 @@ func (m *PersonMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *PersonMutation) ClearField(name string) error { switch name { + case person.FieldStripeID: + m.ClearStripeID() + return nil case person.FieldEmailVerifiedAt: m.ClearEmailVerifiedAt() return nil @@ -2566,6 +2687,9 @@ func (m *PersonMutation) ClearField(name string) error { // It returns an error if the field is not defined in the schema. func (m *PersonMutation) ResetField(name string) error { switch name { + case person.FieldStripeID: + m.ResetStripeID() + return nil case person.FieldEmail: m.ResetEmail() return nil @@ -2608,6 +2732,9 @@ func (m *PersonMutation) ResetField(name string) error { case person.FieldCountry: m.ResetCountry() return nil + case person.FieldSubscribed: + m.ResetSubscribed() + return nil case person.FieldCreatedAt: m.ResetCreatedAt() return nil diff --git a/internal/generated/data/person.go b/internal/generated/data/person.go index 716abe7..83a344c 100755 --- a/internal/generated/data/person.go +++ b/internal/generated/data/person.go @@ -18,6 +18,8 @@ type Person struct { config `fake:"-" json:"-"` // ID of the ent. ID uuid.UUID `json:"id,omitempty"` + // StripeID holds the value of the "stripe_id" field. + StripeID *string `json:"stripe_id,omitempty"` // Email holds the value of the "email" field. Email string `json:"email,omitempty" fake:"{email}"` // EmailVerifiedAt holds the value of the "email_verified_at" field. @@ -46,6 +48,8 @@ type Person struct { City *string `json:"city,omitempty" fake:"{city}"` // Country holds the value of the "country" field. Country *string `json:"country,omitempty" fake:"{countryabr}"` + // Subscribed holds the value of the "subscribed" field. + Subscribed bool `json:"subscribed,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. @@ -95,7 +99,9 @@ func (*Person) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case person.FieldEmail, person.FieldPhone, person.FieldPassword, person.FieldTaxID, person.FieldFirstName, person.FieldLastName, person.FieldLanguage, person.FieldGender, person.FieldAddress, person.FieldPostalCode, person.FieldCity, person.FieldCountry: + case person.FieldSubscribed: + values[i] = new(sql.NullBool) + case person.FieldStripeID, person.FieldEmail, person.FieldPhone, person.FieldPassword, person.FieldTaxID, person.FieldFirstName, person.FieldLastName, person.FieldLanguage, person.FieldGender, person.FieldAddress, person.FieldPostalCode, person.FieldCity, person.FieldCountry: values[i] = new(sql.NullString) case person.FieldEmailVerifiedAt, person.FieldBirthdate, person.FieldCreatedAt, person.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -122,6 +128,13 @@ func (pe *Person) assignValues(columns []string, values []any) error { } else if value != nil { pe.ID = *value } + case person.FieldStripeID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field stripe_id", values[i]) + } else if value.Valid { + pe.StripeID = new(string) + *pe.StripeID = value.String + } case person.FieldEmail: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field email", values[i]) @@ -215,6 +228,12 @@ func (pe *Person) assignValues(columns []string, values []any) error { pe.Country = new(string) *pe.Country = value.String } + case person.FieldSubscribed: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field subscribed", values[i]) + } else if value.Valid { + pe.Subscribed = value.Bool + } case person.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) @@ -273,6 +292,11 @@ func (pe *Person) String() string { var builder strings.Builder builder.WriteString("Person(") builder.WriteString(fmt.Sprintf("id=%v, ", pe.ID)) + if v := pe.StripeID; v != nil { + builder.WriteString("stripe_id=") + builder.WriteString(*v) + } + builder.WriteString(", ") builder.WriteString("email=") builder.WriteString(pe.Email) builder.WriteString(", ") @@ -332,6 +356,9 @@ func (pe *Person) String() string { builder.WriteString(*v) } builder.WriteString(", ") + builder.WriteString("subscribed=") + builder.WriteString(fmt.Sprintf("%v", pe.Subscribed)) + builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(pe.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") diff --git a/internal/generated/data/person/person.go b/internal/generated/data/person/person.go index f3c3c4c..a86e818 100755 --- a/internal/generated/data/person/person.go +++ b/internal/generated/data/person/person.go @@ -18,6 +18,8 @@ const ( Label = "person" // FieldID holds the string denoting the id field in the database. FieldID = "id" + // FieldStripeID holds the string denoting the stripe_id field in the database. + FieldStripeID = "stripe_id" // FieldEmail holds the string denoting the email field in the database. FieldEmail = "email" // FieldEmailVerifiedAt holds the string denoting the email_verified_at field in the database. @@ -46,6 +48,8 @@ const ( FieldCity = "city" // FieldCountry holds the string denoting the country field in the database. FieldCountry = "country" + // FieldSubscribed holds the string denoting the subscribed field in the database. + FieldSubscribed = "subscribed" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldUpdatedAt holds the string denoting the updated_at field in the database. @@ -75,6 +79,7 @@ const ( // Columns holds all SQL columns for person fields. var Columns = []string{ FieldID, + FieldStripeID, FieldEmail, FieldEmailVerifiedAt, FieldPhone, @@ -89,6 +94,7 @@ var Columns = []string{ FieldPostalCode, FieldCity, FieldCountry, + FieldSubscribed, FieldCreatedAt, FieldUpdatedAt, } @@ -129,6 +135,8 @@ var ( CityValidator func(string) error // CountryValidator is a validator for the "country" field. It is called by the builders before save. CountryValidator func(string) error + // DefaultSubscribed holds the default value on creation for the "subscribed" field. + DefaultSubscribed bool // DefaultCreatedAt holds the default value on creation for the "created_at" field. DefaultCreatedAt func() time.Time // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. @@ -169,6 +177,11 @@ func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } +// ByStripeID orders the results by the stripe_id field. +func ByStripeID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldStripeID, opts...).ToFunc() +} + // ByEmail orders the results by the email field. func ByEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldEmail, opts...).ToFunc() @@ -239,6 +252,11 @@ func ByCountry(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCountry, opts...).ToFunc() } +// BySubscribed orders the results by the subscribed field. +func BySubscribed(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSubscribed, opts...).ToFunc() +} + // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() diff --git a/internal/generated/data/person/where.go b/internal/generated/data/person/where.go index 913201e..65830ad 100755 --- a/internal/generated/data/person/where.go +++ b/internal/generated/data/person/where.go @@ -56,6 +56,11 @@ func IDLTE(id uuid.UUID) predicate.Person { return predicate.Person(sql.FieldLTE(FieldID, id)) } +// StripeID applies equality check predicate on the "stripe_id" field. It's identical to StripeIDEQ. +func StripeID(v string) predicate.Person { + return predicate.Person(sql.FieldEQ(FieldStripeID, v)) +} + // Email applies equality check predicate on the "email" field. It's identical to EmailEQ. func Email(v string) predicate.Person { return predicate.Person(sql.FieldEQ(FieldEmail, v)) @@ -121,6 +126,11 @@ func Country(v string) predicate.Person { return predicate.Person(sql.FieldEQ(FieldCountry, v)) } +// Subscribed applies equality check predicate on the "subscribed" field. It's identical to SubscribedEQ. +func Subscribed(v bool) predicate.Person { + return predicate.Person(sql.FieldEQ(FieldSubscribed, v)) +} + // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.Person { return predicate.Person(sql.FieldEQ(FieldCreatedAt, v)) @@ -131,6 +141,81 @@ func UpdatedAt(v time.Time) predicate.Person { return predicate.Person(sql.FieldEQ(FieldUpdatedAt, v)) } +// StripeIDEQ applies the EQ predicate on the "stripe_id" field. +func StripeIDEQ(v string) predicate.Person { + return predicate.Person(sql.FieldEQ(FieldStripeID, v)) +} + +// StripeIDNEQ applies the NEQ predicate on the "stripe_id" field. +func StripeIDNEQ(v string) predicate.Person { + return predicate.Person(sql.FieldNEQ(FieldStripeID, v)) +} + +// StripeIDIn applies the In predicate on the "stripe_id" field. +func StripeIDIn(vs ...string) predicate.Person { + return predicate.Person(sql.FieldIn(FieldStripeID, vs...)) +} + +// StripeIDNotIn applies the NotIn predicate on the "stripe_id" field. +func StripeIDNotIn(vs ...string) predicate.Person { + return predicate.Person(sql.FieldNotIn(FieldStripeID, vs...)) +} + +// StripeIDGT applies the GT predicate on the "stripe_id" field. +func StripeIDGT(v string) predicate.Person { + return predicate.Person(sql.FieldGT(FieldStripeID, v)) +} + +// StripeIDGTE applies the GTE predicate on the "stripe_id" field. +func StripeIDGTE(v string) predicate.Person { + return predicate.Person(sql.FieldGTE(FieldStripeID, v)) +} + +// StripeIDLT applies the LT predicate on the "stripe_id" field. +func StripeIDLT(v string) predicate.Person { + return predicate.Person(sql.FieldLT(FieldStripeID, v)) +} + +// StripeIDLTE applies the LTE predicate on the "stripe_id" field. +func StripeIDLTE(v string) predicate.Person { + return predicate.Person(sql.FieldLTE(FieldStripeID, v)) +} + +// StripeIDContains applies the Contains predicate on the "stripe_id" field. +func StripeIDContains(v string) predicate.Person { + return predicate.Person(sql.FieldContains(FieldStripeID, v)) +} + +// StripeIDHasPrefix applies the HasPrefix predicate on the "stripe_id" field. +func StripeIDHasPrefix(v string) predicate.Person { + return predicate.Person(sql.FieldHasPrefix(FieldStripeID, v)) +} + +// StripeIDHasSuffix applies the HasSuffix predicate on the "stripe_id" field. +func StripeIDHasSuffix(v string) predicate.Person { + return predicate.Person(sql.FieldHasSuffix(FieldStripeID, v)) +} + +// StripeIDIsNil applies the IsNil predicate on the "stripe_id" field. +func StripeIDIsNil() predicate.Person { + return predicate.Person(sql.FieldIsNull(FieldStripeID)) +} + +// StripeIDNotNil applies the NotNil predicate on the "stripe_id" field. +func StripeIDNotNil() predicate.Person { + return predicate.Person(sql.FieldNotNull(FieldStripeID)) +} + +// StripeIDEqualFold applies the EqualFold predicate on the "stripe_id" field. +func StripeIDEqualFold(v string) predicate.Person { + return predicate.Person(sql.FieldEqualFold(FieldStripeID, v)) +} + +// StripeIDContainsFold applies the ContainsFold predicate on the "stripe_id" field. +func StripeIDContainsFold(v string) predicate.Person { + return predicate.Person(sql.FieldContainsFold(FieldStripeID, v)) +} + // EmailEQ applies the EQ predicate on the "email" field. func EmailEQ(v string) predicate.Person { return predicate.Person(sql.FieldEQ(FieldEmail, v)) @@ -1036,6 +1121,16 @@ func CountryContainsFold(v string) predicate.Person { return predicate.Person(sql.FieldContainsFold(FieldCountry, v)) } +// SubscribedEQ applies the EQ predicate on the "subscribed" field. +func SubscribedEQ(v bool) predicate.Person { + return predicate.Person(sql.FieldEQ(FieldSubscribed, v)) +} + +// SubscribedNEQ applies the NEQ predicate on the "subscribed" field. +func SubscribedNEQ(v bool) predicate.Person { + return predicate.Person(sql.FieldNEQ(FieldSubscribed, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.Person { return predicate.Person(sql.FieldEQ(FieldCreatedAt, v)) diff --git a/internal/generated/data/person_create.go b/internal/generated/data/person_create.go index c599b78..ad799d9 100755 --- a/internal/generated/data/person_create.go +++ b/internal/generated/data/person_create.go @@ -23,6 +23,20 @@ type PersonCreate struct { hooks []Hook } +// SetStripeID sets the "stripe_id" field. +func (pc *PersonCreate) SetStripeID(s string) *PersonCreate { + pc.mutation.SetStripeID(s) + return pc +} + +// SetNillableStripeID sets the "stripe_id" field if the given value is not nil. +func (pc *PersonCreate) SetNillableStripeID(s *string) *PersonCreate { + if s != nil { + pc.SetStripeID(*s) + } + return pc +} + // SetEmail sets the "email" field. func (pc *PersonCreate) SetEmail(s string) *PersonCreate { pc.mutation.SetEmail(s) @@ -179,6 +193,20 @@ func (pc *PersonCreate) SetNillableCountry(s *string) *PersonCreate { return pc } +// SetSubscribed sets the "subscribed" field. +func (pc *PersonCreate) SetSubscribed(b bool) *PersonCreate { + pc.mutation.SetSubscribed(b) + return pc +} + +// SetNillableSubscribed sets the "subscribed" field if the given value is not nil. +func (pc *PersonCreate) SetNillableSubscribed(b *bool) *PersonCreate { + if b != nil { + pc.SetSubscribed(*b) + } + return pc +} + // SetCreatedAt sets the "created_at" field. func (pc *PersonCreate) SetCreatedAt(t time.Time) *PersonCreate { pc.mutation.SetCreatedAt(t) @@ -280,6 +308,10 @@ func (pc *PersonCreate) ExecX(ctx context.Context) { // defaults sets the default values of the builder before save. func (pc *PersonCreate) defaults() error { + if _, ok := pc.mutation.Subscribed(); !ok { + v := person.DefaultSubscribed + pc.mutation.SetSubscribed(v) + } if _, ok := pc.mutation.CreatedAt(); !ok { if person.DefaultCreatedAt == nil { return fmt.Errorf("data: uninitialized person.DefaultCreatedAt (forgotten import data/runtime?)") @@ -364,6 +396,9 @@ func (pc *PersonCreate) check() error { return &ValidationError{Name: "country", err: fmt.Errorf(`data: validator failed for field "Person.country": %w`, err)} } } + if _, ok := pc.mutation.Subscribed(); !ok { + return &ValidationError{Name: "subscribed", err: errors.New(`data: missing required field "Person.subscribed"`)} + } if _, ok := pc.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`data: missing required field "Person.created_at"`)} } @@ -405,6 +440,10 @@ func (pc *PersonCreate) createSpec() (*Person, *sqlgraph.CreateSpec) { _node.ID = id _spec.ID.Value = &id } + if value, ok := pc.mutation.StripeID(); ok { + _spec.SetField(person.FieldStripeID, field.TypeString, value) + _node.StripeID = &value + } if value, ok := pc.mutation.Email(); ok { _spec.SetField(person.FieldEmail, field.TypeString, value) _node.Email = value @@ -461,6 +500,10 @@ func (pc *PersonCreate) createSpec() (*Person, *sqlgraph.CreateSpec) { _spec.SetField(person.FieldCountry, field.TypeString, value) _node.Country = &value } + if value, ok := pc.mutation.Subscribed(); ok { + _spec.SetField(person.FieldSubscribed, field.TypeBool, value) + _node.Subscribed = value + } if value, ok := pc.mutation.CreatedAt(); ok { _spec.SetField(person.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value diff --git a/internal/generated/data/person_query.go b/internal/generated/data/person_query.go index 2cd0592..9686182 100755 --- a/internal/generated/data/person_query.go +++ b/internal/generated/data/person_query.go @@ -340,12 +340,12 @@ func (pq *PersonQuery) WithAuthorizations(opts ...func(*AuthorizationQuery)) *Pe // Example: // // var v []struct { -// Email string `json:"email,omitempty" fake:"{email}"` +// StripeID string `json:"stripe_id,omitempty"` // Count int `json:"count,omitempty"` // } // // client.Person.Query(). -// GroupBy(person.FieldEmail). +// GroupBy(person.FieldStripeID). // Aggregate(data.Count()). // Scan(ctx, &v) func (pq *PersonQuery) GroupBy(field string, fields ...string) *PersonGroupBy { @@ -363,11 +363,11 @@ func (pq *PersonQuery) GroupBy(field string, fields ...string) *PersonGroupBy { // Example: // // var v []struct { -// Email string `json:"email,omitempty" fake:"{email}"` +// StripeID string `json:"stripe_id,omitempty"` // } // // client.Person.Query(). -// Select(person.FieldEmail). +// Select(person.FieldStripeID). // Scan(ctx, &v) func (pq *PersonQuery) Select(fields ...string) *PersonSelect { pq.ctx.Fields = append(pq.ctx.Fields, fields...) diff --git a/internal/generated/data/person_update.go b/internal/generated/data/person_update.go index 5384d26..149bd6a 100755 --- a/internal/generated/data/person_update.go +++ b/internal/generated/data/person_update.go @@ -31,6 +31,26 @@ func (pu *PersonUpdate) Where(ps ...predicate.Person) *PersonUpdate { return pu } +// SetStripeID sets the "stripe_id" field. +func (pu *PersonUpdate) SetStripeID(s string) *PersonUpdate { + pu.mutation.SetStripeID(s) + return pu +} + +// SetNillableStripeID sets the "stripe_id" field if the given value is not nil. +func (pu *PersonUpdate) SetNillableStripeID(s *string) *PersonUpdate { + if s != nil { + pu.SetStripeID(*s) + } + return pu +} + +// ClearStripeID clears the value of the "stripe_id" field. +func (pu *PersonUpdate) ClearStripeID() *PersonUpdate { + pu.mutation.ClearStripeID() + return pu +} + // SetEmail sets the "email" field. func (pu *PersonUpdate) SetEmail(s string) *PersonUpdate { pu.mutation.SetEmail(s) @@ -241,6 +261,20 @@ func (pu *PersonUpdate) ClearCountry() *PersonUpdate { return pu } +// SetSubscribed sets the "subscribed" field. +func (pu *PersonUpdate) SetSubscribed(b bool) *PersonUpdate { + pu.mutation.SetSubscribed(b) + return pu +} + +// SetNillableSubscribed sets the "subscribed" field if the given value is not nil. +func (pu *PersonUpdate) SetNillableSubscribed(b *bool) *PersonUpdate { + if b != nil { + pu.SetSubscribed(*b) + } + return pu +} + // SetUpdatedAt sets the "updated_at" field. func (pu *PersonUpdate) SetUpdatedAt(t time.Time) *PersonUpdate { pu.mutation.SetUpdatedAt(t) @@ -433,6 +467,12 @@ func (pu *PersonUpdate) sqlSave(ctx context.Context) (n int, err error) { } } } + if value, ok := pu.mutation.StripeID(); ok { + _spec.SetField(person.FieldStripeID, field.TypeString, value) + } + if pu.mutation.StripeIDCleared() { + _spec.ClearField(person.FieldStripeID, field.TypeString) + } if value, ok := pu.mutation.Email(); ok { _spec.SetField(person.FieldEmail, field.TypeString, value) } @@ -502,6 +542,9 @@ func (pu *PersonUpdate) sqlSave(ctx context.Context) (n int, err error) { if pu.mutation.CountryCleared() { _spec.ClearField(person.FieldCountry, field.TypeString) } + if value, ok := pu.mutation.Subscribed(); ok { + _spec.SetField(person.FieldSubscribed, field.TypeBool, value) + } if value, ok := pu.mutation.UpdatedAt(); ok { _spec.SetField(person.FieldUpdatedAt, field.TypeTime, value) } @@ -615,6 +658,26 @@ type PersonUpdateOne struct { mutation *PersonMutation } +// SetStripeID sets the "stripe_id" field. +func (puo *PersonUpdateOne) SetStripeID(s string) *PersonUpdateOne { + puo.mutation.SetStripeID(s) + return puo +} + +// SetNillableStripeID sets the "stripe_id" field if the given value is not nil. +func (puo *PersonUpdateOne) SetNillableStripeID(s *string) *PersonUpdateOne { + if s != nil { + puo.SetStripeID(*s) + } + return puo +} + +// ClearStripeID clears the value of the "stripe_id" field. +func (puo *PersonUpdateOne) ClearStripeID() *PersonUpdateOne { + puo.mutation.ClearStripeID() + return puo +} + // SetEmail sets the "email" field. func (puo *PersonUpdateOne) SetEmail(s string) *PersonUpdateOne { puo.mutation.SetEmail(s) @@ -825,6 +888,20 @@ func (puo *PersonUpdateOne) ClearCountry() *PersonUpdateOne { return puo } +// SetSubscribed sets the "subscribed" field. +func (puo *PersonUpdateOne) SetSubscribed(b bool) *PersonUpdateOne { + puo.mutation.SetSubscribed(b) + return puo +} + +// SetNillableSubscribed sets the "subscribed" field if the given value is not nil. +func (puo *PersonUpdateOne) SetNillableSubscribed(b *bool) *PersonUpdateOne { + if b != nil { + puo.SetSubscribed(*b) + } + return puo +} + // SetUpdatedAt sets the "updated_at" field. func (puo *PersonUpdateOne) SetUpdatedAt(t time.Time) *PersonUpdateOne { puo.mutation.SetUpdatedAt(t) @@ -1047,6 +1124,12 @@ func (puo *PersonUpdateOne) sqlSave(ctx context.Context) (_node *Person, err err } } } + if value, ok := puo.mutation.StripeID(); ok { + _spec.SetField(person.FieldStripeID, field.TypeString, value) + } + if puo.mutation.StripeIDCleared() { + _spec.ClearField(person.FieldStripeID, field.TypeString) + } if value, ok := puo.mutation.Email(); ok { _spec.SetField(person.FieldEmail, field.TypeString, value) } @@ -1116,6 +1199,9 @@ func (puo *PersonUpdateOne) sqlSave(ctx context.Context) (_node *Person, err err if puo.mutation.CountryCleared() { _spec.ClearField(person.FieldCountry, field.TypeString) } + if value, ok := puo.mutation.Subscribed(); ok { + _spec.SetField(person.FieldSubscribed, field.TypeBool, value) + } if value, ok := puo.mutation.UpdatedAt(); ok { _spec.SetField(person.FieldUpdatedAt, field.TypeTime, value) } diff --git a/internal/generated/data/runtime/runtime.go b/internal/generated/data/runtime/runtime.go index 487f507..ca30976 100755 --- a/internal/generated/data/runtime/runtime.go +++ b/internal/generated/data/runtime/runtime.go @@ -82,7 +82,7 @@ func init() { personFields := schema.Person{}.Fields() _ = personFields // personDescEmail is the schema descriptor for email field. - personDescEmail := personFields[1].Descriptor() + personDescEmail := personFields[2].Descriptor() // person.EmailValidator is a validator for the "email" field. It is called by the builders before save. person.EmailValidator = func() func(string) error { validators := personDescEmail.Validators @@ -100,7 +100,7 @@ func init() { } }() // personDescPhone is the schema descriptor for phone field. - personDescPhone := personFields[3].Descriptor() + personDescPhone := personFields[4].Descriptor() // person.PhoneValidator is a validator for the "phone" field. It is called by the builders before save. person.PhoneValidator = func() func(string) error { validators := personDescPhone.Validators @@ -118,7 +118,7 @@ func init() { } }() // personDescTaxID is the schema descriptor for tax_id field. - personDescTaxID := personFields[5].Descriptor() + personDescTaxID := personFields[6].Descriptor() // person.TaxIDValidator is a validator for the "tax_id" field. It is called by the builders before save. person.TaxIDValidator = func() func(string) error { validators := personDescTaxID.Validators @@ -136,7 +136,7 @@ func init() { } }() // personDescFirstName is the schema descriptor for first_name field. - personDescFirstName := personFields[6].Descriptor() + personDescFirstName := personFields[7].Descriptor() // person.FirstNameValidator is a validator for the "first_name" field. It is called by the builders before save. person.FirstNameValidator = func() func(string) error { validators := personDescFirstName.Validators @@ -154,7 +154,7 @@ func init() { } }() // personDescLastName is the schema descriptor for last_name field. - personDescLastName := personFields[7].Descriptor() + personDescLastName := personFields[8].Descriptor() // person.LastNameValidator is a validator for the "last_name" field. It is called by the builders before save. person.LastNameValidator = func() func(string) error { validators := personDescLastName.Validators @@ -172,7 +172,7 @@ func init() { } }() // personDescAddress is the schema descriptor for address field. - personDescAddress := personFields[11].Descriptor() + personDescAddress := personFields[12].Descriptor() // person.AddressValidator is a validator for the "address" field. It is called by the builders before save. person.AddressValidator = func() func(string) error { validators := personDescAddress.Validators @@ -190,7 +190,7 @@ func init() { } }() // personDescPostalCode is the schema descriptor for postal_code field. - personDescPostalCode := personFields[12].Descriptor() + personDescPostalCode := personFields[13].Descriptor() // person.PostalCodeValidator is a validator for the "postal_code" field. It is called by the builders before save. person.PostalCodeValidator = func() func(string) error { validators := personDescPostalCode.Validators @@ -208,7 +208,7 @@ func init() { } }() // personDescCity is the schema descriptor for city field. - personDescCity := personFields[13].Descriptor() + personDescCity := personFields[14].Descriptor() // person.CityValidator is a validator for the "city" field. It is called by the builders before save. person.CityValidator = func() func(string) error { validators := personDescCity.Validators @@ -226,15 +226,19 @@ func init() { } }() // personDescCountry is the schema descriptor for country field. - personDescCountry := personFields[14].Descriptor() + personDescCountry := personFields[15].Descriptor() // person.CountryValidator is a validator for the "country" field. It is called by the builders before save. person.CountryValidator = personDescCountry.Validators[0].(func(string) error) + // personDescSubscribed is the schema descriptor for subscribed field. + personDescSubscribed := personFields[16].Descriptor() + // person.DefaultSubscribed holds the default value on creation for the subscribed field. + person.DefaultSubscribed = personDescSubscribed.Default.(bool) // personDescCreatedAt is the schema descriptor for created_at field. - personDescCreatedAt := personFields[15].Descriptor() + personDescCreatedAt := personFields[17].Descriptor() // person.DefaultCreatedAt holds the default value on creation for the created_at field. person.DefaultCreatedAt = personDescCreatedAt.Default.(func() time.Time) // personDescUpdatedAt is the schema descriptor for updated_at field. - personDescUpdatedAt := personFields[16].Descriptor() + personDescUpdatedAt := personFields[18].Descriptor() // person.DefaultUpdatedAt holds the default value on creation for the updated_at field. person.DefaultUpdatedAt = personDescUpdatedAt.Default.(func() time.Time) // person.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. diff --git a/internal/services/biller.go b/internal/services/biller.go new file mode 100755 index 0000000..86d56b7 --- /dev/null +++ b/internal/services/biller.go @@ -0,0 +1,20 @@ +package services + +import ( + "github.com/avptp/brain/internal/billing" + "github.com/avptp/brain/internal/config" + "github.com/sarulabs/di/v2" + "github.com/sarulabs/dingo/v4" +) + +const Biller = "biller" + +var BillerDef = dingo.Def{ + Name: Biller, + Scope: di.App, + Build: func(cfg *config.Config) (billing.Biller, error) { + return billing.NewBiller( + cfg, + ), nil + }, +} diff --git a/internal/services/provider/main.go b/internal/services/provider/main.go index 8dda91c..c7b2ae6 100644 --- a/internal/services/provider/main.go +++ b/internal/services/provider/main.go @@ -11,6 +11,7 @@ type Provider struct { func (p *Provider) Load() error { return p.AddDefSlice([]dingo.Def{ + services.BillerDef, services.CaptchaDef, services.ConfigDef, services.DataDef, diff --git a/internal/services/resolver.go b/internal/services/resolver.go index 3eb6794..a022bc4 100755 --- a/internal/services/resolver.go +++ b/internal/services/resolver.go @@ -3,6 +3,7 @@ package services import ( "github.com/avptp/brain/internal/api/resolvers" "github.com/avptp/brain/internal/auth" + "github.com/avptp/brain/internal/billing" "github.com/avptp/brain/internal/config" "github.com/avptp/brain/internal/generated/data" "github.com/avptp/brain/internal/messaging" @@ -17,6 +18,7 @@ var ResolverDef = dingo.Def{ Name: Resolver, Scope: di.App, Build: func( + biller billing.Biller, captcha auth.Captcha, cfg *config.Config, data *data.Client, @@ -24,6 +26,7 @@ var ResolverDef = dingo.Def{ messenger messaging.Messenger, ) (*resolvers.Resolver, error) { return resolvers.NewResolver( + biller, captcha, cfg, data,