Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: improve user update [#98] #103

Merged
merged 1 commit into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/clerk_auth/lib/src/clerk_api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -426,17 +426,23 @@ class Api with Logging {

/// Update details pertaining to the current [User]
///
Future<ApiResponse> updateUser(User user) async {
Future<ApiResponse> updateUser(User user, AuthConfig config) async {
return await _fetchApiResponse(
'/me',
method: HttpMethod.patch,
withSession: true,
params: {
'first_name': user.firstName,
'last_name': user.lastName,
if (config.allowsUsername) //
'username': user.username,
if (config.allowsFirstName) //
'first_name': user.firstName,
if (config.allowsLastName) //
'last_name': user.lastName,
'primary_email_address_id': user.primaryEmailAddressId,
'primary_phone_number_id': user.primaryPhoneNumberId,
'primary_web3_wallet_id': user.primaryWeb3WalletId,
'unsafe_metadata':
user.hasMetadata ? json.encode(user.userMetadata) : null,
},
);
}
Expand Down
17 changes: 11 additions & 6 deletions packages/clerk_auth/lib/src/clerk_auth/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -378,15 +378,20 @@ class Auth {

/// Update the [name] of the current [User]
///
Future<void> updateUserName(String name) async {
if (user case User user when name.isNotEmpty) {
final names = name.split(' ').where((s) => s.isNotEmpty).toList();
final lastName = names.length == 1 ? '' : names.removeLast();
Future<void> updateUser({
String? username,
String? firstName,
String? lastName,
Map<String, dynamic>? metadata,
}) async {
if (user case User user) {
final newUser = user.copyWith(
username: username,
firstName: firstName,
lastName: lastName,
firstName: names.join(' '),
userMetadata: metadata,
);
await _api.updateUser(newUser).then(_housekeeping);
await _api.updateUser(newUser, env.config).then(_housekeeping);
update();
}
}
Expand Down
36 changes: 18 additions & 18 deletions packages/clerk_auth/lib/src/models/environment/auth_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ class AuthConfig {
this.firstFactors = const [],
this.secondFactors = const [],
this.emailAddressVerificationStrategies = const [],
this.usesFirstName = false,
this.usesLastName = false,
this.usesEmailAddress = false,
this.usesPhoneNumber = false,
this.usesUsername = false,
this.usesPassword = false,
this.allowsFirstName = false,
this.allowsLastName = false,
this.allowsEmailAddress = false,
this.allowsPhoneNumber = false,
this.allowsUsername = false,
this.allowsPassword = false,
});

/// id
Expand Down Expand Up @@ -66,29 +66,29 @@ class AuthConfig {
@JsonKey(fromJson: toStrategyList)
final List<Strategy> emailAddressVerificationStrategies;

/// uses first name?
/// allows first name?
@JsonKey(name: 'first_name', fromJson: isOn)
final bool usesFirstName;
final bool allowsFirstName;

/// uses last name?
/// allows last name?
@JsonKey(name: 'last_name', fromJson: isOn)
final bool usesLastName;
final bool allowsLastName;

/// uses email address?
/// allows email address?
@JsonKey(name: 'email_address', fromJson: isOn)
final bool usesEmailAddress;
final bool allowsEmailAddress;

/// uses phone number?
/// allows phone number?
@JsonKey(name: 'phone_number', fromJson: isOn)
final bool usesPhoneNumber;
final bool allowsPhoneNumber;

/// uses username?
/// allows username?
@JsonKey(name: 'username', fromJson: isOn)
final bool usesUsername;
final bool allowsUsername;

/// uses password?
/// allows password?
@JsonKey(name: 'password', fromJson: isOn)
final bool usesPassword;
final bool allowsPassword;

/// empty [AuthConfig]
static const empty = AuthConfig();
Expand Down
25 changes: 13 additions & 12 deletions packages/clerk_auth/lib/src/models/environment/auth_config.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions packages/clerk_auth/lib/src/models/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class User {
/// Constructor
const User({
required this.id,
required this.username,
required this.firstName,
required this.lastName,
required this.profileImageUrl,
Expand All @@ -21,7 +22,7 @@ class User {
required this.primaryWeb3WalletId,
required this.publicMetadata,
required this.privateMetadata,
required this.unsafeMetadata,
required this.userMetadata,
required this.emailAddresses,
required this.phoneNumbers,
required this.web3Wallets,
Expand All @@ -45,6 +46,9 @@ class User {
/// id
final String id;

/// username
final String? username;

/// first name
final String? firstName;

Expand Down Expand Up @@ -76,7 +80,8 @@ class User {
final Map<String, dynamic>? privateMetadata;

/// unsafe metadata
final Map<String, dynamic>? unsafeMetadata;
@JsonKey(name: 'unsafe_metadata')
final Map<String, dynamic>? userMetadata;

/// email addresses
final List<Email>? emailAddresses;
Expand Down Expand Up @@ -136,6 +141,9 @@ class User {
@JsonKey(fromJson: intToDateTime)
final DateTime? lastActiveAt;

/// Does this user have metadata?
bool get hasMetadata => userMetadata?.isNotEmpty == true;

/// fromJson
static User fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

Expand Down Expand Up @@ -196,6 +204,7 @@ class User {

/// copy this user with changed fields
User copyWith({
String? username,
String? firstName,
String? lastName,
String? profileImageUrl,
Expand All @@ -206,7 +215,7 @@ class User {
String? primaryWeb3WalletId,
Map<String, dynamic>? publicMetadata,
Map<String, dynamic>? privateMetadata,
Map<String, dynamic>? unsafeMetadata,
Map<String, dynamic>? userMetadata,
List<Email>? emailAddresses,
List<PhoneNumber>? phoneNumbers,
List<Web3Wallet>? web3Wallets,
Expand All @@ -228,6 +237,7 @@ class User {
}) =>
User(
id: id,
username: username ?? this.username,
firstName: firstName ?? this.firstName,
lastName: lastName ?? this.lastName,
profileImageUrl: profileImageUrl ?? this.profileImageUrl,
Expand All @@ -239,7 +249,7 @@ class User {
primaryWeb3WalletId: primaryWeb3WalletId ?? this.primaryWeb3WalletId,
publicMetadata: publicMetadata ?? this.publicMetadata,
privateMetadata: privateMetadata ?? this.privateMetadata,
unsafeMetadata: unsafeMetadata ?? this.unsafeMetadata,
userMetadata: userMetadata ?? this.userMetadata,
emailAddresses: emailAddresses ?? this.emailAddresses,
phoneNumbers: phoneNumbers ?? this.phoneNumbers,
web3Wallets: web3Wallets ?? this.web3Wallets,
Expand Down
6 changes: 4 additions & 2 deletions packages/clerk_auth/lib/src/models/user.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/clerk_auth/lib/src/version.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -93,21 +93,30 @@ void main() {
'{"response":{"id":"USER_ID","object":"user","username":"test1","first_name":"Test","last_name":"User","image_url":"https://img.clerk.com/eyJ0eXBlIjoiZGVmYXVsdCIsImlpZCI6Imluc18ya3ZZdzF0WkY4OHNvQjdtN0FYaGlEQ2llMmsiLCJyaWQiOiJ1c2VyXzJtbktmQms4MlhkZ0pYUzltNlhGZlkyTGZHTiIsImluaXRpYWxzIjoiVFUifQ","has_image":false,"primary_email_address_id":"IDENTIFIER_ID","primary_phone_number_id":"IDENTIFIER_ID","primary_web3_wallet_id":null,"password_enabled":true,"two_factor_enabled":false,"totp_enabled":false,"backup_code_enabled":false,"email_addresses":[{"id":"IDENTIFIER_ID","object":"email_address","email_address":"test1+clerk_test@some.domain","reserved":false,"verification":{"status":"verified","strategy":"admin","attempts":null,"expire_at":null},"linked_to":[],"created_at":1727707000228,"updated_at":1729075139789}],"phone_numbers":[{"id":"IDENTIFIER_ID","object":"phone_number","phone_number":"+15555550101","reserved_for_second_factor":false,"default_second_factor":false,"reserved":false,"verification":{"status":"verified","strategy":"admin","attempts":null,"expire_at":null},"linked_to":[],"backup_codes":null,"created_at":1727707000277,"updated_at":1727707000277}],"web3_wallets":[],"passkeys":[],"external_accounts":[],"saml_accounts":[],"enterprise_accounts":[],"public_metadata":{},"unsafe_metadata":{},"external_id":null,"last_sign_in_at":1732016622125,"banned":false,"locked":false,"lockout_expires_in_seconds":null,"verification_attempts_remaining":100,"created_at":1727707000164,"updated_at":1732016623480,"delete_self_enabled":true,"create_organization_enabled":true,"last_active_at":1731928604966,"mfa_enabled_at":null,"mfa_disabled_at":null,"legal_accepted_at":null,"profile_image_url":"https://www.gravatar.com/avatar?d=mp"},"client":{"object":"client","id":"CLIENT_ID","sessions":[{"object":"session","id":"SESSION_ID","status":"active","expire_at":$expireAt,"abandon_at":1734608622117,"last_active_at":1732016622117,"last_active_organization_id":null,"actor":null,"user":{"id":"USER_ID","object":"user","username":"test1","first_name":"Test","last_name":"User","image_url":"https://img.clerk.com/eyJ0eXBlIjoiZGVmYXVsdCIsImlpZCI6Imluc18ya3ZZdzF0WkY4OHNvQjdtN0FYaGlEQ2llMmsiLCJyaWQiOiJ1c2VyXzJtbktmQms4MlhkZ0pYUzltNlhGZlkyTGZHTiIsImluaXRpYWxzIjoiVFUifQ","has_image":false,"primary_email_address_id":"IDENTIFIER_ID","primary_phone_number_id":"IDENTIFIER_ID","primary_web3_wallet_id":null,"password_enabled":true,"two_factor_enabled":false,"totp_enabled":false,"backup_code_enabled":false,"email_addresses":[{"id":"IDENTIFIER_ID","object":"email_address","email_address":"test1+clerk_test@some.domain","reserved":false,"verification":{"status":"verified","strategy":"admin","attempts":null,"expire_at":null},"linked_to":[],"created_at":1727707000228,"updated_at":1729075139789}],"phone_numbers":[{"id":"IDENTIFIER_ID","object":"phone_number","phone_number":"+15555550101","reserved_for_second_factor":false,"default_second_factor":false,"reserved":false,"verification":{"status":"verified","strategy":"admin","attempts":null,"expire_at":null},"linked_to":[],"backup_codes":null,"created_at":1727707000277,"updated_at":1727707000277}],"web3_wallets":[],"passkeys":[],"external_accounts":[],"saml_accounts":[],"enterprise_accounts":[],"public_metadata":{},"unsafe_metadata":{},"external_id":null,"last_sign_in_at":1732016622125,"banned":false,"locked":false,"lockout_expires_in_seconds":null,"verification_attempts_remaining":100,"created_at":1727707000164,"updated_at":1732016623480,"delete_self_enabled":true,"create_organization_enabled":true,"last_active_at":1731928604966,"mfa_enabled_at":null,"mfa_disabled_at":null,"legal_accepted_at":null,"profile_image_url":"https://www.gravatar.com/avatar?d=mp","organization_memberships":[]},"public_user_data":{"first_name":"Test","last_name":"User","image_url":"https://img.clerk.com/eyJ0eXBlIjoiZGVmYXVsdCIsImlpZCI6Imluc18ya3ZZdzF0WkY4OHNvQjdtN0FYaGlEQ2llMmsiLCJyaWQiOiJ1c2VyXzJtbktmQms4MlhkZ0pYUzltNlhGZlkyTGZHTiIsImluaXRpYWxzIjoiVFUifQ","has_image":false,"identifier":"test1+clerk_test@some.domain","profile_image_url":"https://www.gravatar.com/avatar?d=mp"},"created_at":1732016622125,"updated_at":1732016622187,"last_active_token":{"object":"token","jwt":"eyJhbGciOiJSUzI1NiIsImNhdCI6ImNsX0I3ZDRQRDExMUFBQSIsImtpZCI6Imluc18ya3ZZdzF0WkY4OHNvQjdtN0FYaGlEQ2llMmsiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjE3MzIwMTY2ODMsImlhdCI6MTczMjAxNjYyMywiaXNzIjoiaHR0cHM6Ly9tb3JlLXlldGktNTMuY2xlcmsuYWNjb3VudHMuZGV2IiwibmJmIjoxNzMyMDE2NjEzLCJzaWQiOiJzZXNzXzJwNERuZjZDamVZMXN0RnB1Z1lQSU52YmJxdCIsInN1YiI6InVzZXJfMm1uS2ZCazgyWGRnSlhTOW02WEZmWTJMZkdOIn0.nwfUsotPCwP0mzpu3a-HR-0d0h5uELQcUc87t2a72pQ17-Vxyg70LH7U0kpLmqpmiPDGEHj2yvXMRllTc_idZsHhPc3P4SXRCKijatap6zKPn8rg6GyTHA2Khpouek2ccR325fot3NxbU-3ApWSWLyq10mx_fV6vs9DKKVBE6U8luaQxqJbfBt-NoU37GSEMm6i5DOmaHwvULFETodd9nclD4WbrGPtxbrzp5nkNWu3YrFM026jwaEBYVY4ULkJyw2vPy8kfvpLSC6InilbooEvVyDxGX4Q8mT-Ew2K5B1zEZDHRawqzalDSFF-QGTfLfp8kM7wR80sVrB-LxglFAQ"}}],"sign_in":null,"sign_up":null,"last_active_session_id":"SESSION_ID","cookie_expires_at":null,"created_at":1732016621555,"updated_at":1732016622183}}',
);

const config = AuthConfig(
allowsFirstName: true,
allowsLastName: true,
);

response = await api.getUser();
expect(response.client?.activeSession?.user is User, true);

user = response.client!.activeSession!.user;
response = await api
.updateUser(user.copyWith(firstName: 'New', lastName: 'Cognomen'));
response = await api.updateUser(
user.copyWith(firstName: 'New', lastName: 'Cognomen'),
config,
);
expect(response.isOkay, true);

response = await api.getUser();
user = response.client?.activeSession?.user;
expect(user?.name, 'New Cognomen');

user = response.client!.activeSession!.user;
response = await api
.updateUser(user.copyWith(firstName: 'Test', lastName: 'User'));
response = await api.updateUser(
user.copyWith(firstName: 'Test', lastName: 'User'),
config,
);
expect(response.isOkay, true);

response = await api.getUser();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,12 @@ class _EditableUserDataState extends State<_EditableUserData> {
Future<void> _update([_]) async {
if (isEditing) {
final authState = ClerkAuth.of(context, listen: false);
if (_controller.text != widget.user.name) {
await authState.updateUserName(_controller.text);
final name = _controller.text;
if (name != widget.user.name && name.isNotEmpty) {
final names = name.split(' ').where((s) => s.isNotEmpty).toList();
final lastName = names.length > 1 ? names.removeLast() : '';
final firstName = names.join(' ');
await authState.updateUser(firstName: firstName, lastName: lastName);
}
if (image case File image) {
await authState.updateUserImage(image);
Expand Down
Loading