diff --git a/appview/pages/templates/user/settings/sites.html b/appview/pages/templates/user/settings/sites.html --- a/appview/pages/templates/user/settings/sites.html +++ b/appview/pages/templates/user/settings/sites.html @@ -135,4 +135,4 @@
-{{ end }} \ No newline at end of file +{{ end }} diff --git a/appview/settings/settings.go b/appview/settings/settings.go --- a/appview/settings/settings.go +++ b/appview/settings/settings.go @@ -1,10 +1,12 @@ package settings import ( + "context" "database/sql" "errors" "fmt" "log" + "log/slog" "net/http" "net/url" "strings" @@ -12,6 +14,7 @@ "time" "github.com/go-chi/chi/v5" "tangled.org/core/api/tangled" + "tangled.org/core/appview/cloudflare" "tangled.org/core/appview/config" "tangled.org/core/appview/db" "tangled.org/core/appview/email" @@ -19,6 +22,7 @@ "tangled.org/core/appview/middleware" "tangled.org/core/appview/models" "tangled.org/core/appview/oauth" "tangled.org/core/appview/pages" + "tangled.org/core/appview/sites" "tangled.org/core/tid" comatproto "github.com/bluesky-social/indigo/api/atproto" @@ -29,10 +33,12 @@ "github.com/google/uuid" ) type Settings struct { - Db *db.DB - OAuth *oauth.OAuth - Pages *pages.Pages - Config *config.Config + Db *db.DB + OAuth *oauth.OAuth + Pages *pages.Pages + Config *config.Config + CfClient *cloudflare.Client + Logger *slog.Logger } func (s *Settings) Router() http.Handler { @@ -239,7 +245,7 @@ did := s.OAuth.GetDid(r) prefs, err := db.GetNotificationPreference(s.Db, did) if err != nil { - log.Printf("failed to get notification preferences: %s", err) + s.Logger.Error("failed to get notification preferences", "err", err) s.Pages.Notice(w, "settings-notifications-error", "Unable to load notification preferences.") return } @@ -269,7 +275,7 @@ } err := s.Db.UpdateNotificationPreferences(r.Context(), prefs) if err != nil { - log.Printf("failed to update notification preferences: %s", err) + s.Logger.Error("failed to update notification preferences", "err", err) s.Pages.Notice(w, "settings-notifications-error", "Unable to save notification preferences.") return } @@ -281,7 +287,7 @@ func (s *Settings) keysSettings(w http.ResponseWriter, r *http.Request) { user := s.OAuth.GetMultiAccountUser(r) pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Active.Did) if err != nil { - log.Println(err) + s.Logger.Error("keys settings", "err", err) } s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{ @@ -294,7 +300,7 @@ func (s *Settings) emailsSettings(w http.ResponseWriter, r *http.Request) { user := s.OAuth.GetMultiAccountUser(r) emails, err := db.GetAllEmails(s.Db, user.Active.Did) if err != nil { - log.Println(err) + s.Logger.Error("emails settings", "err", err) } s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{ @@ -325,7 +331,7 @@ emailToSend := s.buildVerificationEmail(emailAddr, did, code) err := email.SendEmail(emailToSend) if err != nil { - log.Printf("sending email: %s", err) + s.Logger.Error("sending email", "err", err) s.Pages.Notice(w, "settings-emails-error", fmt.Sprintf("Unable to send verification email at this moment, try again later. %s", errorContext)) return err } @@ -337,7 +343,7 @@ func (s *Settings) emails(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: s.Pages.Notice(w, "settings-emails", "Unimplemented.") - log.Println("unimplemented") + s.Logger.Warn("emails: unimplemented method") return case http.MethodPut: did := s.OAuth.GetDid(r) @@ -352,7 +358,7 @@ // check if email already exists in database existingEmail, err := db.GetEmail(s.Db, did, emAddr) if err != nil && !errors.Is(err, sql.ErrNoRows) { - log.Printf("checking for existing email: %s", err) + s.Logger.Error("checking for existing email", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") return } @@ -372,7 +378,7 @@ // Begin transaction tx, err := s.Db.Begin() if err != nil { - log.Printf("failed to start transaction: %s", err) + s.Logger.Error("failed to start transaction", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") return } @@ -384,7 +390,7 @@ Address: emAddr, Verified: false, VerificationCode: code, }); err != nil { - log.Printf("adding email: %s", err) + s.Logger.Error("adding email", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") return } @@ -395,7 +401,7 @@ } // Commit transaction if err := tx.Commit(); err != nil { - log.Printf("failed to commit transaction: %s", err) + s.Logger.Error("failed to commit add-email transaction", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") return } @@ -410,21 +416,21 @@ // Begin transaction tx, err := s.Db.Begin() if err != nil { - log.Printf("failed to start transaction: %s", err) + s.Logger.Error("failed to start transaction", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") return } defer tx.Rollback() if err := db.DeleteEmail(tx, did, emailAddr); err != nil { - log.Printf("deleting email: %s", err) + s.Logger.Error("deleting email", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") return } // Commit transaction if err := tx.Commit(); err != nil { - log.Printf("failed to commit transaction: %s", err) + s.Logger.Error("failed to commit delete-email transaction", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") return } @@ -454,7 +460,7 @@ code := q.Get("code") valid, err := db.CheckValidVerificationCode(s.Db, did, emailAddr, code) if err != nil { - log.Printf("checking email verification: %s", err) + s.Logger.Error("checking email verification", "err", err) s.Pages.Notice(w, "settings-emails-error", "Error verifying email. Please try again later.") return } @@ -466,7 +472,7 @@ } // Mark email as verified in the database if err := db.MarkEmailVerified(s.Db, did, emailAddr); err != nil { - log.Printf("marking email as verified: %s", err) + s.Logger.Error("marking email as verified", "err", err) s.Pages.Notice(w, "settings-emails-error", "Error updating email verification status. Please try again later.") return } @@ -495,7 +501,7 @@ if err != nil { if errors.Is(err, sql.ErrNoRows) { s.Pages.Notice(w, "settings-emails-error", "Email not found. Please add it first.") } else { - log.Printf("checking for existing email: %s", err) + s.Logger.Error("checking for existing email", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") } return @@ -522,7 +528,7 @@ // Begin transaction tx, err := s.Db.Begin() if err != nil { - log.Printf("failed to start transaction: %s", err) + s.Logger.Error("failed to start transaction", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") return } @@ -530,7 +536,7 @@ defer tx.Rollback() // Update the verification code and last sent time if err := db.UpdateVerificationCode(tx, did, emAddr, code); err != nil { - log.Printf("updating email verification: %s", err) + s.Logger.Error("updating email verification code", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") return } @@ -542,7 +548,7 @@ } // Commit transaction if err := tx.Commit(); err != nil { - log.Printf("failed to commit transaction: %s", err) + s.Logger.Error("failed to commit resend-verification transaction", "err", err) s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") return } @@ -561,7 +567,7 @@ return } if err := db.MakeEmailPrimary(s.Db, did, emailAddr); err != nil { - log.Printf("setting primary email: %s", err) + s.Logger.Error("setting primary email", "err", err) s.Pages.Notice(w, "settings-emails-error", "Error setting primary email. Please try again later.") return } @@ -573,7 +579,7 @@ func (s *Settings) keys(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: s.Pages.Notice(w, "settings-keys", "Unimplemented.") - log.Println("unimplemented") + s.Logger.Warn("keys: unimplemented method") return case http.MethodPut: did := s.OAuth.GetDid(r) @@ -588,7 +594,7 @@ } _, _, _, _, err = ssh.ParseAuthorizedKey([]byte(key)) if err != nil { - log.Printf("parsing public key: %s", err) + s.Logger.Error("parsing public key", "err", err) s.Pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a public key.") return } @@ -597,14 +603,14 @@ rkey := tid.TID() tx, err := s.Db.Begin() if err != nil { - log.Printf("failed to start tx; adding public key: %s", err) + s.Logger.Error("failed to start transaction for adding public key", "err", err) s.Pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") return } defer tx.Rollback() if err := db.AddPublicKey(tx, did, name, key, rkey); err != nil { - log.Printf("adding public key: %s", err) + s.Logger.Error("adding public key", "err", err) s.Pages.Notice(w, "settings-keys", "Failed to add public key.") return } @@ -623,16 +629,16 @@ }}, }) // invalid record if err != nil { - log.Printf("failed to create record: %s", err) + s.Logger.Error("failed to create atproto record", "err", err) s.Pages.Notice(w, "settings-keys", "Failed to create record.") return } - log.Println("created atproto record: ", resp.Uri) + s.Logger.Info("created atproto record", "uri", resp.Uri) err = tx.Commit() if err != nil { - log.Printf("failed to commit tx; adding public key: %s", err) + s.Logger.Error("failed to commit add-key transaction", "err", err) s.Pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") return } @@ -648,19 +654,17 @@ name := q.Get("name") rkey := q.Get("rkey") key := q.Get("key") - log.Println(name) - log.Println(rkey) - log.Println(key) + s.Logger.Debug("deleting key", "name", name, "rkey", rkey, "key", key) client, err := s.OAuth.AuthorizedClient(r) if err != nil { - log.Printf("failed to authorize client: %s", err) + s.Logger.Error("failed to authorize client", "err", err) s.Pages.Notice(w, "settings-keys", "Failed to authorize client.") return } if err := db.DeletePublicKey(s.Db, did, name, key); err != nil { - log.Printf("removing public key: %s", err) + s.Logger.Error("removing public key", "err", err) s.Pages.Notice(w, "settings-keys", "Failed to remove public key.") return } @@ -675,12 +679,12 @@ }) // invalid record if err != nil { - log.Printf("failed to delete record from PDS: %s", err) + s.Logger.Error("failed to delete record from PDS", "err", err) s.Pages.Notice(w, "settings-keys", "Failed to remove key from PDS.") return } } - log.Println("deleted successfully") + s.Logger.Info("deleted key successfully", "name", name) s.Pages.HxLocation(w, "/settings/keys") return diff --git a/appview/signup/signup.go b/appview/signup/signup.go --- a/appview/signup/signup.go +++ b/appview/signup/signup.go @@ -14,9 +14,9 @@ "strings" "github.com/go-chi/chi/v5" "github.com/posthog/posthog-go" + "tangled.org/core/appview/cloudflare" "tangled.org/core/appview/config" "tangled.org/core/appview/db" - "tangled.org/core/appview/dns" "tangled.org/core/appview/email" "tangled.org/core/appview/models" "tangled.org/core/appview/pages" @@ -27,7 +27,7 @@ type Signup struct { config *config.Config db *db.DB - cf *dns.Cloudflare + cf *cloudflare.Client posthog posthog.Client idResolver *idresolver.Resolver pages *pages.Pages @@ -36,10 +36,10 @@ disallowedNicknames map[string]bool } func New(cfg *config.Config, database *db.DB, pc posthog.Client, idResolver *idresolver.Resolver, pages *pages.Pages, l *slog.Logger) *Signup { - var cf *dns.Cloudflare - if cfg.Cloudflare.ApiToken != "" && cfg.Cloudflare.ZoneId != "" { + var cf *cloudflare.Client + if cfg.Cloudflare.ApiToken != "" { var err error - cf, err = dns.NewCloudflare(cfg) + cf, err = cloudflare.New(cfg) if err != nil { l.Warn("failed to create cloudflare client, signup will be disabled", "error", err) } @@ -120,7 +120,7 @@ switch r.Method { case http.MethodGet: emailId := r.URL.Query().Get("id") s.pages.Signup(w, pages.SignupParams{ - CloudflareSiteKey: s.config.Cloudflare.TurnstileSiteKey, + CloudflareSiteKey: s.config.Cloudflare.Turnstile.SiteKey, EmailId: emailId, }) case http.MethodPost: @@ -284,18 +284,18 @@ } // XXX: we have a wildcard *.tngl.sh record now // step 2: create DNS record with actual DID - // recordID, err = s.cf.CreateDNSRecord(ctx, dns.Record{ - // Type: "TXT", - // Name: "_atproto." + username, - // Content: fmt.Sprintf(`"did=%s"`, did), - // TTL: 6400, - // Proxied: false, - // }) - // if err != nil { - // s.l.Error("failed to create DNS record", "error", err) - // s.pages.Notice(w, "signup-error", "Failed to create DNS record for your handle. Please contact support.") - // return err - // } + // recordID, err = s.cf.CreateDNSRecord(ctx, cloudflare.DNSRecord{ + // Type: "TXT", + // Name: "_atproto." + username, + // Content: fmt.Sprintf(`"did=%s"`, did), + // TTL: 6400, + // Proxied: false, + // }) + // if err != nil { + // s.l.Error("failed to create DNS record", "error", err) + // s.pages.Notice(w, "signup-error", "Failed to create DNS record for your handle. Please contact support.") + // return err + // } // step 3: add email to database err = db.AddEmail(s.db, models.Email{ @@ -358,12 +358,12 @@ if cfToken == "" { return errors.New("captcha token is empty") } - if s.config.Cloudflare.TurnstileSecretKey == "" { + if s.config.Cloudflare.Turnstile.SecretKey == "" { return errors.New("turnstile secret key not configured") } data := url.Values{} - data.Set("secret", s.config.Cloudflare.TurnstileSecretKey) + data.Set("secret", s.config.Cloudflare.Turnstile.SecretKey) data.Set("response", cfToken) // include the client IP if we have it diff --git a/appview/state/router.go b/appview/state/router.go --- a/appview/state/router.go +++ b/appview/state/router.go @@ -210,10 +210,12 @@ } func (s *State) SettingsRouter() http.Handler { settings := &settings.Settings{ - Db: s.db, - OAuth: s.oauth, - Pages: s.pages, - Config: s.config, + Db: s.db, + OAuth: s.oauth, + Pages: s.pages, + Config: s.config, + CfClient: s.cfClient, + Logger: log.SubLogger(s.logger, "settings"), } return settings.Router() @@ -316,6 +318,7 @@ s.notifier, s.enforcer, log.SubLogger(s.logger, "repo"), s.validator, + s.cfClient, ) return repo.Router(mw) } diff --git a/appview/state/state.go b/appview/state/state.go --- a/appview/state/state.go +++ b/appview/state/state.go @@ -13,6 +13,7 @@ "tangled.org/core/api/tangled" "tangled.org/core/appview" "tangled.org/core/appview/bsky" + "tangled.org/core/appview/cloudflare" "tangled.org/core/appview/config" "tangled.org/core/appview/db" "tangled.org/core/appview/indexer" @@ -63,6 +64,7 @@ knotstream *eventconsumer.Consumer spindlestream *eventconsumer.Consumer logger *slog.Logger validator *validator.Validator + cfClient *cloudflare.Client } func Make(ctx context.Context, config *config.Config) (*State, error) { @@ -172,7 +174,16 @@ notifier := notify.NewMergedNotifier(notifiers) notifier = notify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) - knotstream, err := Knotstream(ctx, config, d, enforcer, posthog, notifier) + var cfClient *cloudflare.Client + if config.Cloudflare.ApiToken != "" { + cfClient, err = cloudflare.New(config) + if err != nil { + logger.Warn("failed to create cloudflare client, sites upload will be disabled", "err", err) + cfClient = nil + } + } + + knotstream, err := Knotstream(ctx, config, d, enforcer, posthog, notifier, cfClient) if err != nil { return nil, fmt.Errorf("failed to start knotstream consumer: %w", err) } @@ -185,22 +196,23 @@ } spindlestream.Start(ctx) state := &State{ - d, - notifier, - indexer, - oauth, - enforcer, - pages, - res, - mentionsResolver, - posthog, - jc, - config, - repoResolver, - knotstream, - spindlestream, - logger, - validator, + db: d, + notifier: notifier, + indexer: indexer, + oauth: oauth, + enforcer: enforcer, + pages: pages, + idResolver: res, + mentionsResolver: mentionsResolver, + posthog: posthog, + jc: jc, + config: config, + repoResolver: repoResolver, + knotstream: knotstream, + spindlestream: spindlestream, + logger: logger, + validator: validator, + cfClient: cfClient, } // fetch initial bluesky posts if configured