Monorepo for Tangled
0
fork

Configure Feed

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

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

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

+118 -71
+73 -41
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 { ··· 79 85 80 86 claim, err := db.GetActiveDomainClaimForDid(s.Db, did) 81 87 if err != nil { 82 - log.Printf("failed to get domain claim: %s", err) 88 + s.Logger.Error("failed to get domain claim", "err", err) 83 89 claim = nil 84 90 } 85 91 ··· 135 141 case errors.Is(err, db.ErrAlreadyClaimed): 136 142 s.Pages.Notice(w, "settings-sites-error", "You already have a domain claimed. Release it before claiming a new one.") 137 143 default: 138 - log.Printf("claiming domain: %s", err) 144 + s.Logger.Error("claiming domain", "err", err) 139 145 s.Pages.Notice(w, "settings-sites-error", "Unable to claim domain at this moment. Try again later.") 140 146 } 141 147 return ··· 166 172 } 167 173 168 174 if err := db.ReleaseDomain(s.Db, did, domain); err != nil { 169 - log.Printf("releasing domain: %s", err) 175 + s.Logger.Error("releasing domain", "err", err) 170 176 s.Pages.Notice(w, "settings-sites-error", "Unable to release domain. Make sure it belongs to your account.") 171 177 return 172 178 } 173 179 180 + // Clean up all site data for this DID asynchronously. 181 + if s.CfClient.Enabled() { 182 + siteConfigs, err := db.GetRepoSiteConfigsForDid(s.Db, did) 183 + if err != nil { 184 + s.Logger.Error("releaseSitesDomain: fetching site configs for cleanup", "err", err) 185 + } 186 + 187 + if err := db.DeleteRepoSiteConfigsForDid(s.Db, did); err != nil { 188 + s.Logger.Error("releaseSitesDomain: deleting site configs from db", "err", err) 189 + } 190 + 191 + go func() { 192 + ctx := context.Background() 193 + 194 + // Delete each repo's R2 objects. 195 + for _, sc := range siteConfigs { 196 + if err := sites.Delete(ctx, s.CfClient, did, sc.RepoName); err != nil { 197 + s.Logger.Error("releaseSitesDomain: R2 delete failed", "did", did, "repo", sc.RepoName, "err", err) 198 + } 199 + } 200 + 201 + // Delete the single KV entry for the domain. 202 + if err := sites.DeleteAllDomainMappings(ctx, s.CfClient, domain); err != nil { 203 + s.Logger.Error("releaseSitesDomain: KV delete failed", "domain", domain, "err", err) 204 + } 205 + }() 206 + } 207 + 174 208 s.Pages.HxLocation(w, "/settings/sites") 175 209 } 176 210 ··· 211 245 212 246 prefs, err := db.GetNotificationPreference(s.Db, did) 213 247 if err != nil { 214 - log.Printf("failed to get notification preferences: %s", err) 248 + s.Logger.Error("failed to get notification preferences", "err", err) 215 249 s.Pages.Notice(w, "settings-notifications-error", "Unable to load notification preferences.") 216 250 return 217 251 } ··· 241 275 242 276 err := s.Db.UpdateNotificationPreferences(r.Context(), prefs) 243 277 if err != nil { 244 - log.Printf("failed to update notification preferences: %s", err) 278 + s.Logger.Error("failed to update notification preferences", "err", err) 245 279 s.Pages.Notice(w, "settings-notifications-error", "Unable to save notification preferences.") 246 280 return 247 281 } ··· 253 287 user := s.OAuth.GetMultiAccountUser(r) 254 288 pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Active.Did) 255 289 if err != nil { 256 - log.Println(err) 290 + s.Logger.Error("keys settings", "err", err) 257 291 } 258 292 259 293 s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{ ··· 266 300 user := s.OAuth.GetMultiAccountUser(r) 267 301 emails, err := db.GetAllEmails(s.Db, user.Active.Did) 268 302 if err != nil { 269 - log.Println(err) 303 + s.Logger.Error("emails settings", "err", err) 270 304 } 271 305 272 306 s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{ ··· 297 331 298 332 err := email.SendEmail(emailToSend) 299 333 if err != nil { 300 - log.Printf("sending email: %s", err) 334 + s.Logger.Error("sending email", "err", err) 301 335 s.Pages.Notice(w, "settings-emails-error", fmt.Sprintf("Unable to send verification email at this moment, try again later. %s", errorContext)) 302 336 return err 303 337 } ··· 309 343 switch r.Method { 310 344 case http.MethodGet: 311 345 s.Pages.Notice(w, "settings-emails", "Unimplemented.") 312 - log.Println("unimplemented") 346 + s.Logger.Warn("emails: unimplemented method") 313 347 return 314 348 case http.MethodPut: 315 349 did := s.OAuth.GetDid(r) ··· 324 358 // check if email already exists in database 325 359 existingEmail, err := db.GetEmail(s.Db, did, emAddr) 326 360 if err != nil && !errors.Is(err, sql.ErrNoRows) { 327 - log.Printf("checking for existing email: %s", err) 361 + s.Logger.Error("checking for existing email", "err", err) 328 362 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 329 363 return 330 364 } ··· 344 378 // Begin transaction 345 379 tx, err := s.Db.Begin() 346 380 if err != nil { 347 - log.Printf("failed to start transaction: %s", err) 381 + s.Logger.Error("failed to start transaction", "err", err) 348 382 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 349 383 return 350 384 } ··· 356 390 Verified: false, 357 391 VerificationCode: code, 358 392 }); err != nil { 359 - log.Printf("adding email: %s", err) 393 + s.Logger.Error("adding email", "err", err) 360 394 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 361 395 return 362 396 } ··· 367 401 368 402 // Commit transaction 369 403 if err := tx.Commit(); err != nil { 370 - log.Printf("failed to commit transaction: %s", err) 404 + s.Logger.Error("failed to commit add-email transaction", "err", err) 371 405 s.Pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 372 406 return 373 407 } ··· 382 416 // Begin transaction 383 417 tx, err := s.Db.Begin() 384 418 if err != nil { 385 - log.Printf("failed to start transaction: %s", err) 419 + s.Logger.Error("failed to start transaction", "err", err) 386 420 s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 387 421 return 388 422 } 389 423 defer tx.Rollback() 390 424 391 425 if err := db.DeleteEmail(tx, did, emailAddr); err != nil { 392 - log.Printf("deleting email: %s", err) 426 + s.Logger.Error("deleting email", "err", err) 393 427 s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 394 428 return 395 429 } 396 430 397 431 // Commit transaction 398 432 if err := tx.Commit(); err != nil { 399 - log.Printf("failed to commit transaction: %s", err) 433 + s.Logger.Error("failed to commit delete-email transaction", "err", err) 400 434 s.Pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 401 435 return 402 436 } ··· 426 460 427 461 valid, err := db.CheckValidVerificationCode(s.Db, did, emailAddr, code) 428 462 if err != nil { 429 - log.Printf("checking email verification: %s", err) 463 + s.Logger.Error("checking email verification", "err", err) 430 464 s.Pages.Notice(w, "settings-emails-error", "Error verifying email. Please try again later.") 431 465 return 432 466 } ··· 438 472 439 473 // Mark email as verified in the database 440 474 if err := db.MarkEmailVerified(s.Db, did, emailAddr); err != nil { 441 - log.Printf("marking email as verified: %s", err) 475 + s.Logger.Error("marking email as verified", "err", err) 442 476 s.Pages.Notice(w, "settings-emails-error", "Error updating email verification status. Please try again later.") 443 477 return 444 478 } ··· 467 501 if errors.Is(err, sql.ErrNoRows) { 468 502 s.Pages.Notice(w, "settings-emails-error", "Email not found. Please add it first.") 469 503 } else { 470 - log.Printf("checking for existing email: %s", err) 504 + s.Logger.Error("checking for existing email", "err", err) 471 505 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 472 506 } 473 507 return ··· 494 528 // Begin transaction 495 529 tx, err := s.Db.Begin() 496 530 if err != nil { 497 - log.Printf("failed to start transaction: %s", err) 531 + s.Logger.Error("failed to start transaction", "err", err) 498 532 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 499 533 return 500 534 } ··· 502 536 503 537 // Update the verification code and last sent time 504 538 if err := db.UpdateVerificationCode(tx, did, emAddr, code); err != nil { 505 - log.Printf("updating email verification: %s", err) 539 + s.Logger.Error("updating email verification code", "err", err) 506 540 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 507 541 return 508 542 } ··· 514 548 515 549 // Commit transaction 516 550 if err := tx.Commit(); err != nil { 517 - log.Printf("failed to commit transaction: %s", err) 551 + s.Logger.Error("failed to commit resend-verification transaction", "err", err) 518 552 s.Pages.Notice(w, "settings-emails-error", "Unable to resend verification email at this moment, try again later.") 519 553 return 520 554 } ··· 533 567 } 534 568 535 569 if err := db.MakeEmailPrimary(s.Db, did, emailAddr); err != nil { 536 - log.Printf("setting primary email: %s", err) 570 + s.Logger.Error("setting primary email", "err", err) 537 571 s.Pages.Notice(w, "settings-emails-error", "Error setting primary email. Please try again later.") 538 572 return 539 573 } ··· 545 579 switch r.Method { 546 580 case http.MethodGet: 547 581 s.Pages.Notice(w, "settings-keys", "Unimplemented.") 548 - log.Println("unimplemented") 582 + s.Logger.Warn("keys: unimplemented method") 549 583 return 550 584 case http.MethodPut: 551 585 did := s.OAuth.GetDid(r) ··· 560 594 561 595 _, _, _, _, err = ssh.ParseAuthorizedKey([]byte(key)) 562 596 if err != nil { 563 - log.Printf("parsing public key: %s", err) 597 + s.Logger.Error("parsing public key", "err", err) 564 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.") 565 599 return 566 600 } ··· 569 603 570 604 tx, err := s.Db.Begin() 571 605 if err != nil { 572 - 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) 573 607 s.Pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") 574 608 return 575 609 } 576 610 defer tx.Rollback() 577 611 578 612 if err := db.AddPublicKey(tx, did, name, key, rkey); err != nil { 579 - log.Printf("adding public key: %s", err) 613 + s.Logger.Error("adding public key", "err", err) 580 614 s.Pages.Notice(w, "settings-keys", "Failed to add public key.") 581 615 return 582 616 } ··· 595 629 }) 596 630 // invalid record 597 631 if err != nil { 598 - log.Printf("failed to create record: %s", err) 632 + s.Logger.Error("failed to create atproto record", "err", err) 599 633 s.Pages.Notice(w, "settings-keys", "Failed to create record.") 600 634 return 601 635 } 602 636 603 - log.Println("created atproto record: ", resp.Uri) 637 + s.Logger.Info("created atproto record", "uri", resp.Uri) 604 638 605 639 err = tx.Commit() 606 640 if err != nil { 607 - log.Printf("failed to commit tx; adding public key: %s", err) 641 + s.Logger.Error("failed to commit add-key transaction", "err", err) 608 642 s.Pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") 609 643 return 610 644 } ··· 620 654 rkey := q.Get("rkey") 621 655 key := q.Get("key") 622 656 623 - log.Println(name) 624 - log.Println(rkey) 625 - log.Println(key) 657 + s.Logger.Debug("deleting key", "name", name, "rkey", rkey, "key", key) 626 658 627 659 client, err := s.OAuth.AuthorizedClient(r) 628 660 if err != nil { 629 - log.Printf("failed to authorize client: %s", err) 661 + s.Logger.Error("failed to authorize client", "err", err) 630 662 s.Pages.Notice(w, "settings-keys", "Failed to authorize client.") 631 663 return 632 664 } 633 665 634 666 if err := db.DeletePublicKey(s.Db, did, name, key); err != nil { 635 - log.Printf("removing public key: %s", err) 667 + s.Logger.Error("removing public key", "err", err) 636 668 s.Pages.Notice(w, "settings-keys", "Failed to remove public key.") 637 669 return 638 670 } ··· 647 679 648 680 // invalid record 649 681 if err != nil { 650 - log.Printf("failed to delete record from PDS: %s", err) 682 + s.Logger.Error("failed to delete record from PDS", "err", err) 651 683 s.Pages.Notice(w, "settings-keys", "Failed to remove key from PDS.") 652 684 return 653 685 } 654 686 } 655 - log.Println("deleted successfully") 687 + s.Logger.Info("deleted key successfully", "name", name) 656 688 657 689 s.Pages.HxLocation(w, "/settings/keys") 658 690 return
+9 -9
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 } ··· 119 119 switch r.Method { 120 120 case http.MethodGet: 121 121 s.pages.Signup(w, pages.SignupParams{ 122 - CloudflareSiteKey: s.config.Cloudflare.TurnstileSiteKey, 122 + CloudflareSiteKey: s.config.Cloudflare.Turnstile.SiteKey, 123 123 }) 124 124 case http.MethodPost: 125 125 if s.cf == nil { ··· 281 281 } 282 282 283 283 // step 2: create DNS record with actual DID 284 - recordID, err = s.cf.CreateDNSRecord(ctx, dns.Record{ 284 + recordID, err = s.cf.CreateDNSRecord(ctx, cloudflare.DNSRecord{ 285 285 Type: "TXT", 286 286 Name: "_atproto." + username, 287 287 Content: fmt.Sprintf(`"did=%s"`, did), ··· 355 355 return errors.New("captcha token is empty") 356 356 } 357 357 358 - if s.config.Cloudflare.TurnstileSecretKey == "" { 358 + if s.config.Cloudflare.Turnstile.SecretKey == "" { 359 359 return errors.New("turnstile secret key not configured") 360 360 } 361 361 362 362 data := url.Values{} 363 - data.Set("secret", s.config.Cloudflare.TurnstileSecretKey) 363 + data.Set("secret", s.config.Cloudflare.Turnstile.SecretKey) 364 364 data.Set("response", cfToken) 365 365 366 366 // include the client IP if we have it
+7 -4
appview/state/router.go
··· 209 209 210 210 func (s *State) SettingsRouter() http.Handler { 211 211 settings := &settings.Settings{ 212 - Db: s.db, 213 - OAuth: s.oauth, 214 - Pages: s.pages, 215 - Config: s.config, 212 + Db: s.db, 213 + OAuth: s.oauth, 214 + Pages: s.pages, 215 + Config: s.config, 216 + CfClient: s.cfClient, 217 + Logger: log.SubLogger(s.logger, "settings"), 216 218 } 217 219 218 220 return settings.Router() ··· 315 317 s.enforcer, 316 318 log.SubLogger(s.logger, "repo"), 317 319 s.validator, 320 + s.cfClient, 318 321 ) 319 322 return repo.Router(mw) 320 323 }
+29 -17
appview/state/state.go
··· 12 12 13 13 "tangled.org/core/api/tangled" 14 14 "tangled.org/core/appview" 15 + "tangled.org/core/appview/cloudflare" 15 16 "tangled.org/core/appview/config" 16 17 "tangled.org/core/appview/db" 17 18 "tangled.org/core/appview/indexer" ··· 60 61 spindlestream *eventconsumer.Consumer 61 62 logger *slog.Logger 62 63 validator *validator.Validator 64 + cfClient *cloudflare.Client 63 65 } 64 66 65 67 func Make(ctx context.Context, config *config.Config) (*State, error) { ··· 168 170 notifier := notify.NewMergedNotifier(notifiers) 169 171 notifier = notify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) 170 172 171 - knotstream, err := Knotstream(ctx, config, d, enforcer, posthog, notifier) 173 + var cfClient *cloudflare.Client 174 + if config.Cloudflare.ApiToken != "" { 175 + cfClient, err = cloudflare.New(config) 176 + if err != nil { 177 + logger.Warn("failed to create cloudflare client, sites upload will be disabled", "err", err) 178 + cfClient = nil 179 + } 180 + } 181 + 182 + knotstream, err := Knotstream(ctx, config, d, enforcer, posthog, notifier, cfClient) 172 183 if err != nil { 173 184 return nil, fmt.Errorf("failed to start knotstream consumer: %w", err) 174 185 } ··· 181 192 spindlestream.Start(ctx) 182 193 183 194 state := &State{ 184 - d, 185 - notifier, 186 - indexer, 187 - oauth, 188 - enforcer, 189 - pages, 190 - res, 191 - mentionsResolver, 192 - posthog, 193 - jc, 194 - config, 195 - repoResolver, 196 - knotstream, 197 - spindlestream, 198 - logger, 199 - validator, 195 + db: d, 196 + notifier: notifier, 197 + indexer: indexer, 198 + oauth: oauth, 199 + enforcer: enforcer, 200 + pages: pages, 201 + idResolver: res, 202 + mentionsResolver: mentionsResolver, 203 + posthog: posthog, 204 + jc: jc, 205 + config: config, 206 + repoResolver: repoResolver, 207 + knotstream: knotstream, 208 + spindlestream: spindlestream, 209 + logger: logger, 210 + validator: validator, 211 + cfClient: cfClient, 200 212 } 201 213 202 214 return state, nil