Monorepo for Tangled tangled.org

appview: introduce email notifications for @ mentions on issue/pr comments #393

closed opened by boltless.me targeting master from boltless.me/core: feat/mentions

Stacked on top of #392

Yes, I know we have stacked PRs, but I want to explicitly separate two sets of commits and review both on different places

This is MVC implementation of email notification.

Still lot of parts are missing, but this is a PR with most basic features.

Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3lvdu42ftpn22
+105 -95
Interdiff #5 โ†’ #6
appview/pages/markup/markdown.go

This file has not been changed.

appview/pages/markup/markdown_at_extension.go

This file has not been changed.

appview/db/issues.go

This file has not been changed.

appview/issues/issues.go

This file has not been changed.

appview/notify/merged_notifier.go

This file has not been changed.

appview/notify/notifier.go

This file has not been changed.

appview/posthog/notifier.go

This file has not been changed.

appview/email/email.go

This file has not been changed.

+59 -59
appview/settings/settings.go
··· 42 42 43 43 44 44 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 45 58 r.Delete("/", s.keys) 46 59 }) 47 60 48 61 r.Post("/email/preference", s.emailPreference) 49 62 50 63 r.Route("/emails", func(r chi.Router) { 64 + r.Get("/", s.emailsSettings) 51 65 r.Put("/", s.emails) 52 - r.Delete("/", s.emails) 53 66 54 67 55 68 ··· 62 75 63 76 64 77 65 - log.Println(err) 66 - } 67 78 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + 100 + func (s *Settings) emailsSettings(w http.ResponseWriter, r *http.Request) { 101 + user := s.OAuth.GetUser(r) 68 102 preference, err := db.GetUserEmailPreference(s.Db, user.Did) 69 103 70 104 emails, err := db.GetAllEmails(s.Db, user.Did) ··· 72 106 log.Println(err) 73 107 } 74 108 75 - s.Pages.Settings(w, pages.SettingsParams{ 76 - LoggedInUser: user, 77 - PubKeys: pubKeys, 78 - Emails: emails, 79 - EmailNotifPreference: preference, 109 + s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{ 110 + LoggedInUser: user, 111 + Emails: emails, 112 + NotifPreference: preference, 113 + Tabs: settingsTabs, 114 + Tab: "emails", 80 115 }) 81 116 } 82 117 ··· 182 217 183 218 184 219 220 + return email.Email{ 221 + APIKey: s.Config.Resend.ApiKey, 222 + From: s.Config.Resend.SentFrom, 223 + To: emailAddr, 224 + Subject: "Verify your Tangled email", 225 + Text: `Click the link below (or copy and paste it into your browser) to verify your email address. 226 + ` + verifyURL, 185 227 186 228 187 229 188 230 189 231 190 232 233 + func (s *Settings) sendVerificationEmail(w http.ResponseWriter, did, emailAddr, code string, errorContext string) error { 234 + emailToSend := s.buildVerificationEmail(emailAddr, did, code) 191 235 236 + err := email.SendEmail(emailToSend) 237 + if err != nil { 238 + log.Printf("sending email: %s", err) 239 + s.Pages.Notice(w, "settings-emails-error", fmt.Sprintf("Unable to send verification email at this moment, try again later. %s", errorContext)) 240 + return err 241 + } 192 242 193 243 194 244 ··· 328 378 329 379 330 380 331 - 332 - 333 - 334 - 335 - 336 - 337 - 338 - 339 - 340 - 341 - 342 - 343 - 344 - 345 - 346 - s.Pages.HxLocation(w, "/settings") 381 + s.Pages.HxLocation(w, "/settings/emails") 347 382 } 348 383 349 384 func (s *Settings) emailPreference(w http.ResponseWriter, r *http.Request) { ··· 373 408 func (s *Settings) keys(w http.ResponseWriter, r *http.Request) { 374 409 switch r.Method { 375 410 case http.MethodGet: 376 - 377 - 378 - 379 - 380 - 381 - 382 - 383 - 384 - 385 - 386 - 387 - 388 - 389 - return email.Email{ 390 - APIKey: s.Config.Resend.ApiKey, 391 - From: s.Config.Resend.SentFrom, 392 - To: emailAddr, 393 - Subject: "Verify your Tangled email", 394 - Text: `Click the link below (or copy and paste it into your browser) to verify your email address. 395 - ` + verifyURL, 396 - 397 - 398 - 399 - 400 - 401 - 402 - func (s *Settings) sendVerificationEmail(w http.ResponseWriter, did, emailAddr, code string, errorContext string) error { 403 - emailToSend := s.buildVerificationEmail(emailAddr, did, code) 404 - 405 - err := email.SendEmail(emailToSend) 406 - if err != nil { 407 - log.Printf("sending email: %s", err) 408 - s.Pages.Notice(w, "settings-emails-error", fmt.Sprintf("Unable to send verification email at this moment, try again later. %s", errorContext)) 409 - return err 410 - }
appview/signup/signup.go

This file has not been changed.

appview/email/notifier.go

This file has not been changed.

appview/state/state.go

This patch was likely rebased, as context lines do not match.

+7 -7
appview/db/db.go
··· 678 678 return err 679 679 }) 680 680 681 - runMigration(conn, "add-email-notif-preference-to-profile", func(tx *sql.Tx) error { 682 - _, err := tx.Exec(` 683 - alter table profile add column email_notif_preference integer not null default 0 check (email_notif_preference in (0, 1, 2)); -- disable, metion, enable 684 - `) 685 - return err 686 - }) 687 - 688 681 return &DB{db}, nil 689 682 } 690 683 ··· 710 703 return err 711 704 }) 712 705 706 + runMigration(conn, "add-email-notif-preference-to-profile", func(tx *sql.Tx) error { 707 + _, err := tx.Exec(` 708 + alter table profile add column email_notif_preference integer not null default 0 check (email_notif_preference in (0, 1, 2)); -- disable, metion, enable 709 + `) 710 + return err 711 + }) 712 + 713 713 return &DB{db}, nil 714 714 } 715 715
appview/db/email.go

This file has not been changed.

appview/db/profile.go

This patch was likely rebased, as context lines do not match.

+8 -8
appview/pages/pages.go
··· 307 307 } 308 308 309 309 type SettingsParams struct { 310 - LoggedInUser *oauth.User 311 - PubKeys []db.PublicKey 312 - Emails []db.Email 313 - EmailNotifPreference db.EmailPreference 310 + LoggedInUser *oauth.User 311 + PubKeys []db.PublicKey 312 + Emails []db.Email 314 313 } 315 314 316 315 func (p *Pages) Settings(w io.Writer, params SettingsParams) error { ··· 329 328 } 330 329 331 330 type UserEmailsSettingsParams struct { 332 - LoggedInUser *oauth.User 333 - Emails []db.Email 334 - Tabs []map[string]any 335 - Tab string 331 + LoggedInUser *oauth.User 332 + Emails []db.Email 333 + NotifPreference db.EmailPreference 334 + Tabs []map[string]any 335 + Tab string 336 336 } 337 337 338 338 func (p *Pages) UserEmailsSettings(w io.Writer, params UserEmailsSettingsParams) error {
-20
appview/pages/templates/settings.html
··· 93 93 {{ define "emails" }} 94 94 <h2 class="text-sm font-bold py-2 px-6 uppercase dark:text-gray-300">email addresses</h2> 95 95 <section class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit"> 96 - <form 97 - hx-post="/settings/email/preference" 98 - hx-swap="none" 99 - hx-indicator="#email-preference-spinner" 100 - > 101 - <select 102 - name="preference" 103 - class="p-1 border border-gray-200 bg-white dark:bg-gray-700 dark:text-white dark:border-gray-600" 104 - > 105 - <option value="enable" {{ if .EmailNotifPreference.IsEnabled }}selected{{ end }}>Enable Email Notifications</option> 106 - <option value="mention" {{ if .EmailNotifPreference.IsMention }}selected{{ end }}>Only on Mentions</option> 107 - <option value="disable" {{ if .EmailNotifPreference.IsDisabled }}selected{{ end }}>Disable Email Notifications</option> 108 - </select> 109 - <button type="submit" class="btn text-base"> 110 - <span>Save Preference</span> 111 - <span id="email-preference-spinner" class="group"> 112 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 113 - </span> 114 - </button> 115 - </form> 116 96 <p class="mb-8 dark:text-gray-300">Commits authored using emails listed here will be associated with your Tangled profile.</p> 117 97 <div id="email-list" class="flex flex-col gap-6 mb-8"> 118 98 {{ range $index, $email := .Emails }}
appview/pulls/pulls.go

This patch was likely rebased, as context lines do not match.

+31 -1
appview/pages/templates/user/settings/emails.html
··· 11 11 </div> 12 12 <div class="col-span-1 md:col-span-3 flex flex-col gap-6"> 13 13 {{ template "emailSettings" . }} 14 + {{ template "emailList" . }} 14 15 </div> 15 16 </section> 16 17 </div> 17 18 {{ end }} 18 19 19 20 {{ define "emailSettings" }} 21 + <form 22 + hx-post="/settings/email/preference" 23 + hx-swap="none" 24 + hx-indicator="#email-preference-spinner" 25 + class="grid grid-cols-1 md:grid-cols-3 gap-4" 26 + > 27 + <div class="col-span-1 md:col-span-2"> 28 + <h2 class="text-sm pb-2 uppercase font-bold">Email Notifications</h2> 29 + </div> 30 + <select 31 + name="preference" 32 + class="p-1 border border-gray-200 bg-white dark:bg-gray-700 dark:text-white dark:border-gray-600" 33 + > 34 + <option value="enable" {{ if .NotifPreference.IsEnabled }}selected{{ end }}>Enable</option> 35 + <option value="mention" {{ if .NotifPreference.IsMention }}selected{{ end }}>Only on Mentions</option> 36 + <option value="disable" {{ if .NotifPreference.IsDisabled }}selected{{ end }}>Disable</option> 37 + </select> 38 + <div class="md:col-start-2 col-span-2 flex justify-end"> 39 + <button type="submit" class="btn text-base"> 40 + <span>Save Preference</span> 41 + <span id="email-preference-spinner" class="group"> 42 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 43 + </span> 44 + </button> 45 + </div> 46 + </form> 47 + {{ end }} 48 + 49 + {{ define "emailList" }} 20 50 <div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center"> 21 51 <div class="col-span-1 md:col-span-2"> 22 52 <h2 class="text-sm pb-2 uppercase font-bold">Email Addresses</h2> ··· 91 121 <div id="settings-emails-error" class="text-red-500 dark:text-red-400"></div> 92 122 <div id="settings-emails-success" class="text-green-500 dark:text-green-400"></div> 93 123 </form> 94 - {{ end }} 124 + {{ end }}

History

7 rounds 4 comments
sign up or login to add to the discussion
7 commits
expand
appview/pages: markup: add @ user-mention parsing in markdown
appview: add NewIssueComment event
appview: db/issues: set IssueId on GetIssue
appview: email: support multiple recipients on SendEmail
appview: email: send email notification on mention
appview: settings: add email preference setting
appview: email: notify mentioned users on pull-comments
expand 1 comment
closed without merging
7 commits
expand
appview/pages: markup: add @ user-mention parsing in markdown
appview: add NewIssueComment event
appview: db/issues: set IssueId on GetIssue
appview: email: support multiple recipients on SendEmail
appview: email: send email notification on mention
appview: settings: add email preference setting
appview: email: notify mentioned users on pull-comments
expand 0 comments
7 commits
expand
appview: reporesolver: pass entire Repo object through context
apview: replace all use of db.Repo.AtUri with db.Repo.RepoAt()
appview: db/repos: remove AtUri from Repo
appview/pages: markup: add @ user-mention parsing in markdown
appview: add NewIssueComment event
appview: email: send email notification on mention
appview: email: notify mentioned users on pull-comments
expand 0 comments
7 commits
expand
appview: reporesolver: pass entire Repo object through context
apview: replace all use of db.Repo.AtUri with db.Repo.RepoAt()
appview: db/repos: remove AtUri from Repo
appview/pages: markup: add @ user-mention parsing in markdown
appview: add NewIssueComment event
appview: email: send email notification on mention
appview: email: notify mentioned users on pull-comments
expand 0 comments
7 commits
expand
appview: reporesolver: pass entire Repo object through context
apview: replace all use of db.Repo.AtUri with db.Repo.RepoAt()
appview: db/repos: remove AtUri from Repo
appview/pages: markup: add @ user-mention parsing in markdown
appview: add NewIssueComment event
appview: email: send email notification on mention
appview: email: notify mentioned users on pull-comments
expand 0 comments
6 commits
expand
appview: reporesolver: pass entire Repo object through context
apview: replace all use of db.Repo.AtUri with db.Repo.RepoAt()
appview/pages: markup: add @ user-mention parsing in markdown
appview: add NewIssueComment event
appview: email: send email notification on mention
appview: email: notify mentioned users on pull-comments
expand 1 comment

@oppi.li I addressed your last two points. Thank you again for the detailed review.

for regex part, I'm not sure if we can share part of regex and modify by our needs.

7 commits
expand
appview: reporesolver: pass entire Repo object through context
apview: replace all use of db.Repo.AtUri with db.Repo.RepoAt()
appview/pages: markup: add @ user-mention parsing in markdown
appview: add NewIssueComment event
appview: email: send email notification on mention
appview: email: add source to email notification
appview: email: notify mentioned users on pull-comments
expand 2 comments

i'd prefer if this was stacked! it would make it slightly easier to review:

  • in 1b6628ab: can we use the handle regex from the userutil module and customize it a bit for atRegex?
  • 18b248e5 and 9eebc110 seem to be modifying the same file, it would be nice to merge the two changes (running jj absorb when editing the second change should automatically solve it!).
  • similarly, dfa71348 seems to update the logic for issue-comment emails. would be nice to collapse or clean up the diffs.

this was only a first pass of reviews, but ill go through and have a closer look at the logic in the code, and give this a go locally. thanks again for working on this, its looking pretty cool already!

@oppi.li Thank you for the review!

and yeah I agree that I should have made this PR stacked.

Tell me if you think complete resubmission in form of stacked PR will be better.