this repo has no description

feat: Update session model to include PDS and refactor session storage logic

+67 -48
+15 -29
lib/api.dart
··· 300 return null; 301 } 302 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 303 - final issuer = session.session.issuer; 304 final did = session.session.subject; 305 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 306 final record = { 307 'collection': 'social.grain.gallery', 308 'repo': did, ··· 391 return null; 392 } 393 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 394 - final issuer = session.session.issuer; 395 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.uploadBlob'); 396 397 // Detect MIME type, fallback to application/octet-stream if unknown 398 String? mimeType = lookupMimeType(file.path); ··· 439 return null; 440 } 441 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 442 - final issuer = session.session.issuer; 443 final did = session.session.subject; 444 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 445 final record = { 446 'collection': 'social.grain.photo', 447 'repo': did, ··· 480 return null; 481 } 482 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 483 - final issuer = session.session.issuer; 484 final did = session.session.subject; 485 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 486 final record = { 487 'collection': 'social.grain.gallery.item', 488 'repo': did, ··· 523 return null; 524 } 525 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 526 - final issuer = session.session.issuer; 527 final did = session.session.subject; 528 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 529 final record = { 530 'collection': 'social.grain.comment', 531 'repo': did, ··· 562 return null; 563 } 564 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 565 - final issuer = session.session.issuer; 566 final did = session.session.subject; 567 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 568 final record = { 569 'collection': 'social.grain.favorite', 570 'repo': did, ··· 594 return null; 595 } 596 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 597 - final issuer = session.session.issuer; 598 final did = session.session.subject; 599 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 600 final record = { 601 'collection': 'social.grain.graph.follow', 602 'repo': did, ··· 628 return false; 629 } 630 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 631 - final issuer = session.session.issuer; 632 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.deleteRecord'); 633 final repo = session.session.subject; 634 if (repo.isEmpty) { 635 appLogger.w('No repo (DID) available from session for deleteRecord'); ··· 679 return false; 680 } 681 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 682 - final issuer = session.session.issuer; 683 final did = session.session.subject; 684 // Fetch the raw profile record from atproto getRecord endpoint 685 final getUrl = Uri.parse( 686 - '$issuer/xrpc/com.atproto.repo.getRecord?repo=$did&collection=social.grain.actor.profile&rkey=self', 687 ); 688 final getResp = await dpopClient.send( 689 method: 'GET', ··· 713 } 714 } 715 // Update the profile record 716 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.putRecord'); 717 final record = { 718 'collection': 'social.grain.actor.profile', 719 'repo': did, ··· 781 return false; 782 } 783 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 784 - final issuer = session.session.issuer; 785 final did = session.session.subject; 786 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.applyWrites'); 787 788 final updates = <Map<String, dynamic>>[]; 789 int position = 0; ··· 843 return false; 844 } 845 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 846 - final issuer = session.session.issuer; 847 final did = session.session.subject; 848 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.putRecord'); 849 // Extract rkey from galleryUri 850 String rkey = ''; 851 try { ··· 905 return null; 906 } 907 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 908 - final issuer = session.session.issuer; 909 final did = session.session.subject; 910 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 911 final record = { 912 'collection': 'social.grain.photo.exif', 913 'repo': did, ··· 955 return false; 956 } 957 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 958 - final issuer = session.session.issuer; 959 final did = session.session.subject; 960 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.applyWrites'); 961 962 // Fetch current photo records for all photos 963 final photoRecords = await fetchPhotoRecords(); ··· 1032 return {}; 1033 } 1034 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 1035 - final issuer = session.session.issuer; 1036 final did = session.session.subject; 1037 final url = Uri.parse( 1038 - '$issuer/xrpc/com.atproto.repo.listRecords?repo=$did&collection=social.grain.photo', 1039 ); 1040 1041 final response = await dpopClient.send(
··· 300 return null; 301 } 302 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 303 final did = session.session.subject; 304 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.createRecord'); 305 final record = { 306 'collection': 'social.grain.gallery', 307 'repo': did, ··· 390 return null; 391 } 392 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 393 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.uploadBlob'); 394 395 // Detect MIME type, fallback to application/octet-stream if unknown 396 String? mimeType = lookupMimeType(file.path); ··· 437 return null; 438 } 439 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 440 final did = session.session.subject; 441 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.createRecord'); 442 final record = { 443 'collection': 'social.grain.photo', 444 'repo': did, ··· 477 return null; 478 } 479 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 480 final did = session.session.subject; 481 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.createRecord'); 482 final record = { 483 'collection': 'social.grain.gallery.item', 484 'repo': did, ··· 519 return null; 520 } 521 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 522 final did = session.session.subject; 523 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.createRecord'); 524 final record = { 525 'collection': 'social.grain.comment', 526 'repo': did, ··· 557 return null; 558 } 559 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 560 final did = session.session.subject; 561 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.createRecord'); 562 final record = { 563 'collection': 'social.grain.favorite', 564 'repo': did, ··· 588 return null; 589 } 590 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 591 final did = session.session.subject; 592 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.createRecord'); 593 final record = { 594 'collection': 'social.grain.graph.follow', 595 'repo': did, ··· 621 return false; 622 } 623 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 624 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.deleteRecord'); 625 final repo = session.session.subject; 626 if (repo.isEmpty) { 627 appLogger.w('No repo (DID) available from session for deleteRecord'); ··· 671 return false; 672 } 673 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 674 final did = session.session.subject; 675 // Fetch the raw profile record from atproto getRecord endpoint 676 final getUrl = Uri.parse( 677 + '${session.pds}/xrpc/com.atproto.repo.getRecord?repo=$did&collection=social.grain.actor.profile&rkey=self', 678 ); 679 final getResp = await dpopClient.send( 680 method: 'GET', ··· 704 } 705 } 706 // Update the profile record 707 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.putRecord'); 708 final record = { 709 'collection': 'social.grain.actor.profile', 710 'repo': did, ··· 772 return false; 773 } 774 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 775 final did = session.session.subject; 776 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.applyWrites'); 777 778 final updates = <Map<String, dynamic>>[]; 779 int position = 0; ··· 833 return false; 834 } 835 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 836 final did = session.session.subject; 837 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.putRecord'); 838 // Extract rkey from galleryUri 839 String rkey = ''; 840 try { ··· 894 return null; 895 } 896 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 897 final did = session.session.subject; 898 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.createRecord'); 899 final record = { 900 'collection': 'social.grain.photo.exif', 901 'repo': did, ··· 943 return false; 944 } 945 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 946 final did = session.session.subject; 947 + final url = Uri.parse('${session.pds}/xrpc/com.atproto.repo.applyWrites'); 948 949 // Fetch current photo records for all photos 950 final photoRecords = await fetchPhotoRecords(); ··· 1019 return {}; 1020 } 1021 final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 1022 final did = session.session.subject; 1023 final url = Uri.parse( 1024 + '${session.pds}/xrpc/com.atproto.repo.listRecords?repo=$did&collection=social.grain.photo', 1025 ); 1026 1027 final response = await dpopClient.send(
+6 -9
lib/auth.dart
··· 37 } 38 39 Future<void> _saveSession(Session session) async { 40 - final atprotoSessionJson = jsonEncode(session.session.toJson()); 41 - await _storage.write(key: 'atproto_session', value: atprotoSessionJson); 42 - await _storage.write(key: 'api_token', value: session.token); 43 } 44 45 Future<Session?> _loadSession() async { 46 - final sessionJsonString = await _storage.read(key: 'atproto_session'); 47 - final token = await _storage.read(key: 'api_token'); 48 - if (sessionJsonString == null || token == null) return null; 49 50 try { 51 final sessionJson = jsonDecode(sessionJsonString); 52 - return Session(session: AtprotoSession.fromJson(sessionJson), token: token); 53 } catch (e) { 54 // Optionally log or clear storage if corrupted 55 return null; ··· 96 // Revoke session on the server 97 await apiService.revokeSession(); 98 // Remove session from secure storage 99 - await _storage.delete(key: 'atproto_session'); 100 - await _storage.delete(key: 'api_token'); 101 // Clear any in-memory session/user data 102 apiService.currentUser = null; 103 }
··· 37 } 38 39 Future<void> _saveSession(Session session) async { 40 + final sessionJson = jsonEncode(session.toJson()); 41 + await _storage.write(key: 'session', value: sessionJson); 42 } 43 44 Future<Session?> _loadSession() async { 45 + final sessionJsonString = await _storage.read(key: 'session'); 46 + if (sessionJsonString == null) return null; 47 48 try { 49 final sessionJson = jsonDecode(sessionJsonString); 50 + return Session.fromJson(sessionJson); 51 } catch (e) { 52 // Optionally log or clear storage if corrupted 53 return null; ··· 94 // Revoke session on the server 95 await apiService.revokeSession(); 96 // Remove session from secure storage 97 + await _storage.delete(key: 'session'); 98 // Clear any in-memory session/user data 99 apiService.currentUser = null; 100 }
+5 -1
lib/models/session.dart
··· 7 8 @freezed 9 class Session with _$Session { 10 - const factory Session({required AtprotoSession session, required String token}) = _Session; 11 12 factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json); 13 }
··· 7 8 @freezed 9 class Session with _$Session { 10 + const factory Session({ 11 + required AtprotoSession session, 12 + required String token, 13 + required String pds, 14 + }) = _Session; 15 16 factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json); 17 }
+35 -8
lib/models/session.freezed.dart
··· 23 mixin _$Session { 24 AtprotoSession get session => throw _privateConstructorUsedError; 25 String get token => throw _privateConstructorUsedError; 26 27 /// Serializes this Session to a JSON map. 28 Map<String, dynamic> toJson() => throw _privateConstructorUsedError; ··· 38 factory $SessionCopyWith(Session value, $Res Function(Session) then) = 39 _$SessionCopyWithImpl<$Res, Session>; 40 @useResult 41 - $Res call({AtprotoSession session, String token}); 42 43 $AtprotoSessionCopyWith<$Res> get session; 44 } ··· 57 /// with the given fields replaced by the non-null parameter values. 58 @pragma('vm:prefer-inline') 59 @override 60 - $Res call({Object? session = null, Object? token = null}) { 61 return _then( 62 _value.copyWith( 63 session: null == session ··· 68 ? _value.token 69 : token // ignore: cast_nullable_to_non_nullable 70 as String, 71 ) 72 as $Val, 73 ); ··· 92 ) = __$$SessionImplCopyWithImpl<$Res>; 93 @override 94 @useResult 95 - $Res call({AtprotoSession session, String token}); 96 97 @override 98 $AtprotoSessionCopyWith<$Res> get session; ··· 111 /// with the given fields replaced by the non-null parameter values. 112 @pragma('vm:prefer-inline') 113 @override 114 - $Res call({Object? session = null, Object? token = null}) { 115 return _then( 116 _$SessionImpl( 117 session: null == session ··· 122 ? _value.token 123 : token // ignore: cast_nullable_to_non_nullable 124 as String, 125 ), 126 ); 127 } ··· 130 /// @nodoc 131 @JsonSerializable() 132 class _$SessionImpl implements _Session { 133 - const _$SessionImpl({required this.session, required this.token}); 134 135 factory _$SessionImpl.fromJson(Map<String, dynamic> json) => 136 _$$SessionImplFromJson(json); ··· 139 final AtprotoSession session; 140 @override 141 final String token; 142 143 @override 144 String toString() { 145 - return 'Session(session: $session, token: $token)'; 146 } 147 148 @override ··· 151 (other.runtimeType == runtimeType && 152 other is _$SessionImpl && 153 (identical(other.session, session) || other.session == session) && 154 - (identical(other.token, token) || other.token == token)); 155 } 156 157 @JsonKey(includeFromJson: false, includeToJson: false) 158 @override 159 - int get hashCode => Object.hash(runtimeType, session, token); 160 161 /// Create a copy of Session 162 /// with the given fields replaced by the non-null parameter values. ··· 176 const factory _Session({ 177 required final AtprotoSession session, 178 required final String token, 179 }) = _$SessionImpl; 180 181 factory _Session.fromJson(Map<String, dynamic> json) = _$SessionImpl.fromJson; ··· 184 AtprotoSession get session; 185 @override 186 String get token; 187 188 /// Create a copy of Session 189 /// with the given fields replaced by the non-null parameter values.
··· 23 mixin _$Session { 24 AtprotoSession get session => throw _privateConstructorUsedError; 25 String get token => throw _privateConstructorUsedError; 26 + String get pds => throw _privateConstructorUsedError; 27 28 /// Serializes this Session to a JSON map. 29 Map<String, dynamic> toJson() => throw _privateConstructorUsedError; ··· 39 factory $SessionCopyWith(Session value, $Res Function(Session) then) = 40 _$SessionCopyWithImpl<$Res, Session>; 41 @useResult 42 + $Res call({AtprotoSession session, String token, String pds}); 43 44 $AtprotoSessionCopyWith<$Res> get session; 45 } ··· 58 /// with the given fields replaced by the non-null parameter values. 59 @pragma('vm:prefer-inline') 60 @override 61 + $Res call({ 62 + Object? session = null, 63 + Object? token = null, 64 + Object? pds = null, 65 + }) { 66 return _then( 67 _value.copyWith( 68 session: null == session ··· 73 ? _value.token 74 : token // ignore: cast_nullable_to_non_nullable 75 as String, 76 + pds: null == pds 77 + ? _value.pds 78 + : pds // ignore: cast_nullable_to_non_nullable 79 + as String, 80 ) 81 as $Val, 82 ); ··· 101 ) = __$$SessionImplCopyWithImpl<$Res>; 102 @override 103 @useResult 104 + $Res call({AtprotoSession session, String token, String pds}); 105 106 @override 107 $AtprotoSessionCopyWith<$Res> get session; ··· 120 /// with the given fields replaced by the non-null parameter values. 121 @pragma('vm:prefer-inline') 122 @override 123 + $Res call({ 124 + Object? session = null, 125 + Object? token = null, 126 + Object? pds = null, 127 + }) { 128 return _then( 129 _$SessionImpl( 130 session: null == session ··· 135 ? _value.token 136 : token // ignore: cast_nullable_to_non_nullable 137 as String, 138 + pds: null == pds 139 + ? _value.pds 140 + : pds // ignore: cast_nullable_to_non_nullable 141 + as String, 142 ), 143 ); 144 } ··· 147 /// @nodoc 148 @JsonSerializable() 149 class _$SessionImpl implements _Session { 150 + const _$SessionImpl({ 151 + required this.session, 152 + required this.token, 153 + required this.pds, 154 + }); 155 156 factory _$SessionImpl.fromJson(Map<String, dynamic> json) => 157 _$$SessionImplFromJson(json); ··· 160 final AtprotoSession session; 161 @override 162 final String token; 163 + @override 164 + final String pds; 165 166 @override 167 String toString() { 168 + return 'Session(session: $session, token: $token, pds: $pds)'; 169 } 170 171 @override ··· 174 (other.runtimeType == runtimeType && 175 other is _$SessionImpl && 176 (identical(other.session, session) || other.session == session) && 177 + (identical(other.token, token) || other.token == token) && 178 + (identical(other.pds, pds) || other.pds == pds)); 179 } 180 181 @JsonKey(includeFromJson: false, includeToJson: false) 182 @override 183 + int get hashCode => Object.hash(runtimeType, session, token, pds); 184 185 /// Create a copy of Session 186 /// with the given fields replaced by the non-null parameter values. ··· 200 const factory _Session({ 201 required final AtprotoSession session, 202 required final String token, 203 + required final String pds, 204 }) = _$SessionImpl; 205 206 factory _Session.fromJson(Map<String, dynamic> json) = _$SessionImpl.fromJson; ··· 209 AtprotoSession get session; 210 @override 211 String get token; 212 + @override 213 + String get pds; 214 215 /// Create a copy of Session 216 /// with the given fields replaced by the non-null parameter values.
+6 -1
lib/models/session.g.dart
··· 10 _$SessionImpl( 11 session: AtprotoSession.fromJson(json['session'] as Map<String, dynamic>), 12 token: json['token'] as String, 13 ); 14 15 Map<String, dynamic> _$$SessionImplToJson(_$SessionImpl instance) => 16 - <String, dynamic>{'session': instance.session, 'token': instance.token};
··· 10 _$SessionImpl( 11 session: AtprotoSession.fromJson(json['session'] as Map<String, dynamic>), 12 token: json['token'] as String, 13 + pds: json['pds'] as String, 14 ); 15 16 Map<String, dynamic> _$$SessionImplToJson(_$SessionImpl instance) => 17 + <String, dynamic>{ 18 + 'session': instance.session, 19 + 'token': instance.token, 20 + 'pds': instance.pds, 21 + };