Fixed user management

This commit is contained in:
2026-02-18 20:43:11 +08:00
parent af6cfe76b4
commit 8c1bb7646e
5 changed files with 360 additions and 77 deletions
+53 -62
View File
@@ -75,63 +75,66 @@ class AdminUserController {
await _client.from('profiles').update({'role': role}).eq('id', userId);
}
/// Password administration — forwarded to the admin Edge Function.
// Centralized helper that calls the admin Edge Function and surfaces
// a clear error for 401/bad_jwt so the UI can react (sign out/reauth).
Future<dynamic> _invokeAdminFunction(
String action,
Map<String, dynamic> payload,
) async {
final accessToken = _client.auth.currentSession?.accessToken;
final response = await _client.functions.invoke(
'admin_user_management',
body: {'action': action, ...payload},
headers: accessToken == null
? null
: {'Authorization': 'Bearer $accessToken'},
);
if (response.status == 401) {
// If the gateway rejects the JWT, proactively clear the local session
// so the app can re-authenticate and obtain a valid Supabase token.
try {
await _client.auth.signOut();
} catch (_) {
// ignore sign-out errors
}
throw Exception(
'Unauthorized: invalid or expired session token (bad_jwt)',
);
}
if (response.status != 200) {
throw Exception(response.data ?? 'Admin request failed');
}
return response.data;
}
Future<void> setPassword({
required String userId,
required String password,
}) async {
final payload = {
'action': 'set_password',
if (password.length < 8) {
throw Exception('Password must be at least 8 characters');
}
await _invokeAdminFunction('set_password', {
'userId': userId,
'password': password,
};
final accessToken = _client.auth.currentSession?.accessToken;
final response = await _client.functions.invoke(
'admin_user_management',
body: payload,
headers: accessToken == null
? null
: {'Authorization': 'Bearer $accessToken'},
);
if (response.status != 200) {
throw Exception(response.data ?? 'Failed to reset password');
}
});
}
/// Set/unset a user's ban/lock via the admin Edge Function (preferred).
Future<void> setLock({required String userId, required bool locked}) async {
final payload = {'action': 'set_lock', 'userId': userId, 'locked': locked};
final accessToken = _client.auth.currentSession?.accessToken;
final response = await _client.functions.invoke(
'admin_user_management',
body: payload,
headers: accessToken == null
? null
: {'Authorization': 'Bearer $accessToken'},
);
if (response.status != 200) {
throw Exception(response.data ?? 'Failed to update lock state');
}
await _invokeAdminFunction('set_lock', {
'userId': userId,
'locked': locked,
});
}
/// Fetch user email + banned state from the admin Edge Function (auth.user).
Future<AdminUserStatus> fetchStatus(String userId) async {
final payload = {'action': 'get_user', 'userId': userId};
final accessToken = _client.auth.currentSession?.accessToken;
final response = await _client.functions.invoke(
'admin_user_management',
body: payload,
headers: accessToken == null
? null
: {'Authorization': 'Bearer $accessToken'},
);
if (response.status != 200) {
return AdminUserStatus(email: null, bannedUntil: null);
}
final data = response.data;
final user = (data is Map<String, dynamic>)
? (data['user'] as Map<String, dynamic>?)
: null;
final data = await _invokeAdminFunction('get_user', {'userId': userId});
final user =
(data as Map<String, dynamic>?)?['user'] as Map<String, dynamic>?;
final email = user?['email'] as String?;
DateTime? bannedUntil;
final bannedRaw = user?['banned_until'];
@@ -146,27 +149,15 @@ class AdminUserController {
);
}
/// Server-side paginated listing via Edge Function (returns auth + profile light view).
Future<List<Map<String, dynamic>>> listUsers(AdminUserQuery q) async {
final payload = {
'action': 'list_users',
final data = await _invokeAdminFunction('list_users', {
'offset': q.offset,
'limit': q.limit,
'searchQuery': q.searchQuery,
};
final accessToken = _client.auth.currentSession?.accessToken;
final response = await _client.functions.invoke(
'admin_user_management',
body: payload,
headers: accessToken == null
? null
: {'Authorization': 'Bearer $accessToken'},
);
if (response.status != 200) {
throw Exception(response.data ?? 'Failed to list users');
}
final users = (response.data is Map && response.data['users'] is List)
? (response.data['users'] as List).cast<Map<String, dynamic>>()
});
final users = (data is Map && data['users'] is List)
? (data['users'] as List).cast<Map<String, dynamic>>()
: <Map<String, dynamic>>[];
return users;
}