~netlandish/links

03aaf413b83c65527a2291a6c2a4b1da9e1fa38d — Peter Sanchez 22 days ago a1dc8a4
Moving the welcome link to a async task.
2 files changed, 99 insertions(+), 63 deletions(-)

M accounts/processors.go
M accounts/userfetch.go
M accounts/processors.go => accounts/processors.go +88 -0
@@ 2,15 2,103 @@ package accounts

import (
	"context"
	"fmt"
	"links"
	"links/models"
	"strings"

	"git.sr.ht/~emersion/gqlclient"
	work "git.sr.ht/~sircmpwn/dowork"
	sq "github.com/Masterminds/squirrel"
	sendy "hg.code.netlandish.com/~netlandish/sendygo"
	"netlandish.com/x/gobwebs"
	"netlandish.com/x/gobwebs/crypto"
	"netlandish.com/x/gobwebs/database"
	"netlandish.com/x/gobwebs/server"
	"netlandish.com/x/gobwebs/timezone"
)

func addWelcomeLink(ctx context.Context, user gobwebs.User) error {
	opts := &database.FilterOptions{
		Filter: sq.And{
			sq.Eq{"o.owner_id": user.GetID()},
			sq.Eq{"o.org_type": models.OrgTypeUser},
		},
		Limit: 1,
	}
	orgs, err := models.GetOrganizations(ctx, opts)
	if err != nil {
		return err
	}
	if len(orgs) == 0 {
		// Should never be reached
		return fmt.Errorf("User has no organizations")
	}

	type GraphQLResponse struct {
		Link models.OrgLink `json:"addLink"`
	}

	var result GraphQLResponse
	q := `mutation AddLink($title: String!, $url: String!, $description: String,
				$visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!,
				$archive: Boolean! $slug: String!, $tags: String, $override: Boolean!) {
					addLink(input: {
								title: $title,
								url: $url,
								description: $description,
								visibility: $visibility,
								unread: $unread,
								starred: $starred,
								archive: $archive,
								orgSlug: $slug,
								tags: $tags,
								override: $override}) {
						id
					}
				}`
	op := gqlclient.NewOperation(q)
	op.Var("title", "Welcome to LinkTaco! Getting Started Guide")
	op.Var("url", "https://man.code.netlandish.com/~netlandish/links/getting-started.md")
	op.Var("description", "Welcome! This is your getting started guide for LinkTaco. Please click the link to read it and start saving your links right away!")
	op.Var("visibility", models.OrgLinkVisibilityPrivate)
	op.Var("tags", "help, linktaco, documentation, getting started")
	op.Var("unread", true)
	op.Var("starred", true)
	op.Var("archive", false)
	op.Var("slug", orgs[0].Slug)
	op.Var("override", true)

	return links.Execute(ctx, op, &result)
}

func AddWelcomeLinkTask(user gobwebs.User, gctx *server.Context) *work.Task {
	return work.NewTask(func(ctx context.Context) error {
		// If ever there was a use case for context merge functionality
		// https://github.com/golang/go/issues/36503
		ctx = crypto.Context(ctx, crypto.ForContext(gctx.Request().Context()))
		ctx = server.ServerContext(ctx, gctx.Server)
		ctx = database.Context(ctx, gctx.Server.DB)
		ctx = timezone.Context(ctx, "UTC")
		return addWelcomeLink(ctx, user)
	}).Retries(3).Before(func(ctx context.Context, task *work.Task) {
		gobwebs.TaskIDWork(task)
		gctx.Server.Logger().Printf(
			"Running task AddWelcomeLinkTask %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 AddWelcomeLinkTask %s after %d attempts.",
				task.Metadata["id"], task.Attempts())
		} else {
			gctx.Server.Logger().Printf(
				"Failed task AddWelcomeLinkTask %s after %d attempts: %v",
				task.Metadata["id"], task.Attempts(), task.Result())
		}
	})
}

func sendSendySubscribe(ctx context.Context, user gobwebs.User, gctx *server.Context) error {
	u := user.(*models.User)
	file := gctx.Server.Config.File

M accounts/userfetch.go => accounts/userfetch.go +11 -63
@@ 7,11 7,8 @@ import (
	"links/internal/localizer"
	"links/models"

	"git.sr.ht/~emersion/gqlclient"
	sq "github.com/Masterminds/squirrel"
	"github.com/labstack/echo/v4"
	"netlandish.com/x/gobwebs"
	"netlandish.com/x/gobwebs/database"
	"netlandish.com/x/gobwebs/messages"
	"netlandish.com/x/gobwebs/server"
)


@@ 165,15 162,23 @@ func (u *UserFetch) ProcessSuccessfulEmailUpdate(c echo.Context) error {
	return nil
}

// ProcessSuccessfulEmailConfirmation handle tasks after user is logged in
// ProcessSuccessfulEmailConfirmation handle tasks after user confirms their email address
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))

	// Add welcome link
	err := gctx.Server.QueueTask("general", AddWelcomeLinkTask(user, gctx))
	if err != nil {
		gctx.Server.Logger().Printf("Error queueing sendSendySubscribeTask: %v", err)
		gctx.Server.Logger().Printf("Error queueing AddWelcomeLinkTask: %v", err)
	}

	// Sendy
	err = gctx.Server.QueueTask("general", SendySubscribeTask(user, gctx))
	if err != nil {
		gctx.Server.Logger().Printf("Error queueing SendySubscribeTask: %v", err)
	}
	messages.Success(c, lt.Translate("You've successfully confirmed your email address."))
	return nil


@@ 230,63 235,6 @@ func (u *UserFetch) ProcessSuccessfulLogin(c echo.Context, user gobwebs.User) er

	lt := localizer.GetSessionLocalizer(c)
	messages.Success(c, lt.Translate("Successful login."))
	if !lUser.LastLogin.Valid {
		// User's first time logging in. Let's set them a welcome link to the getting started doc
		opts := &database.FilterOptions{
			Filter: sq.And{
				sq.Eq{"o.owner_id": user.GetID()},
				sq.Eq{"o.org_type": models.OrgTypeUser},
			},
			Limit: 1,
		}
		orgs, err := models.GetOrganizations(c.Request().Context(), opts)
		if err != nil {
			return err
		}
		if len(orgs) == 0 {
			// Should never be reached
			return fmt.Errorf("User has no organizations")
		}

		type GraphQLResponse struct {
			Link models.OrgLink `json:"addLink"`
		}

		var result GraphQLResponse
		q := `mutation AddLink($title: String!, $url: String!, $description: String,
							  $visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!,
							  $archive: Boolean! $slug: String!, $tags: String, $override: Boolean!) {
					addLink(input: {
								title: $title,
								url: $url,
								description: $description,
								visibility: $visibility,
								unread: $unread,
								starred: $starred,
								archive: $archive,
								orgSlug: $slug,
								tags: $tags,
								override: $override}) {
						id
					}
				}`
		op := gqlclient.NewOperation(q)
		op.Var("title", "Welcome to LinkTaco! Getting Started Guide")
		op.Var("url", "https://man.code.netlandish.com/~netlandish/links/getting-started.md")
		op.Var("description", "Welcome! This is your getting started guide for LinkTaco. Please click the link to read it and start saving your links right away!")
		op.Var("visibility", models.OrgLinkVisibilityPrivate)
		op.Var("tags", "help, linktaco, documentation, getting started")
		op.Var("unread", true)
		op.Var("starred", true)
		op.Var("archive", false)
		op.Var("slug", orgs[0].Slug)
		op.Var("override", true)

		err = links.Execute(c.Request().Context(), op, &result)
		if err != nil {
			return err
		}
	}
	return nil
}