···302302// GetUserByDID retrieves a user by DID
303303func GetUserByDID(db *sql.DB, did string) (*User, error) {
304304 var user User
305305+ var avatar sql.NullString
305306 err := db.QueryRow(`
306307 SELECT did, handle, pds_endpoint, avatar, last_seen
307308 FROM users
308309 WHERE did = ?
309309- `, did).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &user.Avatar, &user.LastSeen)
310310+ `, did).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &avatar, &user.LastSeen)
310311311312 if err == sql.ErrNoRows {
312313 return nil, nil
313314 }
314315 if err != nil {
315316 return nil, err
317317+ }
318318+319319+ // Handle NULL avatar
320320+ if avatar.Valid {
321321+ user.Avatar = avatar.String
316322 }
317323318324 return &user, nil
···321327// GetUserByHandle retrieves a user by handle
322328func GetUserByHandle(db *sql.DB, handle string) (*User, error) {
323329 var user User
330330+ var avatar sql.NullString
324331 err := db.QueryRow(`
325332 SELECT did, handle, pds_endpoint, avatar, last_seen
326333 FROM users
327334 WHERE handle = ?
328328- `, handle).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &user.Avatar, &user.LastSeen)
335335+ `, handle).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &avatar, &user.LastSeen)
329336330337 if err == sql.ErrNoRows {
331338 return nil, nil
332339 }
333340 if err != nil {
334341 return nil, err
342342+ }
343343+344344+ // Handle NULL avatar
345345+ if avatar.Valid {
346346+ user.Avatar = avatar.String
335347 }
336348337349 return &user, nil
+28
pkg/appview/jetstream/processor.go
···285285// This is called when Jetstream receives an identity event indicating a handle change.
286286// The identity cache is invalidated to ensure the next lookup uses the new handle,
287287// and the database is updated to reflect the change in the UI.
288288+//
289289+// Only processes events for users who already exist in our database (have ATCR activity).
288290func (p *Processor) ProcessIdentity(ctx context.Context, did string, newHandle string) error {
291291+ // Check if user exists in our database - only update if they're an ATCR user
292292+ user, err := db.GetUserByDID(p.db, did)
293293+ if err != nil {
294294+ return fmt.Errorf("failed to check user existence: %w", err)
295295+ }
296296+297297+ // Skip if user doesn't exist - they don't have any ATCR activity (manifests, profiles, etc.)
298298+ if user == nil {
299299+ return nil
300300+ }
301301+289302 // Update handle in database
290303 if err := db.UpdateUserHandle(p.db, did, newHandle); err != nil {
291304 slog.Warn("Failed to update user handle in database",
···308321 slog.Info("Processed identity change event",
309322 "component", "processor",
310323 "did", did,
324324+ "old_handle", user.Handle,
311325 "new_handle", newHandle)
312326313327 return nil
···326340// - If truly deactivated: Resolution fails and user won't appear in new queries
327341//
328342// This approach prevents data loss from PDS migrations while still handling deactivations.
343343+//
344344+// Only processes events for users who already exist in our database (have ATCR activity).
329345func (p *Processor) ProcessAccount(ctx context.Context, did string, active bool, status string) error {
330346 // Only process deactivation events
331347 if active || status != "deactivated" {
332348 return nil
333349 }
334350351351+ // Check if user exists in our database - only update if they're an ATCR user
352352+ user, err := db.GetUserByDID(p.db, did)
353353+ if err != nil {
354354+ return fmt.Errorf("failed to check user existence: %w", err)
355355+ }
356356+357357+ // Skip if user doesn't exist - they don't have any ATCR activity
358358+ if user == nil {
359359+ return nil
360360+ }
361361+335362 // Invalidate cached identity data to force re-resolution on next lookup
336363 // This will discover if the account was migrated (new PDS) or truly deactivated (resolution fails)
337364 if err := atproto.InvalidateIdentity(ctx, did); err != nil {
···345372 slog.Info("Processed account deactivation event - cache invalidated",
346373 "component", "processor",
347374 "did", did,
375375+ "handle", user.Handle,
348376 "status", status)
349377350378 return nil
+2-13
pkg/appview/jetstream/worker.go
···443443 }
444444445445 identity := event.Identity
446446- slog.Info("Jetstream processing identity event",
447447- "did", identity.DID,
448448- "handle", identity.Handle,
449449- "seq", identity.Seq)
450450-451451- // Process via shared processor
446446+ // Process via shared processor (only ATCR users will be logged at Info level)
452447 return w.processor.ProcessIdentity(context.Background(), identity.DID, identity.Handle)
453448}
454449···459454 }
460455461456 account := event.Account
462462- slog.Info("Jetstream processing account event",
463463- "did", account.DID,
464464- "active", account.Active,
465465- "status", account.Status,
466466- "seq", account.Seq)
467467-468468- // Process via shared processor
457457+ // Process via shared processor (only ATCR users will be logged at Info level)
469458 return w.processor.ProcessAccount(context.Background(), account.DID, account.Active, account.Status)
470459}
471460