From 0ec859a422a2b1aec44329b619a76ae9593cc6ca Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Fri, 20 Sep 2024 08:13:45 -0600 Subject: [PATCH] Adding support for Sendy integration --- accounts/processors.go | 58 +++++++++++++++++++++++++++++++++++ accounts/routes.go | 19 ++++++------ accounts/userfetch.go | 7 +++++ api/graph/schema.resolvers.go | 19 +++++++++++- config.example.ini | 6 ++++ go.mod | 1 + go.sum | 2 ++ models/user.go | 12 ++++++++ 8 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 accounts/processors.go diff --git a/accounts/processors.go b/accounts/processors.go new file mode 100644 index 0000000..b00e9cf --- /dev/null +++ b/accounts/processors.go @@ -0,0 +1,58 @@ +package accounts + +import ( + "context" + "links/models" + "strings" + + work "git.sr.ht/~sircmpwn/dowork" + sendy "hg.code.netlandish.com/~netlandish/sendygo" + "netlandish.com/x/gobwebs" + "netlandish.com/x/gobwebs/server" +) + +func sendSendySubscribe(ctx context.Context, user gobwebs.User, gctx *server.Context) error { + u := user.(*models.User) + file := gctx.Server.Config.File + key, ok1 := file.Get("sendy", "api-key") + url, ok2 := file.Get("sendy", "api-url") + list, ok3 := file.Get("sendy", "list-id") + if !ok1 || !ok2 || !ok3 { + gctx.Server.Logger().Printf("No Sendy config present. Failing silently") + return nil + } + client := sendy.NewClient(url, key, gctx.Server.Config.Debug) + err := client.Subscribe(sendy.SusbcribeParams{ + List: list, + Email: user.GetEmail(), + Name: u.GetFirstName(), + GDPR: true, + }) + if err != nil { + if !strings.Contains(err.Error(), "Already subscribed") { + return err + } + } + return nil +} + +func SendySubscribeTask(user gobwebs.User, gctx *server.Context) *work.Task { + return work.NewTask(func(ctx context.Context) error { + return sendSendySubscribe(ctx, user, gctx) + }).Retries(3).Before(func(ctx context.Context, task *work.Task) { + gobwebs.TaskIDWork(task) + gctx.Server.Logger().Printf( + "Running task sendSendySubscribeTask %s for the %d attempt.", + task.Metadata["id"], task.Attempts()) + }).After(func(ctx context.Context, task *work.Task) { + if task.Result() == nil { + gctx.Server.Logger().Printf( + "Completed task sendSendySubscribeTask %s after %d attempts.", + task.Metadata["id"], task.Attempts()) + } else { + gctx.Server.Logger().Printf( + "Failed task sendSendySubscribeTask %s after %d attempts: %v", + task.Metadata["id"], task.Attempts(), task.Result()) + } + }) +} diff --git a/accounts/routes.go b/accounts/routes.go index 560c073..94351fa 100644 --- a/accounts/routes.go +++ b/accounts/routes.go @@ -264,6 +264,16 @@ func (s *Service) CompleteRegister(c echo.Context) error { if key == "" { return echo.NotFoundHandler(c) } + + var conf *gaccounts.Confirmation + conf, err := gaccounts.GetConfirmation(c.Request().Context(), key) + if err != nil { + if err == sql.ErrNoRows { + return echo.NotFoundHandler(c) + } + return err + } + gctx := c.(*server.Context) lt := localizer.GetSessionLocalizer(c) pd := localizer.NewPageData(lt.Translate("Complete Registration")) @@ -299,15 +309,6 @@ func (s *Service) CompleteRegister(c echo.Context) error { } } - // We get the user from the confirmation - conf, err := gaccounts.GetConfirmation(c.Request().Context(), key) - if err != nil { - if err == sql.ErrNoRows { - return echo.NotFoundHandler(c) - } - return err - } - user, err := models.GetUser(c.Request().Context(), conf.UserID, true) if err != nil { return echo.NotFoundHandler(c) diff --git a/accounts/userfetch.go b/accounts/userfetch.go index 58136eb..1f9355c 100644 --- a/accounts/userfetch.go +++ b/accounts/userfetch.go @@ -165,6 +165,13 @@ func (u *UserFetch) ProcessSuccessfulEmailUpdate(c echo.Context) error { // ProcessSuccessfulEmailConfirmation handle tasks after user is logged in func (u *UserFetch) ProcessSuccessfulEmailConfirmation(c echo.Context) error { lt := localizer.GetSessionLocalizer(c) + // Sendy users list. Log on error + user := c.Get("user").(*models.User) + gctx := c.(*server.Context) + err := gctx.Server.QueueTask("general", SendySubscribeTask(user, gctx)) + if err != nil { + gctx.Server.Logger().Printf("Error queueing sendSendySubscribeTask: %v", err) + } messages.Success(c, lt.Translate("You've successfully confirmed your email address.")) return nil } diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 4ab9927..a6d4304 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -40,7 +40,7 @@ import ( "golang.org/x/image/draw" "golang.org/x/net/idna" "netlandish.com/x/gobwebs" - "netlandish.com/x/gobwebs-oauth2" + oauth2 "netlandish.com/x/gobwebs-oauth2" gaccounts "netlandish.com/x/gobwebs/accounts" "netlandish.com/x/gobwebs/crypto" "netlandish.com/x/gobwebs/database" @@ -1246,6 +1246,14 @@ func (r *mutationResolver) Register(ctx context.Context, input *model.RegisterIn if err != nil { return nil, err } + + // Sendy users list. Log on error + srv := server.ForContext(ctx) + gctx := c.(*server.Context) + err = srv.QueueTask("general", accounts.SendySubscribeTask(user, gctx)) + if err != nil { + gctx.Server.Logger().Printf("Error queueing sendSendySubscribeTask: %v", err) + } } else { conf := gaccounts.NewConfirmation( gaccounts.EMAILCONF, user, time.Now().In(time.UTC).Add(time.Hour*24)) @@ -1417,6 +1425,15 @@ func (r *mutationResolver) CompleteRegister(ctx context.Context, input *model.Co if err != nil { return nil, err } + + // Sendy users list. Log on error + srv := server.ForContext(ctx) + c := server.EchoForContext(ctx) + gctx := c.(*server.Context) + err = srv.QueueTask("general", accounts.SendySubscribeTask(user, gctx)) + if err != nil { + gctx.Server.Logger().Printf("Error queueing sendSendySubscribeTask: %v", err) + } return user, nil } diff --git a/config.example.ini b/config.example.ini index 9bd402a..8b591d3 100644 --- a/config.example.ini +++ b/config.example.ini @@ -210,3 +210,9 @@ enabled=false [registration] enabled=true + +# Sendy integration +[sendy] +api-key=API Key for Sendy +api-url=https://yoursendydomain.com +list-id=Sendy List ID diff --git a/go.mod b/go.mod index 6b3ca66..efb151e 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( golang.org/x/net v0.26.0 golang.org/x/text v0.16.0 golang.org/x/time v0.5.0 + hg.code.netlandish.com/~netlandish/sendygo v0.0.0-20230124192435-bbf347776232 netlandish.com/x/gobwebs v0.0.0-20240828125517-6fc18f8bc093 netlandish.com/x/gobwebs-formguard v0.0.0-20240917220134-4b6f2ec7d1f5 netlandish.com/x/gobwebs-graphql v0.0.0-20240827211219-adfac6aa2e95 diff --git a/go.sum b/go.sum index 2101b9b..ee70c2f 100644 --- a/go.sum +++ b/go.sum @@ -2420,6 +2420,8 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +hg.code.netlandish.com/~netlandish/sendygo v0.0.0-20230124192435-bbf347776232 h1:SGP+LuCWLRf2gl0lE3GsnTcTvWCoKEriBAIz/167EbE= +hg.code.netlandish.com/~netlandish/sendygo v0.0.0-20230124192435-bbf347776232/go.mod h1:ny9djyPdFFkLQHQ1izlSPUOn7ec/W1w5Wguts3cvLHQ= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/models/user.go b/models/user.go index ab69ecc..51f4dda 100644 --- a/models/user.go +++ b/models/user.go @@ -293,3 +293,15 @@ func (u *User) ToLocalTZ(tz string) error { } return nil } + +// GetFirstName will attempt to return the user's first name +func (u *User) GetFirstName() string { + var name string + parts := strings.Split(u.Name, " ") + if len(parts) == 0 { + name = "" + } else { + name = parts[0] + } + return name +} -- 2.45.2