Monorepo for Tangled
0

Configure Feed

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

appview/oauth: navigate first-time tangled users to welcome screen

Signed-off-by: oppiliappan <me@oppi.li>

author
oppiliappan
committer
Tangled
date (Jul 16, 2026, 4:33 PM +0300) commit 027b82da parent dd8e4a4e change-id qzwqrknw
+73 -43
+44
appview/db/profile.go
··· 398 398 return syntax.DID(did), nil 399 399 } 400 400 401 + // whether a DID has authored any Tangled record, counts some common records towards this 402 + func IsTangledUser(e Execer, did string) (bool, error) { 403 + profile, err := GetProfile(e, did) 404 + if err != nil { 405 + return false, err 406 + } 407 + if profile != nil { 408 + return true, nil 409 + } 410 + 411 + keys, err := GetPublicKeysForDid(e, did) 412 + if err != nil { 413 + return false, err 414 + } 415 + if len(keys) > 0 { 416 + return true, nil 417 + } 418 + 419 + counts := []func() (int64, error){ 420 + func() (int64, error) { return CountRepos(e, orm.FilterEq("did", did)) }, 421 + func() (int64, error) { return CountStrings(e, orm.FilterEq("did", did)) }, 422 + func() (int64, error) { return CountStars(e, orm.FilterEq("did", did)) }, 423 + } 424 + for _, count := range counts { 425 + n, err := count() 426 + if err != nil { 427 + return false, err 428 + } 429 + if n > 0 { 430 + return true, nil 431 + } 432 + } 433 + 434 + stats, err := GetFollowerFollowingCount(e, did) 435 + if err != nil { 436 + return false, err 437 + } 438 + if stats.Following > 0 { 439 + return true, nil 440 + } 441 + 442 + return false, nil 443 + } 444 + 401 445 func GetProfile(e Execer, did string) (*models.Profile, error) { 402 446 var profile models.Profile 403 447 var pronouns sql.Null[string]
+23 -43
appview/oauth/handler.go
··· 15 15 comatproto "github.com/bluesky-social/indigo/api/atproto" 16 16 "github.com/bluesky-social/indigo/atproto/auth/oauth" 17 17 "github.com/bluesky-social/indigo/atproto/syntax" 18 - lexutil "github.com/bluesky-social/indigo/lex/util" 19 18 xrpc "github.com/bluesky-social/indigo/xrpc" 20 19 "github.com/go-chi/chi/v5" 21 20 "github.com/posthog/posthog-go" ··· 95 94 96 95 o.Logger.Debug("session saved successfully") 97 96 97 + did := sessData.AccountDID.String() 98 + 99 + // default to true, so users don't have to onboard again 100 + isTangledUser, err := db.IsTangledUser(o.Db, did) 101 + if err != nil { 102 + isTangledUser = true 103 + } 104 + 105 + isNewUser := !isTangledUser 106 + if isNewUser { 107 + if ob, _ := db.GetOnboarding(o.Db, did); ob == nil { 108 + if err := db.UpsertOnboarding(o.Db, &models.Onboarding{ 109 + Did: did, 110 + Step: models.OnboardingStepProfile, 111 + Status: models.OnboardingInProgress, 112 + }); err != nil { 113 + o.Logger.Error("failed to seed onboarding record", "did", did, "err", err) 114 + } 115 + } 116 + } 117 + 98 118 go o.addToDefaultKnot(sessData.AccountDID) 99 119 go o.addToDefaultSpindle(sessData.AccountDID.String()) 100 - go o.ensureTangledProfile(sessData) 101 120 go o.autoClaimTnglShDomain(sessData.AccountDID.String()) 102 121 103 122 if !o.Config.Core.Dev { ··· 116 135 117 136 if o.isAccountDeactivated(sessData) { 118 137 redirectURL = "/settings/profile" 138 + } else if isNewUser { 139 + redirectURL = "/welcome" 119 140 } 120 141 121 142 http.Redirect(w, r, redirectURL, http.StatusFound) ··· 302 323 } 303 324 304 325 return nil 305 - } 306 - 307 - func (o *OAuth) ensureTangledProfile(sessData *oauth.ClientSessionData) { 308 - ctx := context.Background() 309 - did := sessData.AccountDID.String() 310 - l := o.Logger.With("did", did) 311 - 312 - profile, _ := db.GetProfile(o.Db, did) 313 - if profile != nil { 314 - l.Debug("profile already exists in DB") 315 - return 316 - } 317 - 318 - l.Debug("creating empty Tangled profile") 319 - 320 - sess, err := o.resumeSession(ctx, sessData.AccountDID, sessData.SessionID) 321 - if err != nil { 322 - l.Error("failed to resume session for profile creation", "err", err) 323 - return 324 - } 325 - client := sess.APIClient() 326 - 327 - _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 328 - Collection: tangled.ActorProfileNSID, 329 - Repo: did, 330 - Rkey: "self", 331 - Record: &lexutil.LexiconTypeDecoder{Val: &tangled.ActorProfile{}}, 332 - }) 333 - 334 - if err != nil { 335 - l.Error("failed to create empty profile on PDS", "err", err) 336 - return 337 - } 338 - 339 - emptyProfile := &models.Profile{Did: did} 340 - if err := db.UpsertProfile(o.Db, emptyProfile); err != nil { 341 - l.Error("failed to create empty profile in DB", "err", err) 342 - return 343 - } 344 - 345 - l.Debug("successfully created empty Tangled profile on PDS and DB") 346 326 } 347 327 348 328 // create a AppPasswordSession using apppasswords
+6
appview/state/profile.go
··· 90 90 profile = &models.Profile{Did: did} 91 91 } 92 92 93 + isTangledUser, err := db.IsTangledUser(s.db, did) 94 + if err != nil { 95 + return nil, fmt.Errorf("failed to determine tangled user status: %w", err) 96 + } 97 + 93 98 repoCount, err := db.CountRepos(s.db, orm.FilterEq("did", did)) 94 99 if err != nil { 95 100 return nil, fmt.Errorf("failed to get repo count: %w", err) ··· 141 146 return &pages.ProfileCard{ 142 147 UserDid: did, 143 148 HasProfile: hasProfile, 149 + IsTangledUser: isTangledUser, 144 150 Profile: profile, 145 151 FollowStatus: followStatus, 146 152 VouchRelationship: vouchRelationship,