Monorepo for Tangled tangled.org

appview/settings: add domain claim/release with r2, kv and db cleanup

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

anirudh.fi c25b9122 efb26f13

verified
+99 -80
+1 -1
appview/pages/templates/user/settings/sites.html
··· 135 135 <div id="settings-sites-error" class="text-red-500 dark:text-red-400"></div> 136 136 <div id="settings-sites-success" class="text-green-500 dark:text-green-400"></div> 137 137 </form> 138 - {{ end }} 138 + {{ end }}
+42 -38
appview/settings/settings.go
··· 1 1 package settings 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 5 6 "errors" 6 7 "fmt" 7 8 "log" 9 + "log/slog" 8 10 "net/http" 9 11 "net/url" 10 12 "strings" ··· 12 14 13 15 "github.com/go-chi/chi/v5" 14 16 "tangled.org/core/api/tangled" 17 + "tangled.org/core/appview/cloudflare" 15 18 "tangled.org/core/appview/config" 16 19 "tangled.org/core/appview/db" 17 20 "tangled.org/core/appview/email" ··· 19 22 "tangled.org/core/appview/models" 20 23 "tangled.org/core/appview/oauth" 21 24 "tangled.org/core/appview/pages" 25 + "tangled.org/core/appview/sites" 22 26 "tangled.org/core/tid" 23 27 24 28 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 29 33 ) 30 34 31 35 type Settings struct { 32 - Db *db.DB 33 - OAuth *oauth.OAuth 34 - Pages *pages.Pages 35 - Config *config.Config 36 + Db *db.DB 37 + OAuth *oauth.OAuth 38 + Pages *pages.Pages 39 + Config *config.Config 40 + CfClient *cloudflare.Client 41 + Logger *slog.Logger 36 42 } 37 43 38 44 func (s *Settings) Router() http.Handler { ··· 239 245 240 246 prefs, err := db.GetNotificationPreference(s.Db, did) 241 247 if err != nil { 242 - log.Printf("failed to get notification preferences: %s", err) 248 + s.Logger.Error("failed to get notification preferences", "err", err) 243 249 s.Pages.Notice(w, "settings-notifications-error", "Unable to load notification preferences.") 244 250 return 245 251 } ··· 269 275 270 276 err := s.Db.UpdateNotificationPreferences(r.Context(), prefs) 271 277 if err != nil { 272 - log.Printf("failed to update notification preferences: %s", err) 278 + s.Logger.Error("failed to update notification preferences", "err", err) 273 279 s.Pages.Notice(w, "settings-notifications-error", "Unable to save notification preferences.") 274 280 return 275 281 } ··· 281 287 user := s.OAuth.GetMultiAccountUser(r) 282 288 pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Active.Did) 283 289 if err != nil { 284 - log.Println(err) 290 + s.Logger.Error("keys settings", "err", err) 285 291 } 286 292 287 293 s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{ ··· 294 300 user := s.OAuth.GetMultiAccountUser(r) 295 301 emails, err := db.GetAllEmails(s.Db, user.Active.Did) 296 302 if err != nil { 297 - log.Println(err) 303 + s.Logger.Error("emails settings", "err", err) 298 304 } 299 305 300 306 s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{ ··· 325 331 326 332 err := email.SendEmail(emailToSend) 327 333 if err != nil { 328 - log.Printf("sending email: %s", err) 334 + s.Logger.Error("sending email", "err", err) 329 335 s.Pages.Notice(w, "settings-emails-error", fmt.Sprintf("Unable to send verification email at this moment, try again later. %s", errorContext)) 330 336 return err 331 337 } ··· 337 343 switch r.Method { 338 344 case http.MethodGet: 339 345 s.Pages.Notice(w, "settings-emails", "Unimplemented.") 340 - log.Println("unimplemented") 346 + s.Logger.Warn("emails: unimplemented method") 341 347 return 342 348 case http.MethodPut: 343 349 did := s.OAuth.GetDid(r) ··· 352 358 // check if email already exists in database 353 359 existingEmail, err := db.GetEmail(s.Db, did, emAddr) 354 360 if err != nil && !errors.Is(err, sql.ErrNoRows) { 355 - log.Printf("checking for existing email: %s", err) 361 + s.Logger.Error("checking for existing email", "err", err) 356 362 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 357 363 return 358 364 } ··· 372 378 // Begin transaction 373 379 tx, err := s.Db.Begin() 374 380 if err != nil { 375 - log.Printf("failed to start transaction: %s", err) 381 + s.Logger.Error("failed to start transaction", "err", err) 376 382 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 377 383 return 378 384 } ··· 384 390 Verified: false, 385 391 VerificationCode: code, 386 392 }); err != nil { 387 - log.Printf("adding email: %s", err) 393 + s.Logger.Error("adding email", "err", err) 388 394 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 389 395 return 390 396 } ··· 395 401 396 402 // Commit transaction 397 403 if err := tx.Commit(); err != nil { 398 - log.Printf("failed to commit transaction: %s", err) 404 + s.Logger.Error("failed to commit add-email transaction", "err", err) 399 405 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 400 406 return 401 407 } ··· 410 416 // Begin transaction 411 417 tx, err := s.Db.Begin() 412 418 if err != nil { 413 - log.Printf("failed to start transaction: %s", err) 419 + s.Logger.Error("failed to start transaction", "err", err) 414 420 s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 415 421 return 416 422 } 417 423 defer tx.Rollback() 418 424 419 425 if err := db.DeleteEmail(tx, did, emailAddr); err != nil { 420 - log.Printf("deleting email: %s", err) 426 + s.Logger.Error("deleting email", "err", err) 421 427 s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 422 428 return 423 429 } 424 430 425 431 // Commit transaction 426 432 if err := tx.Commit(); err != nil { 427 - log.Printf("failed to commit transaction: %s", err) 433 + s.Logger.Error("failed to commit delete-email transaction", "err", err) 428 434 s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 429 435 return 430 436 } ··· 454 460 455 461 valid, err := db.CheckValidVerificationCode(s.Db, did, emailAddr, code) 456 462 if err != nil { 457 - log.Printf("checking email verification: %s", err) 463 + s.Logger.Error("checking email verification", "err", err) 458 464 s.Pages.Notice(w, "settings-emails-error", "Error verifying email. Please try again later.") 459 465 return 460 466 } ··· 466 472 467 473 // Mark email as verified in the database 468 474 if err := db.MarkEmailVerified(s.Db, did, emailAddr); err != nil { 469 - log.Printf("marking email as verified: %s", err) 475 + s.Logger.Error("marking email as verified", "err", err) 470 476 s.Pages.Notice(w, "settings-emails-error", "Error updating email verification status. Please try again later.") 471 477 return 472 478 } ··· 495 501 if errors.Is(err, sql.ErrNoRows) { 496 502 s.Pages.Notice(w, "settings-emails-error", "Email not found. Please add it first.") 497 503 } else { 498 - log.Printf("checking for existing email: %s", err) 504 + s.Logger.Error("checking for existing email", "err", err) 499 505 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 500 506 } 501 507 return ··· 522 528 // Begin transaction 523 529 tx, err := s.Db.Begin() 524 530 if err != nil { 525 - log.Printf("failed to start transaction: %s", err) 531 + s.Logger.Error("failed to start transaction", "err", err) 526 532 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 527 533 return 528 534 } ··· 530 536 531 537 // Update the verification code and last sent time 532 538 if err := db.UpdateVerificationCode(tx, did, emAddr, code); err != nil { 533 - log.Printf("updating email verification: %s", err) 539 + s.Logger.Error("updating email verification code", "err", err) 534 540 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 535 541 return 536 542 } ··· 542 548 543 549 // Commit transaction 544 550 if err := tx.Commit(); err != nil { 545 - log.Printf("failed to commit transaction: %s", err) 551 + s.Logger.Error("failed to commit resend-verification transaction", "err", err) 546 552 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 547 553 return 548 554 } ··· 561 567 } 562 568 563 569 if err := db.MakeEmailPrimary(s.Db, did, emailAddr); err != nil { 564 - log.Printf("setting primary email: %s", err) 570 + s.Logger.Error("setting primary email", "err", err) 565 571 s.Pages.Notice(w, "settings-emails-error", "Error setting primary email. Please try again later.") 566 572 return 567 573 } ··· 573 579 switch r.Method { 574 580 case http.MethodGet: 575 581 s.Pages.Notice(w, "settings-keys", "Unimplemented.") 576 - log.Println("unimplemented") 582 + s.Logger.Warn("keys: unimplemented method") 577 583 return 578 584 case http.MethodPut: 579 585 did := s.OAuth.GetDid(r) ··· 588 594 589 595 _, _, _, _, err = ssh.ParseAuthorizedKey([]byte(key)) 590 596 if err != nil { 591 - log.Printf("parsing public key: %s", err) 597 + s.Logger.Error("parsing public key", "err", err) 592 598 s.Pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a <strong>public</strong> key.") 593 599 return 594 600 } ··· 597 603 598 604 tx, err := s.Db.Begin() 599 605 if err != nil { 600 - log.Printf("failed to start tx; adding public key: %s", err) 606 + s.Logger.Error("failed to start transaction for adding public key", "err", err) 601 607 s.Pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") 602 608 return 603 609 } 604 610 defer tx.Rollback() 605 611 606 612 if err := db.AddPublicKey(tx, did, name, key, rkey); err != nil { 607 - log.Printf("adding public key: %s", err) 613 + s.Logger.Error("adding public key", "err", err) 608 614 s.Pages.Notice(w, "settings-keys", "Failed to add public key.") 609 615 return 610 616 } ··· 623 629 }) 624 630 // invalid record 625 631 if err != nil { 626 - log.Printf("failed to create record: %s", err) 632 + s.Logger.Error("failed to create atproto record", "err", err) 627 633 s.Pages.Notice(w, "settings-keys", "Failed to create record.") 628 634 return 629 635 } 630 636 631 - log.Println("created atproto record: ", resp.Uri) 637 + s.Logger.Info("created atproto record", "uri", resp.Uri) 632 638 633 639 err = tx.Commit() 634 640 if err != nil { 635 - log.Printf("failed to commit tx; adding public key: %s", err) 641 + s.Logger.Error("failed to commit add-key transaction", "err", err) 636 642 s.Pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") 637 643 return 638 644 } ··· 648 654 rkey := q.Get("rkey") 649 655 key := q.Get("key") 650 656 651 - log.Println(name) 652 - log.Println(rkey) 653 - log.Println(key) 657 + s.Logger.Debug("deleting key", "name", name, "rkey", rkey, "key", key) 654 658 655 659 client, err := s.OAuth.AuthorizedClient(r) 656 660 if err != nil { 657 - log.Printf("failed to authorize client: %s", err) 661 + s.Logger.Error("failed to authorize client", "err", err) 658 662 s.Pages.Notice(w, "settings-keys", "Failed to authorize client.") 659 663 return 660 664 } 661 665 662 666 if err := db.DeletePublicKey(s.Db, did, name, key); err != nil { 663 - log.Printf("removing public key: %s", err) 667 + s.Logger.Error("removing public key", "err", err) 664 668 s.Pages.Notice(w, "settings-keys", "Failed to remove public key.") 665 669 return 666 670 } ··· 675 679 676 680 // invalid record 677 681 if err != nil { 678 - log.Printf("failed to delete record from PDS: %s", err) 682 + s.Logger.Error("failed to delete record from PDS", "err", err) 679 683 s.Pages.Notice(w, "settings-keys", "Failed to remove key from PDS.") 680 684 return 681 685 } 682 686 } 683 - log.Println("deleted successfully") 687 + s.Logger.Info("deleted key successfully", "name", name) 684 688 685 689 s.Pages.HxLocation(w, "/settings/keys") 686 690 return
+20 -20
appview/signup/signup.go
··· 14 14 15 15 "github.com/go-chi/chi/v5" 16 16 "github.com/posthog/posthog-go" 17 + "tangled.org/core/appview/cloudflare" 17 18 "tangled.org/core/appview/config" 18 19 "tangled.org/core/appview/db" 19 - "tangled.org/core/appview/dns" 20 20 "tangled.org/core/appview/email" 21 21 "tangled.org/core/appview/models" 22 22 "tangled.org/core/appview/pages" ··· 27 27 type Signup struct { 28 28 config *config.Config 29 29 db *db.DB 30 - cf *dns.Cloudflare 30 + cf *cloudflare.Client 31 31 posthog posthog.Client 32 32 idResolver *idresolver.Resolver 33 33 pages *pages.Pages ··· 36 36 } 37 37 38 38 func New(cfg *config.Config, database *db.DB, pc posthog.Client, idResolver *idresolver.Resolver, pages *pages.Pages, l *slog.Logger) *Signup { 39 - var cf *dns.Cloudflare 40 - if cfg.Cloudflare.ApiToken != "" && cfg.Cloudflare.ZoneId != "" { 39 + var cf *cloudflare.Client 40 + if cfg.Cloudflare.ApiToken != "" { 41 41 var err error 42 - cf, err = dns.NewCloudflare(cfg) 42 + cf, err = cloudflare.New(cfg) 43 43 if err != nil { 44 44 l.Warn("failed to create cloudflare client, signup will be disabled", "error", err) 45 45 } ··· 120 120 case http.MethodGet: 121 121 emailId := r.URL.Query().Get("id") 122 122 s.pages.Signup(w, pages.SignupParams{ 123 - CloudflareSiteKey: s.config.Cloudflare.TurnstileSiteKey, 123 + CloudflareSiteKey: s.config.Cloudflare.Turnstile.SiteKey, 124 124 EmailId: emailId, 125 125 }) 126 126 case http.MethodPost: ··· 284 284 285 285 // XXX: we have a wildcard *.tngl.sh record now 286 286 // step 2: create DNS record with actual DID 287 - // recordID, err = s.cf.CreateDNSRecord(ctx, dns.Record{ 288 - // Type: "TXT", 289 - // Name: "_atproto." + username, 290 - // Content: fmt.Sprintf(`"did=%s"`, did), 291 - // TTL: 6400, 292 - // Proxied: false, 293 - // }) 294 - // if err != nil { 295 - // s.l.Error("failed to create DNS record", "error", err) 296 - // s.pages.Notice(w, "signup-error", "Failed to create DNS record for your handle. Please contact support.") 297 - // return err 298 - // } 287 + // recordID, err = s.cf.CreateDNSRecord(ctx, cloudflare.DNSRecord{ 288 + // Type: "TXT", 289 + // Name: "_atproto." + username, 290 + // Content: fmt.Sprintf(`"did=%s"`, did), 291 + // TTL: 6400, 292 + // Proxied: false, 293 + // }) 294 + // if err != nil { 295 + // s.l.Error("failed to create DNS record", "error", err) 296 + // s.pages.Notice(w, "signup-error", "Failed to create DNS record for your handle. Please contact support.") 297 + // return err 298 + // } 299 299 300 300 // step 3: add email to database 301 301 err = db.AddEmail(s.db, models.Email{ ··· 358 358 return errors.New("captcha token is empty") 359 359 } 360 360 361 - if s.config.Cloudflare.TurnstileSecretKey == "" { 361 + if s.config.Cloudflare.Turnstile.SecretKey == "" { 362 362 return errors.New("turnstile secret key not configured") 363 363 } 364 364 365 365 data := url.Values{} 366 - data.Set("secret", s.config.Cloudflare.TurnstileSecretKey) 366 + data.Set("secret", s.config.Cloudflare.Turnstile.SecretKey) 367 367 data.Set("response", cfToken) 368 368 369 369 // include the client IP if we have it
+7 -4
appview/state/router.go
··· 210 210 211 211 func (s *State) SettingsRouter() http.Handler { 212 212 settings := &settings.Settings{ 213 - Db: s.db, 214 - OAuth: s.oauth, 215 - Pages: s.pages, 216 - Config: s.config, 213 + Db: s.db, 214 + OAuth: s.oauth, 215 + Pages: s.pages, 216 + Config: s.config, 217 + CfClient: s.cfClient, 218 + Logger: log.SubLogger(s.logger, "settings"), 217 219 } 218 220 219 221 return settings.Router() ··· 316 318 s.enforcer, 317 319 log.SubLogger(s.logger, "repo"), 318 320 s.validator, 321 + s.cfClient, 319 322 ) 320 323 return repo.Router(mw) 321 324 }
+29 -17
appview/state/state.go
··· 13 13 "tangled.org/core/api/tangled" 14 14 "tangled.org/core/appview" 15 15 "tangled.org/core/appview/bsky" 16 + "tangled.org/core/appview/cloudflare" 16 17 "tangled.org/core/appview/config" 17 18 "tangled.org/core/appview/db" 18 19 "tangled.org/core/appview/indexer" ··· 63 64 spindlestream *eventconsumer.Consumer 64 65 logger *slog.Logger 65 66 validator *validator.Validator 67 + cfClient *cloudflare.Client 66 68 } 67 69 68 70 func Make(ctx context.Context, config *config.Config) (*State, error) { ··· 172 174 notifier := notify.NewMergedNotifier(notifiers) 173 175 notifier = notify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) 174 176 175 - knotstream, err := Knotstream(ctx, config, d, enforcer, posthog, notifier) 177 + var cfClient *cloudflare.Client 178 + if config.Cloudflare.ApiToken != "" { 179 + cfClient, err = cloudflare.New(config) 180 + if err != nil { 181 + logger.Warn("failed to create cloudflare client, sites upload will be disabled", "err", err) 182 + cfClient = nil 183 + } 184 + } 185 + 186 + knotstream, err := Knotstream(ctx, config, d, enforcer, posthog, notifier, cfClient) 176 187 if err != nil { 177 188 return nil, fmt.Errorf("failed to start knotstream consumer: %w", err) 178 189 } ··· 185 196 spindlestream.Start(ctx) 186 197 187 198 state := &State{ 188 - d, 189 - notifier, 190 - indexer, 191 - oauth, 192 - enforcer, 193 - pages, 194 - res, 195 - mentionsResolver, 196 - posthog, 197 - jc, 198 - config, 199 - repoResolver, 200 - knotstream, 201 - spindlestream, 202 - logger, 203 - validator, 199 + db: d, 200 + notifier: notifier, 201 + indexer: indexer, 202 + oauth: oauth, 203 + enforcer: enforcer, 204 + pages: pages, 205 + idResolver: res, 206 + mentionsResolver: mentionsResolver, 207 + posthog: posthog, 208 + jc: jc, 209 + config: config, 210 + repoResolver: repoResolver, 211 + knotstream: knotstream, 212 + spindlestream: spindlestream, 213 + logger: logger, 214 + validator: validator, 215 + cfClient: cfClient, 204 216 } 205 217 206 218 // fetch initial bluesky posts if configured