~netlandish/links

0ec859a422a2b1aec44329b619a76ae9593cc6ca — Peter Sanchez a month ago 34e72bd
Adding support for Sendy integration
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
}