Monorepo for Tangled
tangled.org
1package email
2
3import (
4 "context"
5 "fmt"
6 "log"
7
8 securejoin "github.com/cyphar/filepath-securejoin"
9 "tangled.sh/tangled.sh/core/appview/config"
10 "tangled.sh/tangled.sh/core/appview/db"
11 "tangled.sh/tangled.sh/core/appview/notify"
12 "tangled.sh/tangled.sh/core/idresolver"
13)
14
15type EmailNotifier struct {
16 db *db.DB
17 idResolver *idresolver.Resolver
18 Config *config.Config
19 notify.BaseNotifier
20}
21
22func NewEmailNotifier(
23 db *db.DB,
24 idResolver *idresolver.Resolver,
25 config *config.Config,
26) notify.Notifier {
27 return &EmailNotifier{
28 db,
29 idResolver,
30 config,
31 notify.BaseNotifier{},
32 }
33}
34
35var _ notify.Notifier = &EmailNotifier{}
36
37// TODO: yeah this is just bad design. should be moved under idResolver ore include repoResolver at first place
38func (n *EmailNotifier) repoOwnerSlashName(ctx context.Context, repo *db.Repo) (string, error) {
39 repoOwnerID, err := n.idResolver.ResolveIdent(ctx, repo.Did)
40 if err != nil || repoOwnerID.Handle.IsInvalidHandle() {
41 return "", fmt.Errorf("resolve comment owner did: %w", err)
42 }
43 repoOwnerHandle := repoOwnerID.Handle
44 var repoOwnerSlashName string
45 if repoOwnerHandle != "" && !repoOwnerHandle.IsInvalidHandle() {
46 repoOwnerSlashName, _ = securejoin.SecureJoin(fmt.Sprintf("@%s", repoOwnerHandle), repo.Name)
47 } else {
48 repoOwnerSlashName = repo.DidSlashRepo()
49 }
50 return repoOwnerSlashName, nil
51}
52
53func (n *EmailNotifier) buildIssueEmail(ctx context.Context, repo *db.Repo, issue *db.Issue, comment *db.Comment) (Email, error) {
54 commentOwner, err := n.idResolver.ResolveIdent(ctx, comment.OwnerDid)
55 if err != nil || commentOwner.Handle.IsInvalidHandle() {
56 return Email{}, fmt.Errorf("resolve comment owner did: %w", err)
57 }
58 baseUrl := n.Config.Core.AppviewHost
59 repoOwnerSlashName, err := n.repoOwnerSlashName(ctx, repo)
60 if err != nil {
61 return Email{}, nil
62 }
63 url := fmt.Sprintf("%s/%s/issues/%d#comment-%d", baseUrl, repoOwnerSlashName, comment.Issue, comment.CommentId)
64 return Email{
65 APIKey: n.Config.Resend.ApiKey,
66 From: n.Config.Resend.SentFrom,
67 Subject: fmt.Sprintf("[%s] %s (issue#%d)", repoOwnerSlashName, issue.Title, issue.IssueId),
68 Html: fmt.Sprintf(`<p><b>@%s</b> mentioned you</p><a href="%s">View it on tangled.sh</a>.`, commentOwner.Handle.String(), url),
69 }, nil
70}
71
72func (n *EmailNotifier) buildPullEmail(ctx context.Context, repo *db.Repo, pull *db.Pull, comment *db.PullComment) (Email, error) {
73 commentOwner, err := n.idResolver.ResolveIdent(ctx, comment.OwnerDid)
74 if err != nil || commentOwner.Handle.IsInvalidHandle() {
75 return Email{}, fmt.Errorf("resolve comment owner did: %w", err)
76 }
77 repoOwnerSlashName, err := n.repoOwnerSlashName(ctx, repo)
78 if err != nil {
79 return Email{}, nil
80 }
81 baseUrl := n.Config.Core.AppviewHost
82 url := fmt.Sprintf("%s/%s/pulls/%d#comment-%d", baseUrl, repoOwnerSlashName, comment.PullId, comment.ID)
83 return Email{
84 APIKey: n.Config.Resend.ApiKey,
85 From: n.Config.Resend.SentFrom,
86 Subject: fmt.Sprintf("[%s] %s (pr#%d)", repoOwnerSlashName, pull.Title, pull.PullId),
87 Html: fmt.Sprintf(`<p><b>@%s</b> mentioned you</p><a href="%s">View it on tangled.sh</a>.`, commentOwner.Handle.String(), url),
88 }, nil
89}
90
91func (n *EmailNotifier) gatherRecipientEmails(ctx context.Context, handles []string) []string {
92 recipients := []string{}
93 resolvedIdents := n.idResolver.ResolveIdents(ctx, handles)
94 for _, id := range resolvedIdents {
95 email, err := db.GetPrimaryEmail(n.db, id.DID.String())
96 if err != nil {
97 log.Println("failed to get primary email:", err)
98 continue
99 }
100 recipients = append(recipients, email.Address)
101 }
102 return recipients
103}
104
105func (n *EmailNotifier) NewIssueComment(ctx context.Context, repo *db.Repo, issue *db.Issue, comment *db.Comment, mentions []string) {
106 email, err := n.buildIssueEmail(ctx, repo, issue, comment)
107 if err != nil {
108 log.Println("failed to create issue-email:", err)
109 return
110 }
111 // TODO: get issue-subscribed user DIDs and merge with mentioned users
112 recipients := n.gatherRecipientEmails(ctx, mentions)
113 log.Println("sending email to:", recipients)
114 if err = SendEmail(email, recipients...); err != nil {
115 log.Println("error sending email:", err)
116 }
117}
118
119func (n *EmailNotifier) NewPullComment(ctx context.Context, repo *db.Repo, pull *db.Pull, comment *db.PullComment, mentions []string) {
120 email, err := n.buildPullEmail(ctx, repo, pull, comment)
121 if err != nil {
122 log.Println("failed to create pull-email:", err)
123 }
124 recipients := n.gatherRecipientEmails(ctx, mentions)
125 log.Println("sending email to:", recipients)
126 if err = SendEmail(email); err != nil {
127 log.Println("error sending email:", err)
128 }
129}