A accounts/processors.go => accounts/processors.go +58 -0
@@ 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())
+ }
+ })
+}
M accounts/routes.go => accounts/routes.go +10 -9
@@ 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)
M accounts/userfetch.go => accounts/userfetch.go +7 -0
@@ 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
}
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +18 -1
@@ 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
}
M config.example.ini => config.example.ini +6 -0
@@ 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
M go.mod => go.mod +1 -0
@@ 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
M go.sum => go.sum +2 -0
@@ 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=
M models/user.go => models/user.go +12 -0
@@ 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
+}