Received: from mail.netlandish.com (mail.netlandish.com [174.136.98.166])
	by code.netlandish.com (Postfix) with ESMTP id CE63C1ED
	for <~netlandish/links-dev@lists.code.netlandish.com>; Thu, 30 Jan 2025 12:42:04 +0000 (UTC)
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.221.169; helo=mail-vk1-f169.google.com; envelope-from=peter@netlandish.com; receiver=<UNKNOWN> 
Authentication-Results: mail.netlandish.com;
	dkim=pass (1024-bit key; unprotected) header.d=netlandish.com header.i=@netlandish.com header.b=mJj0FwFM
Received: from mail-vk1-f169.google.com (mail-vk1-f169.google.com [209.85.221.169])
	by mail.netlandish.com (Postfix) with ESMTP id 605E11D80BE
	for <~netlandish/links-dev@lists.code.netlandish.com>; Thu, 30 Jan 2025 12:48:48 +0000 (UTC)
Received: by mail-vk1-f169.google.com with SMTP id 71dfb90a1353d-5188b485988so242131e0c.3
        for <~netlandish/links-dev@lists.code.netlandish.com>; Thu, 30 Jan 2025 04:48:48 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=netlandish.com; s=google; t=1738241327; x=1738846127; darn=lists.code.netlandish.com;
        h=content-transfer-encoding:mime-version:message-id:date:subject:cc
         :to:from:from:to:cc:subject:date:message-id:reply-to;
        bh=Kg020kf4wTkOB9HvE8iKq0S5IS3EZnin5vZ5xw1sIH0=;
        b=mJj0FwFMRz8sKXhCcn+WCksVvSyhQ3GGoVLitgkFRIO+vetAMXmaasyOCHL01V/B8n
         JIASqUIPrUGm2G3emA4yv8SQgIg0c+EFcKQSVcmk4sEXW5tOnL9WkXG2IO+LC3YqXKN3
         rs+/eUsqyOKfvSkJSykhO6iQpbt5VLJpZbZww=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20230601; t=1738241327; x=1738846127;
        h=content-transfer-encoding:mime-version:message-id:date:subject:cc
         :to:from:x-gm-message-state:from:to:cc:subject:date:message-id
         :reply-to;
        bh=Kg020kf4wTkOB9HvE8iKq0S5IS3EZnin5vZ5xw1sIH0=;
        b=kqLMfS6fhk5j7FTTWzkll36NIrAZ+j9NczEi2GK980HLltHWAR2gKx+To5XRTiPz3V
         LnJBpHp2WQMHFfCQNe621qwGA7e+F/ar7ood1F1lpUdTiewLBxEZt/bWCsYbXGyuf/tK
         YioPcWwL+vDg76tBstrR0LuXlaqC//hENh0V636uaTL6vJARP31pEwXOnYD4Npq6AB+F
         5IaEKpv7+DpunCqTSXiRMUGHNg0S7CLxVcD2VcwGSxvGzpo6Iy7uY/xJuNQWqpv2uUJq
         bQRKSZP5gGa8eHDXnkm2K9TguPNXVxvRz2EaUt+fG1EEi+xl2wwMLAGMX1v40N1m7qkf
         v4kQ==
X-Gm-Message-State: AOJu0Yz27AhAP2QjYbmVA42GnjA707uuExBeUQEd+Pl1qd3fMGF57HRL
	VyBEmwQjwFtMDoQ8/wypMo9VGYzk/ncwaureAvf2yfVpoK13k7fhSCGIAsY/JhSdw5rlxPkzsVL
	vxwM=
X-Gm-Gg: ASbGncsyTrr6WpmHCI/lx8lZdR5Pcfinhsf7JIRmxu5s50pnWFkcsSb/PbslIyhJsss
	Vtra6jz2tnNyTmtA6TeU4iclnux8B8p7Bu6U/wfT60qfxwYfmdLcogCUshM3OQYz2zrj/0XDB2e
	+7KOW6aDE46XQl73WB3A3mlS/xOtZ84zPWS4VjOtfirDs1WZIOxn60WrkBhWlawTsNZYmEwSlW5
	avAJ8u+I+GrPaaP5pcMQYxNiP0/GTyirT7TIyLkUV6pMKFw9NejWZq5Vm76WaEGwUwRj0Tan1I+
	Y6RkB+5GJ/XIsh7a
X-Google-Smtp-Source: AGHT+IHSThpquRPohyqiH5hcrf+g0EkfqcJoxVu3+gzBmSmtMNSAhYpjRi71AtRV1IxDXt96wZyTgw==
X-Received: by 2002:a05:6102:1941:b0:4b2:5c0a:98b7 with SMTP id ada2fe7eead31-4b9a4f1ca1cmr4739636137.6.1738241327179;
        Thu, 30 Jan 2025 04:48:47 -0800 (PST)
Received: from localhost ([2803:2d60:1107:87f:f76d:3508:72c4:c178])
        by smtp.gmail.com with ESMTPSA id ada2fe7eead31-4b9baa642a6sm217807137.8.2025.01.30.04.48.45
        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
        Thu, 30 Jan 2025 04:48:46 -0800 (PST)
From: Peter Sanchez <peter@netlandish.com>
To: ~netlandish/links-dev@lists.code.netlandish.com
Cc: Peter Sanchez <peter@netlandish.com>
Subject: [PATCH links] Added support to specify which Sendy list-id to integrate with depending on the situation.
Date: Thu, 30 Jan 2025 06:47:43 -0600
Message-ID: <20250130124831.9627-1-peter@netlandish.com>
X-Mailer: git-send-email 2.47.2
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit

Changelog-added: Specify Sendy list-id when integrating per situation
---
Also includes some misc cleanup done while working on this.

 accounts/processors.go        | 11 +++++++----
 accounts/userfetch.go         |  6 +++---
 api/graph/schema.resolvers.go | 17 +++++++++--------
 billing/processors.go         | 14 ++++++++++++++
 billing/routes_test.go        |  3 ++-
 config.example.ini            |  1 +
 6 files changed, 36 insertions(+), 16 deletions(-)

diff --git a/accounts/processors.go b/accounts/processors.go
index 8aa0fa4..b78e3ee 100644
--- a/accounts/processors.go
+++ b/accounts/processors.go
@@ -99,12 +99,12 @@ func AddWelcomeLinkTask(user gobwebs.User, gctx *server.Context) *work.Task {
 	})
 }
 
-func sendSendySubscribe(ctx context.Context, user gobwebs.User, gctx *server.Context) error {
+func sendSendySubscribe(ctx context.Context, user gobwebs.User, gctx *server.Context, listid string) 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")
+	list, ok3 := file.Get("sendy", listid)
 	if !ok1 || !ok2 || !ok3 {
 		gctx.Server.Logger().Printf("No Sendy config present. Failing silently")
 		return nil
@@ -124,9 +124,12 @@ func sendSendySubscribe(ctx context.Context, user gobwebs.User, gctx *server.Con
 	return nil
 }
 
-func SendySubscribeTask(user gobwebs.User, gctx *server.Context) *work.Task {
+func SendySubscribeTask(user gobwebs.User, gctx *server.Context, listid string) *work.Task {
 	return work.NewTask(func(ctx context.Context) error {
-		return sendSendySubscribe(ctx, user, gctx)
+		if listid == "" {
+			listid = "list-id"
+		}
+		return sendSendySubscribe(ctx, user, gctx, listid)
 	}).Retries(3).Before(func(ctx context.Context, task *work.Task) {
 		gobwebs.TaskIDWork(task)
 		gctx.Server.Logger().Printf(
diff --git a/accounts/userfetch.go b/accounts/userfetch.go
index c368bfd..6b8bba4 100644
--- a/accounts/userfetch.go
+++ b/accounts/userfetch.go
@@ -176,7 +176,7 @@ func (u *UserFetch) ProcessSuccessfulEmailConfirmation(c echo.Context) error {
 	}
 
 	// Sendy
-	err = gctx.Server.QueueTask("general", SendySubscribeTask(user, gctx))
+	err = gctx.Server.QueueTask("general", SendySubscribeTask(user, gctx, ""))
 	if err != nil {
 		gctx.Server.Logger().Printf("Error queueing SendySubscribeTask: %v", err)
 	}
@@ -219,9 +219,9 @@ func (u *UserFetch) ProcessLogin(c echo.Context, user gobwebs.User) error {
 		if err != nil {
 			return err
 		}
-		return fmt.Errorf(lt.Translate("You must verify your email address before logging in"))
+		return fmt.Errorf("%s", lt.Translate("You must verify your email address before logging in"))
 	} else if linkUser.IsLocked {
-		return fmt.Errorf(lt.Translate("This account is currently unable to access the system."))
+		return fmt.Errorf("%s", lt.Translate("This account is currently unable to access the system."))
 	}
 	return nil
 }
diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index 2a3a255..9f02533 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -1286,7 +1286,7 @@ func (r *mutationResolver) Register(ctx context.Context, input *model.RegisterIn
 		// Sendy users list. Log on error
 		srv := server.ForContext(ctx)
 		gctx := c.(*server.Context)
-		err = srv.QueueTask("general", accounts.SendySubscribeTask(user, gctx))
+		err = srv.QueueTask("general", accounts.SendySubscribeTask(user, gctx, ""))
 		if err != nil {
 			gctx.Server.Logger().Printf("Error queueing sendSendySubscribeTask: %v", err)
 		}
@@ -1477,7 +1477,7 @@ func (r *mutationResolver) CompleteRegister(ctx context.Context, input *model.Co
 	srv := server.ForContext(ctx)
 	c := server.EchoForContext(ctx)
 	gctx := c.(*server.Context)
-	err = srv.QueueTask("general", accounts.SendySubscribeTask(user, gctx))
+	err = srv.QueueTask("general", accounts.SendySubscribeTask(user, gctx, ""))
 	if err != nil {
 		gctx.Server.Logger().Printf("Error queueing sendSendySubscribeTask: %v", err)
 	}
@@ -6238,6 +6238,13 @@ func (r *queryResolver) GetUsers(ctx context.Context, input *model.GetUserInput)
 		return nil, nil
 
 	}
+
+	if input.After != nil && input.Before != nil {
+		validator.Error("%s", lt.Translate("You can not send both after and before cursors")).
+			WithCode(valid.ErrValidationGlobalCode)
+		return nil, nil
+	}
+
 	opts := &database.FilterOptions{
 		Filter: sq.And{
 			sq.Eq{"o.org_type": models.OrgTypeUser},
@@ -6256,12 +6263,6 @@ func (r *queryResolver) GetUsers(ctx context.Context, input *model.GetUserInput)
 
 	}
 
-	if input.After != nil && input.Before != nil {
-		validator.Error("%s", lt.Translate("You can not send both after and before cursors")).
-			WithCode(valid.ErrValidationGlobalCode)
-		return nil, nil
-	}
-
 	numElements := model.PaginationDefault
 	var hasPrevPage bool
 	var hasNextPage bool
diff --git a/billing/processors.go b/billing/processors.go
index bdc18f8..c75448c 100644
--- a/billing/processors.go
+++ b/billing/processors.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"links"
+	"links/accounts"
 	"links/internal/localizer"
 	"links/models"
 	"strings"
@@ -37,6 +38,7 @@ func ProcessCheckoutSessionCompletedTask(ctx context.Context, conf *config.Confi
 	}
 	org := orgs[0]
 
+	srv := server.ForContext(ctx)
 	stripeClient, err := GetStripeClient(conf)
 	if err != nil {
 		return err
@@ -135,6 +137,17 @@ func ProcessCheckoutSessionCompletedTask(ctx context.Context, conf *config.Confi
 		return err
 	}
 
+	// Fetch user and add their email to Sendy paid user list. Ignore error fetching user
+	user, err := models.GetUser(ctx, org.OwnerID, false)
+	if err == nil {
+		gctx := &server.Context{
+			Server: srv,
+			User:   user,
+		}
+		// Again, ignore errors here.
+		srv.QueueTask("general", accounts.SendySubscribeTask(user, gctx, "paid-list-id"))
+	}
+
 	return nil
 }
 
@@ -142,6 +155,7 @@ func CheckoutSessionCompletedTask(srv *server.Server, data map[string]any) *work
 	return work.NewTask(func(ctx context.Context) error {
 		ctx = database.Context(ctx, srv.DB)
 		ctx = timezone.Context(ctx, "UTC")
+		ctx = server.ServerContext(ctx, srv)
 		return ProcessCheckoutSessionCompletedTask(ctx, srv.Config, data)
 	}).Retries(3).Before(func(ctx context.Context, task *work.Task) {
 		gobwebs.TaskIDWork(task)
diff --git a/billing/routes_test.go b/billing/routes_test.go
index 70d73d6..3082be4 100644
--- a/billing/routes_test.go
+++ b/billing/routes_test.go
@@ -286,7 +286,8 @@ func TestWebhooks(t *testing.T) {
 		httpmock.RegisterResponder("GET", "https://api.stripe.com/v1/subscriptions/stripe_subscription_id_test",
 			httpmock.NewStringResponder(200, string(subscriptionsBody)))
 
-		err = billing.ProcessCheckoutSessionCompletedTask(dbCtx, srv.Config, dataMap)
+		srvCtx := server.ServerContext(dbCtx, srv)
+		err = billing.ProcessCheckoutSessionCompletedTask(srvCtx, srv.Config, dataMap)
 		c.NoError(err)
 
 		opts := &database.FilterOptions{
diff --git a/config.example.ini b/config.example.ini
index caa93e7..7d56af8 100644
--- a/config.example.ini
+++ b/config.example.ini
@@ -222,3 +222,4 @@ enabled=true
 api-key=API Key for Sendy
 api-url=https://yoursendydomain.com
 list-id=Sendy List ID
+paid-list-id=Sendy Paid User List ID
-- 
2.47.2

