This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / xrpc / account.go
5.1 kB 173 lines
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}