This repository has no description
1package xrpc
2
3import (
4 "database/sql"
5 "encoding/json"
6 "errors"
7 "net/http"
8 "strings"
9
10 "tangled.org/core/api/tangled"
11 "tangled.org/core/appview/db"
12 "tangled.org/core/appview/email"
13 xrpcerr "tangled.org/core/xrpc/errors"
14)
15
16func (x *Xrpc) AccountListEmails(w http.ResponseWriter, r *http.Request) {
17 l := x.Logger.With("handler", "AccountListEmails")
18
19 did, ok := actorDid(r)
20 if !ok {
21 writeError(w, xrpcerr.MissingActorDidError, http.StatusForbidden)
22 return
23 }
24
25 emails, err := db.GetAllEmails(x.DB, did)
26 if err != nil {
27 l.Error("failed to get emails", "err", err)
28 writeError(w, errInternal, http.StatusInternalServerError)
29 return
30 }
31
32 items := make([]*tangled.TempAccountListEmails_Email, 0, len(emails))
33 for _, e := range emails {
34 items = append(items, &tangled.TempAccountListEmails_Email{
35 Address: e.Address,
36 Verified: e.Verified,
37 Primary: e.Primary,
38 CreatedAt: e.CreatedAt.UTC().Format(timeFormat),
39 })
40 }
41
42 x.writeJSON(w, &tangled.TempAccountListEmails_Output{Emails: items})
43}
44
45func (x *Xrpc) AccountDeleteEmail(w http.ResponseWriter, r *http.Request) {
46 l := x.Logger.With("handler", "AccountDeleteEmail")
47
48 did, ok := actorDid(r)
49 if !ok {
50 writeError(w, xrpcerr.MissingActorDidError, http.StatusForbidden)
51 return
52 }
53
54 var input tangled.TempAccountDeleteEmail_Input
55 if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
56 writeError(w, errBadRequestBody, http.StatusBadRequest)
57 return
58 }
59 addr := strings.TrimSpace(input.Email)
60
61 existing, err := db.GetEmail(x.DB, did, addr)
62 if err != nil {
63 if errors.Is(err, sql.ErrNoRows) {
64 writeError(w, xrpcErrorTag("EmailNotFound", "the email address is not associated with this account"), http.StatusNotFound)
65 return
66 }
67 l.Error("failed to get email", "err", err)
68 writeError(w, errInternal, http.StatusInternalServerError)
69 return
70 }
71 if existing.Primary {
72 writeError(w, xrpcErrorTag("CannotDeletePrimary", "the primary email address cannot be deleted; set another address as primary first"), http.StatusBadRequest)
73 return
74 }
75
76 if err := db.DeleteEmail(x.DB, did, addr); err != nil {
77 l.Error("failed to delete email", "err", err)
78 writeError(w, errInternal, http.StatusInternalServerError)
79 return
80 }
81
82 w.WriteHeader(http.StatusOK)
83}
84
85func (x *Xrpc) AccountSetPrimaryEmail(w http.ResponseWriter, r *http.Request) {
86 l := x.Logger.With("handler", "AccountSetPrimaryEmail")
87
88 did, ok := actorDid(r)
89 if !ok {
90 writeError(w, xrpcerr.MissingActorDidError, http.StatusForbidden)
91 return
92 }
93
94 var input tangled.TempAccountSetPrimaryEmail_Input
95 if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
96 writeError(w, errBadRequestBody, http.StatusBadRequest)
97 return
98 }
99 addr := strings.TrimSpace(input.Email)
100
101 existing, err := db.GetEmail(x.DB, did, addr)
102 if err != nil {
103 if errors.Is(err, sql.ErrNoRows) {
104 writeError(w, xrpcErrorTag("EmailNotFound", "the email address is not associated with this account"), http.StatusNotFound)
105 return
106 }
107 l.Error("failed to get email", "err", err)
108 writeError(w, errInternal, http.StatusInternalServerError)
109 return
110 }
111 if !existing.Verified {
112 writeError(w, xrpcErrorTag("EmailNotVerified", "the email address must be verified before it can be made primary"), http.StatusBadRequest)
113 return
114 }
115
116 if err := db.MakeEmailPrimary(x.DB, did, addr); err != nil {
117 l.Error("failed to set primary email", "err", err)
118 writeError(w, errInternal, http.StatusInternalServerError)
119 return
120 }
121
122 w.WriteHeader(http.StatusOK)
123}
124
125func (x *Xrpc) AccountSubscribeNewsletter(w http.ResponseWriter, r *http.Request) {
126 l := x.Logger.With("handler", "AccountSubscribeNewsletter")
127
128 did, ok := actorDid(r)
129 if !ok {
130 writeError(w, xrpcerr.MissingActorDidError, http.StatusForbidden)
131 return
132 }
133
134 primary, err := db.GetPrimaryEmail(x.DB, did)
135 if err != nil || primary.Address == "" {
136 writeError(w, xrpcErrorTag("NoVerifiedEmail", "a primary email address is required to subscribe"), http.StatusBadRequest)
137 return
138 }
139
140 if err := db.UpsertNewsletterPref(x.DB, did, db.NewsletterStatusSubscribed, primary.Address); err != nil {
141 l.Error("failed to persist newsletter preference", "err", err)
142 writeError(w, errInternal, http.StatusInternalServerError)
143 return
144 }
145
146 if x.Config.Resend.ApiKey != "" && x.Config.Resend.NewsletterSegmentId != "" {
147 go func() {
148 if err := email.AddNewsletterContact(x.Config.Resend.ApiKey, x.Config.Resend.NewsletterSegmentId, primary.Address); err != nil {
149 l.Error("failed to add newsletter contact", "err", err)
150 }
151 }()
152 }
153
154 w.WriteHeader(http.StatusOK)
155}
156
157func (x *Xrpc) AccountDismissNewsletter(w http.ResponseWriter, r *http.Request) {
158 l := x.Logger.With("handler", "AccountDismissNewsletter")
159
160 did, ok := actorDid(r)
161 if !ok {
162 writeError(w, xrpcerr.MissingActorDidError, http.StatusForbidden)
163 return
164 }
165
166 if err := db.UpsertNewsletterPref(x.DB, did, db.NewsletterStatusDismissed, ""); err != nil {
167 l.Error("failed to persist newsletter dismissal", "err", err)
168 writeError(w, errInternal, http.StatusInternalServerError)
169 return
170 }
171
172 w.WriteHeader(http.StatusOK)
173}