1)修改数据库命名,不和业务使用重合。
2)修改user 表,uid为unique, 聊天室文件夹name unique移除
This commit is contained in:
31
apps/im_app/assets/loginData.json
Normal file
31
apps/im_app/assets/loginData.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "OK",
|
||||||
|
"data": {
|
||||||
|
"account_id": "1713925030yFMUBu",
|
||||||
|
"profile": {
|
||||||
|
"uid": 2137067,
|
||||||
|
"uuid": "1713925030yFMUBu",
|
||||||
|
"last_online": 1772819822,
|
||||||
|
"profile_pic": "Image/7e/f5/7ef5b60dd83a34a74c164a21fbd1f098/7ef5b60dd83a34a74c164a21fbd1f098.jpg",
|
||||||
|
"profile_pic_gaussian": "",
|
||||||
|
"nickname": "Happi(哈比)",
|
||||||
|
"contact": "86552205",
|
||||||
|
"country_code": "+65",
|
||||||
|
"email": "happi@winwayinfo.com",
|
||||||
|
"recovery_email": "",
|
||||||
|
"username": "happi",
|
||||||
|
"bio": "",
|
||||||
|
"relationship": 2,
|
||||||
|
"user_alias": null,
|
||||||
|
"channel_id": 1,
|
||||||
|
"channel_group_id": 1,
|
||||||
|
"hint": "1111"
|
||||||
|
},
|
||||||
|
"nonce": "",
|
||||||
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIxMzcwNjcsInVkaWQiOjI2NjI2MzcsInNpZCI6IjE3NzI4NjU0NDc0Z1c2VWc9PSIsImlzcyI6ImFiYy5jb20iLCJhdWQiOlsidXNlciJdLCJleHAiOjE3NzQxNjE0NDcsIm5iZiI6MTc3Mjg2NTQ0NywiaWF0IjoxNzcyODY1NDQ3fQ.gUL6hyKgyPP8Tw9y7kRSq-ndNKfV9uGFhU4YKiQzg0I",
|
||||||
|
"refresh_token": "ps0FF3XayvnJB_P8Cnfu7w-uD781b1-vfmUjbrONZxI=",
|
||||||
|
"device_id": "SP1A.210812",
|
||||||
|
"login_data": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,25 +29,25 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
Favourite: database.favourites,
|
DriftFavourite: database.favourites,
|
||||||
Sound: database.sounds,
|
DriftSound: database.sounds,
|
||||||
Tag: database.tags,
|
DriftTag: database.tags,
|
||||||
PendingFriendRequestHistory: database.pendingFriendRequestHistories,
|
DriftPendingFriendRequestHistory: database.pendingFriendRequestHistories,
|
||||||
Message: database.messages,
|
DriftMessage: database.messages,
|
||||||
RecentMiniApp: database.recentMiniApps,
|
DriftRecentMiniApp: database.recentMiniApps,
|
||||||
Retry: database.retries,
|
DriftRetry: database.retries,
|
||||||
Group: database.groups,
|
DriftGroup: database.groups,
|
||||||
FavoriteMiniApp: database.favoriteMiniApps,
|
DriftFavoriteMiniApp: database.favoriteMiniApps,
|
||||||
DiscoverMiniApp: database.discoverMiniApps,
|
DriftDiscoverMiniApp: database.discoverMiniApps,
|
||||||
ChatCategory: database.chatCategories,
|
DriftChatCategory: database.chatCategories,
|
||||||
ChatBot: database.chatBots,
|
DriftChatBot: database.chatBots,
|
||||||
FavouriteDetail: database.favouriteDetails,
|
DriftFavouriteDetail: database.favouriteDetails,
|
||||||
UserRequestHistory: database.userRequestHistories,
|
DriftUserRequestHistory: database.userRequestHistories,
|
||||||
Workspace: database.workspaces,
|
DriftWorkspace: database.workspaces,
|
||||||
User: database.users,
|
DriftUser: database.users,
|
||||||
ExploreMiniApp: database.exploreMiniApps,
|
DriftExploreMiniApp: database.exploreMiniApps,
|
||||||
CallLog: database.callLogs,
|
DriftCallLog: database.callLogs,
|
||||||
Chat: database.chats,
|
DriftChat: database.chats,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('CallLog')
|
@DataClassName('DriftCallLog')
|
||||||
class CallLogs extends Table {
|
class CallLogs extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
IntColumn get callerId => integer().nullable()();
|
IntColumn get callerId => integer().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('ChatBot')
|
@DataClassName('DriftChatBot')
|
||||||
class ChatBots extends Table {
|
class ChatBots extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('ChatCategory')
|
@DataClassName('DriftChatCategory')
|
||||||
class ChatCategories extends Table {
|
class ChatCategories extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
TextColumn get name => text().nullable().unique()();
|
TextColumn get name => text().nullable()();
|
||||||
TextColumn get includedChatIds => text().nullable()();
|
TextColumn get includedChatIds => text().nullable()();
|
||||||
TextColumn get excludedChatIds => text().nullable()();
|
TextColumn get excludedChatIds => text().nullable()();
|
||||||
IntColumn get seq => integer().nullable()();
|
IntColumn get seq => integer().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Chat')
|
@DataClassName('DriftChat')
|
||||||
class Chats extends Table {
|
class Chats extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
IntColumn get typ => integer().nullable()();
|
IntColumn get typ => integer().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('DiscoverMiniApp')
|
@DataClassName('DriftDiscoverMiniApp')
|
||||||
class DiscoverMiniApps extends Table {
|
class DiscoverMiniApps extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('ExploreMiniApp')
|
@DataClassName('DriftExploreMiniApp')
|
||||||
class ExploreMiniApps extends Table {
|
class ExploreMiniApps extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('FavoriteMiniApp')
|
@DataClassName('DriftFavoriteMiniApp')
|
||||||
class FavoriteMiniApps extends Table {
|
class FavoriteMiniApps extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('FavouriteDetail')
|
@DataClassName('DriftFavouriteDetail')
|
||||||
class FavouriteDetails extends Table {
|
class FavouriteDetails extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
TextColumn get relatedId => text().withDefault(const Constant(''))();
|
TextColumn get relatedId => text().withDefault(const Constant(''))();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Favourite')
|
@DataClassName('DriftFavourite')
|
||||||
class Favourites extends Table {
|
class Favourites extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
TextColumn get parentId => text().withDefault(const Constant(''))();
|
TextColumn get parentId => text().withDefault(const Constant(''))();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Group')
|
@DataClassName('DriftGroup')
|
||||||
class Groups extends Table {
|
class Groups extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
IntColumn get userJoinDate => integer().nullable()();
|
IntColumn get userJoinDate => integer().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Message')
|
@DataClassName('DriftMessage')
|
||||||
class Messages extends Table {
|
class Messages extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
IntColumn get messageId => integer().nullable()();
|
IntColumn get messageId => integer().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('PendingFriendRequestHistory')
|
@DataClassName('DriftPendingFriendRequestHistory')
|
||||||
class PendingFriendRequestHistories extends Table {
|
class PendingFriendRequestHistories extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
IntColumn get uid => integer()();
|
IntColumn get uid => integer()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('RecentMiniApp')
|
@DataClassName('DriftRecentMiniApp')
|
||||||
class RecentMiniApps extends Table {
|
class RecentMiniApps extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Retry')
|
@DataClassName('DriftRetry')
|
||||||
class Retries extends Table {
|
class Retries extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
IntColumn get uid => integer().nullable()();
|
IntColumn get uid => integer().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Sound')
|
@DataClassName('DriftSound')
|
||||||
class Sounds extends Table {
|
class Sounds extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
TextColumn get filePath => text().withDefault(const Constant(''))();
|
TextColumn get filePath => text().withDefault(const Constant(''))();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Tag')
|
@DataClassName('DriftTag')
|
||||||
class Tags extends Table {
|
class Tags extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
IntColumn get uid => integer().nullable()();
|
IntColumn get uid => integer().nullable()();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('UserRequestHistory')
|
@DataClassName('DriftUserRequestHistory')
|
||||||
class UserRequestHistories extends Table {
|
class UserRequestHistories extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
IntColumn get status => integer().nullable()();
|
IntColumn get status => integer().nullable()();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('User')
|
@DataClassName('DriftUser')
|
||||||
class Users extends Table {
|
class Users extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
IntColumn get uid => integer().nullable()();
|
IntColumn get uid => integer().unique()();
|
||||||
TextColumn get uuid => text().nullable()();
|
TextColumn get uuid => text().nullable()();
|
||||||
IntColumn get lastOnline => integer().nullable()();
|
IntColumn get lastOnline => integer().nullable()();
|
||||||
TextColumn get profilePic => text().nullable()();
|
TextColumn get profilePic => text().nullable()();
|
||||||
@@ -36,6 +36,7 @@ class Users extends Table {
|
|||||||
TextColumn get publicKey => text().nullable()();
|
TextColumn get publicKey => text().nullable()();
|
||||||
IntColumn get configBits => integer().withDefault(const Constant(0))();
|
IntColumn get configBits => integer().withDefault(const Constant(0))();
|
||||||
TextColumn get hint => text().nullable()();
|
TextColumn get hint => text().nullable()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get tableName => 'user';
|
String get tableName => 'user';
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Workspace')
|
@DataClassName('DriftWorkspace')
|
||||||
class Workspaces extends Table {
|
class Workspaces extends Table {
|
||||||
IntColumn get id => integer()();
|
IntColumn get id => integer()();
|
||||||
TextColumn get name => text().nullable()();
|
TextColumn get name => text().nullable()();
|
||||||
|
|||||||
@@ -1,68 +1,148 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:im_app/data/local/drift/app_database.dart';
|
||||||
import '../../domain/entities/user.dart';
|
import 'package:im_app/domain/entities/user.dart';
|
||||||
|
|
||||||
part 'user_dto.g.dart';
|
|
||||||
|
|
||||||
/// 用户 DTO(Data Transfer Object)
|
/// 用户 DTO(Data Transfer Object)
|
||||||
///
|
///
|
||||||
/// local / remote 共用的数据传输对象,放在 data/models/。
|
/// local / remote 共用的数据传输对象,放在 data/models/。
|
||||||
/// 提供与 Domain Entity [User] 之间的双向转换。
|
/// 提供与 Domain Entity [User] 之间的双向转换。
|
||||||
///
|
|
||||||
/// ## 数据流位置(本地存储场景)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// 写入本地:
|
|
||||||
/// LoginData.toEntity() → User
|
|
||||||
/// → UserDto.fromEntity(user) → ★ UserDto ★ ← 你在这里
|
|
||||||
/// → toJson() → SQLite / SharedPreferences
|
|
||||||
///
|
|
||||||
/// 读取本地:
|
|
||||||
/// SQLite / SharedPreferences → JSON
|
|
||||||
/// → ★ UserDto.fromJson() ★ ← 你在这里
|
|
||||||
/// → UserDto.toEntity() → User
|
|
||||||
/// → ViewModel.state → View
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// 注意:登录接口的 Response DTO 是 [LoginData](含 token),
|
|
||||||
/// 本类用于纯用户信息的本地持久化,不含 token。
|
|
||||||
@JsonSerializable()
|
|
||||||
class UserDto {
|
class UserDto {
|
||||||
@JsonKey(name: 'user_id')
|
final int uid;
|
||||||
final String userId;
|
final String? uuid;
|
||||||
final String email;
|
final int? lastOnline;
|
||||||
|
final String? profilePic;
|
||||||
|
final String? profilePicGaussian;
|
||||||
final String? nickname;
|
final String? nickname;
|
||||||
final String? avatar;
|
final String? contact;
|
||||||
|
final String? countryCode;
|
||||||
|
final String? email;
|
||||||
|
final String? recoveryEmail;
|
||||||
|
final String? username;
|
||||||
|
final String? bio;
|
||||||
|
final int? relationship;
|
||||||
|
final String? userAlias;
|
||||||
|
final int? channelId;
|
||||||
|
final int? channelGroupId;
|
||||||
|
final String? hint;
|
||||||
|
|
||||||
const UserDto({
|
const UserDto({
|
||||||
required this.userId,
|
required this.uid,
|
||||||
required this.email,
|
this.uuid,
|
||||||
|
this.lastOnline,
|
||||||
|
this.profilePic,
|
||||||
|
this.profilePicGaussian,
|
||||||
this.nickname,
|
this.nickname,
|
||||||
this.avatar,
|
this.contact,
|
||||||
|
this.countryCode,
|
||||||
|
this.email,
|
||||||
|
this.recoveryEmail,
|
||||||
|
this.username,
|
||||||
|
this.bio,
|
||||||
|
this.relationship,
|
||||||
|
this.userAlias,
|
||||||
|
this.channelId,
|
||||||
|
this.channelGroupId,
|
||||||
|
this.hint,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory UserDto.fromJson(Map<String, dynamic> json) =>
|
factory UserDto.fromJson(Map<String, dynamic> json) => UserDto(
|
||||||
_$UserDtoFromJson(json);
|
uid: json['uid'] as int,
|
||||||
|
uuid: json['uuid'],
|
||||||
|
lastOnline: json['last_online'],
|
||||||
|
profilePic: json['profile_pic'],
|
||||||
|
profilePicGaussian: json['profile_pic_gaussian'] ?? '',
|
||||||
|
nickname: json['nickname'],
|
||||||
|
contact: json['contact'],
|
||||||
|
countryCode: json['country_code'],
|
||||||
|
email: json['email'],
|
||||||
|
recoveryEmail: json['recovery_email'] ?? '',
|
||||||
|
username: json['username'],
|
||||||
|
bio: json['bio'] ?? '',
|
||||||
|
relationship: json['relationship'],
|
||||||
|
userAlias: json['user_alias'],
|
||||||
|
channelId: json['channel_id'],
|
||||||
|
channelGroupId: json['channel_group_id'],
|
||||||
|
hint: json['hint'],
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$UserDtoToJson(this);
|
Map<String, dynamic> toJson() => {
|
||||||
|
'uid': uid,
|
||||||
|
'uuid': uuid,
|
||||||
|
'last_online': lastOnline,
|
||||||
|
'profile_pic': profilePic,
|
||||||
|
'profile_pic_gaussian': profilePicGaussian,
|
||||||
|
'nickname': nickname,
|
||||||
|
'contact': contact,
|
||||||
|
'country_code': countryCode,
|
||||||
|
'email': email,
|
||||||
|
'recovery_email': recoveryEmail,
|
||||||
|
'username': username,
|
||||||
|
'bio': bio,
|
||||||
|
'relationship': relationship,
|
||||||
|
'user_alias': userAlias,
|
||||||
|
'channel_id': channelId,
|
||||||
|
'channel_group_id': channelGroupId,
|
||||||
|
'hint': hint,
|
||||||
|
};
|
||||||
|
|
||||||
/// DTO → Domain Entity
|
/// DTO → Domain Entity
|
||||||
User toEntity() {
|
User toEntity() => User(
|
||||||
return User(
|
uid: uid,
|
||||||
id: userId,
|
uuid: uuid,
|
||||||
email: email,
|
lastOnline: lastOnline,
|
||||||
|
profilePic: profilePic,
|
||||||
|
profilePicGaussian: profilePicGaussian,
|
||||||
nickname: nickname,
|
nickname: nickname,
|
||||||
avatar: avatar,
|
contact: contact,
|
||||||
|
countryCode: countryCode,
|
||||||
|
email: email,
|
||||||
|
recoveryEmail: recoveryEmail,
|
||||||
|
username: username,
|
||||||
|
bio: bio,
|
||||||
|
relationship: relationship,
|
||||||
|
userAlias: userAlias,
|
||||||
|
channelId: channelId,
|
||||||
|
channelGroupId: channelGroupId,
|
||||||
|
hint: hint,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
/// Domain Entity → DTO
|
/// Domain Entity → DTO
|
||||||
factory UserDto.fromEntity(User user) {
|
factory UserDto.fromEntity(User user) => UserDto(
|
||||||
return UserDto(
|
uid: user.uid,
|
||||||
userId: user.id,
|
uuid: user.uuid,
|
||||||
email: user.email,
|
lastOnline: user.lastOnline,
|
||||||
|
profilePic: user.profilePic,
|
||||||
|
profilePicGaussian: user.profilePicGaussian,
|
||||||
nickname: user.nickname,
|
nickname: user.nickname,
|
||||||
avatar: user.avatar,
|
contact: user.contact,
|
||||||
|
countryCode: user.countryCode,
|
||||||
|
email: user.email,
|
||||||
|
recoveryEmail: user.recoveryEmail,
|
||||||
|
username: user.username,
|
||||||
|
bio: user.bio,
|
||||||
|
relationship: user.relationship,
|
||||||
|
userAlias: user.userAlias,
|
||||||
|
channelId: user.channelId,
|
||||||
|
channelGroupId: user.channelGroupId,
|
||||||
|
hint: user.hint,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// DTO → Drift Companion (for DB insert/update)
|
||||||
|
UsersCompanion toCompanion() => UsersCompanion(
|
||||||
|
uid: Value(uid),
|
||||||
|
uuid: Value(uuid),
|
||||||
|
lastOnline: Value(lastOnline),
|
||||||
|
profilePic: Value(profilePic),
|
||||||
|
profilePicGaussian: Value(profilePicGaussian ?? ''),
|
||||||
|
nickname: Value(nickname),
|
||||||
|
contact: Value(contact),
|
||||||
|
countryCode: Value(countryCode),
|
||||||
|
email: Value(email),
|
||||||
|
recoveryEmail: Value(recoveryEmail),
|
||||||
|
username: Value(username),
|
||||||
|
bio: Value(bio),
|
||||||
|
relationship: Value(relationship),
|
||||||
|
userAlias: Value(userAlias),
|
||||||
|
hint: Value(hint),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,32 +28,76 @@ part 'get_profile_request.g.dart';
|
|||||||
/// 用户资料响应 DTO(只需反序列化,禁止生成无用的 toJson)
|
/// 用户资料响应 DTO(只需反序列化,禁止生成无用的 toJson)
|
||||||
@JsonSerializable(createToJson: false)
|
@JsonSerializable(createToJson: false)
|
||||||
class ProfileData {
|
class ProfileData {
|
||||||
@JsonKey(name: 'user_id')
|
final int uid;
|
||||||
final String userId;
|
final String uuid;
|
||||||
|
@JsonKey(name: 'last_online')
|
||||||
|
final int lastOnline;
|
||||||
|
@JsonKey(name: 'profile_pic')
|
||||||
|
final String profilePic;
|
||||||
|
@JsonKey(name: 'profile_pic_gaussian')
|
||||||
|
final String profilePicGaussian;
|
||||||
|
final String nickname;
|
||||||
|
final String contact;
|
||||||
|
@JsonKey(name: 'country_code')
|
||||||
|
final String countryCode;
|
||||||
final String email;
|
final String email;
|
||||||
final String? nickname;
|
@JsonKey(name: 'recovery_email')
|
||||||
final String? avatar;
|
final String recoveryEmail;
|
||||||
|
final String username;
|
||||||
|
final String bio;
|
||||||
|
final int relationship;
|
||||||
|
@JsonKey(name: 'user_alias')
|
||||||
|
final String? userAlias;
|
||||||
|
@JsonKey(name: 'channel_id')
|
||||||
|
final int channelId;
|
||||||
|
@JsonKey(name: 'channel_group_id')
|
||||||
|
final int channelGroupId;
|
||||||
|
final String hint;
|
||||||
|
|
||||||
const ProfileData({
|
const ProfileData({
|
||||||
required this.userId,
|
required this.uid,
|
||||||
|
required this.uuid,
|
||||||
|
required this.lastOnline,
|
||||||
|
required this.profilePic,
|
||||||
|
required this.profilePicGaussian,
|
||||||
|
required this.nickname,
|
||||||
|
required this.contact,
|
||||||
|
required this.countryCode,
|
||||||
required this.email,
|
required this.email,
|
||||||
this.nickname,
|
required this.recoveryEmail,
|
||||||
this.avatar,
|
required this.username,
|
||||||
|
required this.bio,
|
||||||
|
required this.relationship,
|
||||||
|
this.userAlias,
|
||||||
|
required this.channelId,
|
||||||
|
required this.channelGroupId,
|
||||||
|
required this.hint,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ProfileData.fromJson(Map<String, dynamic> json) =>
|
factory ProfileData.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ProfileDataFromJson(json);
|
_$ProfileDataFromJson(json);
|
||||||
|
|
||||||
/// DTO → Domain Entity
|
/// DTO → Domain Entity
|
||||||
User toEntity() {
|
User toEntity() => User(
|
||||||
return User(
|
uid: uid,
|
||||||
id: userId,
|
uuid: uuid,
|
||||||
email: email,
|
lastOnline: lastOnline,
|
||||||
|
profilePic: profilePic,
|
||||||
|
profilePicGaussian: profilePicGaussian,
|
||||||
nickname: nickname,
|
nickname: nickname,
|
||||||
avatar: avatar,
|
contact: contact,
|
||||||
|
countryCode: countryCode,
|
||||||
|
email: email,
|
||||||
|
recoveryEmail: recoveryEmail,
|
||||||
|
username: username,
|
||||||
|
bio: bio,
|
||||||
|
relationship: relationship,
|
||||||
|
userAlias: userAlias,
|
||||||
|
channelId: channelId,
|
||||||
|
channelGroupId: channelGroupId,
|
||||||
|
hint: hint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
// Request
|
// Request
|
||||||
|
|||||||
@@ -8,75 +8,155 @@ part 'login_request.g.dart';
|
|||||||
|
|
||||||
/// # /auth/login — 登录接口
|
/// # /auth/login — 登录接口
|
||||||
///
|
///
|
||||||
/// 一个端点 = 一个文件,Response DTO + Request 放在同一文件中。
|
|
||||||
///
|
|
||||||
/// ## 数据流位置
|
/// ## 数据流位置
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// AuthRepositoryImpl.login(email, password)
|
/// AuthRepositoryImpl.login(email, password)
|
||||||
/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里
|
/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里
|
||||||
/// → 服务端 POST /auth/login
|
/// → 服务端 POST /auth/login
|
||||||
/// → 响应 JSON → ★ LoginData ★ ← 也在这里
|
/// → 响应 JSON → ★ LoginResponse ★ ← 也在这里
|
||||||
/// → LoginData.toEntity() → User
|
/// → LoginResponse.toEntity() → User
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
// Response DTO
|
// Response DTO
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
/// 登录响应 DTO
|
@JsonSerializable(createToJson: false)
|
||||||
///
|
class LoginProfile {
|
||||||
/// 服务端返回的登录数据,包含 token 和用户信息。
|
final int uid;
|
||||||
/// 通过 [toEntity] 转换为 Domain Entity [User]。
|
final String uuid;
|
||||||
@JsonSerializable()
|
@JsonKey(name: 'last_online')
|
||||||
class LoginData {
|
final int lastOnline;
|
||||||
final String token;
|
@JsonKey(name: 'profile_pic')
|
||||||
@JsonKey(name: 'user_id')
|
final String profilePic;
|
||||||
final String userId;
|
@JsonKey(name: 'profile_pic_gaussian')
|
||||||
|
final String profilePicGaussian;
|
||||||
|
final String nickname;
|
||||||
|
final String contact;
|
||||||
|
@JsonKey(name: 'country_code')
|
||||||
|
final String countryCode;
|
||||||
final String email;
|
final String email;
|
||||||
final String? nickname;
|
@JsonKey(name: 'recovery_email')
|
||||||
final String? avatar;
|
final String recoveryEmail;
|
||||||
|
final String username;
|
||||||
|
final String bio;
|
||||||
|
final int relationship;
|
||||||
|
@JsonKey(name: 'user_alias')
|
||||||
|
final String? userAlias;
|
||||||
|
@JsonKey(name: 'channel_id')
|
||||||
|
final int channelId;
|
||||||
|
@JsonKey(name: 'channel_group_id')
|
||||||
|
final int channelGroupId;
|
||||||
|
final String hint;
|
||||||
|
|
||||||
|
const LoginProfile({
|
||||||
|
required this.uid,
|
||||||
|
required this.uuid,
|
||||||
|
required this.lastOnline,
|
||||||
|
required this.profilePic,
|
||||||
|
required this.profilePicGaussian,
|
||||||
|
required this.nickname,
|
||||||
|
required this.contact,
|
||||||
|
required this.countryCode,
|
||||||
|
required this.email,
|
||||||
|
required this.recoveryEmail,
|
||||||
|
required this.username,
|
||||||
|
required this.bio,
|
||||||
|
required this.relationship,
|
||||||
|
this.userAlias,
|
||||||
|
required this.channelId,
|
||||||
|
required this.channelGroupId,
|
||||||
|
required this.hint,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory LoginProfile.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$LoginProfileFromJson(json);
|
||||||
|
|
||||||
|
User toEntity() => User(
|
||||||
|
uid: uid,
|
||||||
|
uuid: uuid,
|
||||||
|
lastOnline: lastOnline,
|
||||||
|
profilePic: profilePic,
|
||||||
|
profilePicGaussian: profilePicGaussian,
|
||||||
|
nickname: nickname,
|
||||||
|
contact: contact,
|
||||||
|
countryCode: countryCode,
|
||||||
|
email: email,
|
||||||
|
recoveryEmail: recoveryEmail,
|
||||||
|
username: username,
|
||||||
|
bio: bio,
|
||||||
|
relationship: relationship,
|
||||||
|
userAlias: userAlias,
|
||||||
|
channelId: channelId,
|
||||||
|
channelGroupId: channelGroupId,
|
||||||
|
hint: hint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable(createToJson: false, explicitToJson: true)
|
||||||
|
class LoginData {
|
||||||
|
@JsonKey(name: 'account_id')
|
||||||
|
final String accountId;
|
||||||
|
final LoginProfile profile;
|
||||||
|
final String nonce;
|
||||||
|
@JsonKey(name: 'access_token')
|
||||||
|
final String accessToken;
|
||||||
|
@JsonKey(name: 'refresh_token')
|
||||||
|
final String refreshToken;
|
||||||
|
@JsonKey(name: 'device_id')
|
||||||
|
final String deviceId;
|
||||||
|
@JsonKey(name: 'login_data')
|
||||||
|
final String loginData;
|
||||||
|
|
||||||
const LoginData({
|
const LoginData({
|
||||||
required this.token,
|
required this.accountId,
|
||||||
required this.userId,
|
required this.profile,
|
||||||
required this.email,
|
required this.nonce,
|
||||||
this.nickname,
|
required this.accessToken,
|
||||||
this.avatar,
|
required this.refreshToken,
|
||||||
|
required this.deviceId,
|
||||||
|
required this.loginData,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory LoginData.fromJson(Map<String, dynamic> json) =>
|
factory LoginData.fromJson(Map<String, dynamic> json) =>
|
||||||
_$LoginDataFromJson(json);
|
_$LoginDataFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$LoginDataToJson(this);
|
User toEntity() => profile.toEntity();
|
||||||
|
|
||||||
/// DTO → Domain Entity
|
|
||||||
User toEntity() {
|
|
||||||
return User(
|
|
||||||
id: userId,
|
|
||||||
email: email,
|
|
||||||
nickname: nickname,
|
|
||||||
avatar: avatar,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Top-level envelope: { "code": 0, "message": "OK", "data": { ... } }
|
||||||
|
@JsonSerializable(createToJson: false, explicitToJson: true)
|
||||||
|
class LoginResponse {
|
||||||
|
final int code;
|
||||||
|
final String message;
|
||||||
|
final LoginData data;
|
||||||
|
|
||||||
|
const LoginResponse({
|
||||||
|
required this.code,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory LoginResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$LoginResponseFromJson(json);
|
||||||
|
|
||||||
|
User toEntity() => data.toEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
// Request
|
// Request
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
/// 登录请求
|
|
||||||
///
|
|
||||||
/// `@ApiRequest` 自动生成 `_$LoginRequestApi` mixin,
|
|
||||||
/// 提供 path / method / requestType / includeToken / fromJson 自动注册。
|
|
||||||
@ApiRequest(
|
@ApiRequest(
|
||||||
path: ApiPaths.authLogin,
|
path: ApiPaths.authLogin,
|
||||||
method: HttpMethod.post,
|
method: HttpMethod.post,
|
||||||
responseType: LoginData,
|
responseType: LoginResponse,
|
||||||
requestType: ApiRequestType.login,
|
requestType: ApiRequestType.login,
|
||||||
)
|
)
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class LoginRequest extends ApiRequestable<LoginData> with _$LoginRequestApi {
|
class LoginRequest extends ApiRequestable<LoginResponse>
|
||||||
|
with _$LoginRequestApi {
|
||||||
final String email;
|
final String email;
|
||||||
final String password;
|
final String password;
|
||||||
|
|
||||||
|
|||||||
@@ -30,18 +30,18 @@ class AuthRepositoryImpl implements AuthRepository {
|
|||||||
AuthRepositoryImpl({required NetworksSdkApi client, required void Function(String?) onTokenUpdate,}) : _client = client, _onTokenUpdate = onTokenUpdate;
|
AuthRepositoryImpl({required NetworksSdkApi client, required void Function(String?) onTokenUpdate,}) : _client = client, _onTokenUpdate = onTokenUpdate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<User> login({required String email, required String password,}) async
|
Future<User> login({required String email, required String password}) async {
|
||||||
{
|
final LoginResponse? loginResponse = await _client.executeRequest(
|
||||||
final LoginData? loginData = await _client.executeRequest(LoginRequest(email: email, password: password),);
|
LoginRequest(email: email, password: password),
|
||||||
|
);
|
||||||
|
|
||||||
if (loginData == null) {
|
if (loginResponse == null) {
|
||||||
throw Exception('Login failed: empty response'); // TODO: 接入国际化
|
throw Exception('Login failed: empty response');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回调写入 Token(内存 + 持久化由 Provider 层组合)
|
_onTokenUpdate(loginResponse.data.accessToken);
|
||||||
_onTokenUpdate(loginData.token);
|
|
||||||
|
|
||||||
return loginData.toEntity(); // DTO → Domain Entity
|
return loginResponse.toEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -14,15 +14,81 @@
|
|||||||
/// → View 渲染
|
/// → View 渲染
|
||||||
/// ```
|
/// ```
|
||||||
class User {
|
class User {
|
||||||
final String id;
|
final int uid;
|
||||||
final String email;
|
final String? uuid;
|
||||||
|
final int? lastOnline;
|
||||||
|
final String? profilePic;
|
||||||
|
final String? profilePicGaussian;
|
||||||
final String? nickname;
|
final String? nickname;
|
||||||
final String? avatar;
|
final String? contact;
|
||||||
|
final String? countryCode;
|
||||||
|
final String? email;
|
||||||
|
final String? recoveryEmail;
|
||||||
|
final String? username;
|
||||||
|
final String? bio;
|
||||||
|
final int? relationship;
|
||||||
|
final String? userAlias;
|
||||||
|
final int? channelId;
|
||||||
|
final int? channelGroupId;
|
||||||
|
final String? hint;
|
||||||
|
|
||||||
const User({
|
const User({
|
||||||
required this.id,
|
required this.uid,
|
||||||
required this.email,
|
this.uuid,
|
||||||
|
this.lastOnline,
|
||||||
|
this.profilePic,
|
||||||
|
this.profilePicGaussian,
|
||||||
this.nickname,
|
this.nickname,
|
||||||
this.avatar,
|
this.contact,
|
||||||
|
this.countryCode,
|
||||||
|
this.email,
|
||||||
|
this.recoveryEmail,
|
||||||
|
this.username,
|
||||||
|
this.bio,
|
||||||
|
this.relationship,
|
||||||
|
this.userAlias,
|
||||||
|
this.channelId,
|
||||||
|
this.channelGroupId,
|
||||||
|
this.hint,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
User copyWith({
|
||||||
|
int? uid,
|
||||||
|
String? uuid,
|
||||||
|
int? lastOnline,
|
||||||
|
String? profilePic,
|
||||||
|
String? profilePicGaussian,
|
||||||
|
String? nickname,
|
||||||
|
String? contact,
|
||||||
|
String? countryCode,
|
||||||
|
String? email,
|
||||||
|
String? recoveryEmail,
|
||||||
|
String? username,
|
||||||
|
String? bio,
|
||||||
|
int? relationship,
|
||||||
|
String? userAlias,
|
||||||
|
int? channelId,
|
||||||
|
int? channelGroupId,
|
||||||
|
String? hint,
|
||||||
|
}) {
|
||||||
|
return User(
|
||||||
|
uid: uid ?? this.uid,
|
||||||
|
uuid: uuid ?? this.uuid,
|
||||||
|
lastOnline: lastOnline ?? this.lastOnline,
|
||||||
|
profilePic: profilePic ?? this.profilePic,
|
||||||
|
profilePicGaussian: profilePicGaussian ?? this.profilePicGaussian,
|
||||||
|
nickname: nickname ?? this.nickname,
|
||||||
|
contact: contact ?? this.contact,
|
||||||
|
countryCode: countryCode ?? this.countryCode,
|
||||||
|
email: email ?? this.email,
|
||||||
|
recoveryEmail: recoveryEmail ?? this.recoveryEmail,
|
||||||
|
username: username ?? this.username,
|
||||||
|
bio: bio ?? this.bio,
|
||||||
|
relationship: relationship ?? this.relationship,
|
||||||
|
userAlias: userAlias ?? this.userAlias,
|
||||||
|
channelId: channelId ?? this.channelId,
|
||||||
|
channelGroupId: channelGroupId ?? this.channelGroupId,
|
||||||
|
hint: hint ?? this.hint,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:im_app/app/di/db_provider.dart';
|
import 'package:im_app/app/di/db_provider.dart';
|
||||||
import 'package:im_app/data/local/drift/app_database.dart';
|
import 'package:im_app/data/local/drift/app_database.dart';
|
||||||
@@ -87,12 +86,12 @@ class ChatDbTestViewModel extends _$ChatDbTestViewModel {
|
|||||||
final chunk = List.generate(
|
final chunk = List.generate(
|
||||||
chunkSize.clamp(0, count - i),
|
chunkSize.clamp(0, count - i),
|
||||||
(j) => UsersCompanion.insert(
|
(j) => UsersCompanion.insert(
|
||||||
uid: Value(i + j),
|
uid: i + j,
|
||||||
nickname: Value('User ${i + j}'),
|
nickname: Value('User ${i + j}'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await db.batchInsertOrReplace<User>(chunk);
|
await db.batchInsertOrReplace<DriftUser>(chunk);
|
||||||
completed += chunk.length;
|
completed += chunk.length;
|
||||||
|
|
||||||
// 让出主线程
|
// 让出主线程
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:im_app/data/models/user_dto.dart';
|
||||||
|
import 'package:im_app/data/remote/login_request.dart';
|
||||||
import 'package:networks_sdk/networks_sdk.dart';
|
import 'package:networks_sdk/networks_sdk.dart';
|
||||||
import 'package:im_app/app/di/db_provider.dart';
|
import 'package:im_app/app/di/db_provider.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@@ -56,10 +60,22 @@ class LoginViewModel extends _$LoginViewModel {
|
|||||||
/// 正式 [login] 成功后同样需要调用 [AuthNotifier.login] 更新守卫状态。
|
/// 正式 [login] 成功后同样需要调用 [AuthNotifier.login] 更新守卫状态。
|
||||||
Future<void> demoLogin() async {
|
Future<void> demoLogin() async {
|
||||||
final storageApi = ref.read(storageSdkProvider);
|
final storageApi = ref.read(storageSdkProvider);
|
||||||
///TODO: StorageSDKLifeCycle 需要只在主项目暴露
|
|
||||||
final storageLifeCycle = storageApi as StorageSdkLifecycle;
|
final storageLifeCycle = storageApi as StorageSdkLifecycle;
|
||||||
ref.read(authNotifierProvider).login();
|
final provider = ref.read(authNotifierProvider);
|
||||||
await storageLifeCycle.openDatabase(1234567);
|
|
||||||
|
// Read mock response from assets
|
||||||
|
final String raw = await rootBundle.loadString('assets/loginData.json');
|
||||||
|
final Map<String, dynamic> json = jsonDecode(raw);
|
||||||
|
|
||||||
|
// Parse into LoginData (nested under 'data' key)
|
||||||
|
final loginResponse = LoginResponse.fromJson(json);
|
||||||
|
final user = loginResponse.data.toEntity();
|
||||||
|
|
||||||
|
provider.login();
|
||||||
|
// Open database for the user
|
||||||
|
await storageLifeCycle.openDatabase(user.uid);
|
||||||
|
final userCompanion = UserDto.fromEntity(user).toCompanion();
|
||||||
|
storageApi.insert(userCompanion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 执行登录
|
/// 执行登录
|
||||||
|
|||||||
@@ -62,3 +62,5 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- assets/
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class DatabaseRepositoryImpl implements DatabaseRepository {
|
|||||||
) async {
|
) async {
|
||||||
final db = _db;
|
final db = _db;
|
||||||
if (db == null) return;
|
if (db == null) return;
|
||||||
await db.into(table).insertOnConflictUpdate(companion);
|
await db.into(table).insert(companion, mode: InsertMode.insertOrReplace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -44,7 +44,11 @@ class DatabaseRepositoryImpl implements DatabaseRepository {
|
|||||||
final db = _db;
|
final db = _db;
|
||||||
if (db == null) return;
|
if (db == null) return;
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(batch) => batch.insertAllOnConflictUpdate(table, companions),
|
(batch) => batch.insertAll(
|
||||||
|
table,
|
||||||
|
companions,
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user