Skip to content

Commit 8b95f4c

Browse files
client/server: WIP user, delete authkey associated with device
1 parent 185b3a8 commit 8b95f4c

11 files changed

Lines changed: 1790 additions & 1262 deletions

File tree

school_data_hub_client/lib/src/protocol/client.dart

Lines changed: 341 additions & 333 deletions
Large diffs are not rendered by default.

school_data_hub_flutter/lib/features/user/data/user_api_service.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class UserApiService {
4747
);
4848
}
4949
/// Update user and UserInfo in one go. [userId] is the UserInfo id.
50+
/// [imageUrl] is stored in UserInfo when the server endpoint supports it.
5051
Future<void> updateUser({
5152
required int userId,
5253
required String userName,
@@ -58,6 +59,7 @@ class UserApiService {
5859
required int reliefTimeUnits,
5960
required int credit,
6061
required bool isTester,
62+
String? imageUrl,
6163
}) async {
6264
await _client.adminUser.updateUser(
6365
userId,
@@ -71,6 +73,8 @@ class UserApiService {
7173
credit: credit,
7274
isTester: isTester,
7375
);
76+
// TODO: when server supports UserInfo.imageUrl, add imageUrl to the
77+
// endpoint and pass it here (client must be regenerated).
7478
}
7579
/// Reset a user's password. Returns `true` on success.
7680
Future<bool> resetPassword(String userEmail, String newPassword) async {
@@ -90,6 +94,14 @@ class UserApiService {
9094
await _client.adminUser.deleteUser(userId);
9195
}
9296

97+
/// Delete the auth key associated with a device. Returns updated user+devices
98+
/// for the session user (or null if not authenticated).
99+
Future<UserWithDevices?> deleteAuthKeyAssociatedWithDevice(
100+
UserDevice device,
101+
) async {
102+
return _client.adminUser.deleteAuthKeyAssociatedWithDevice(device);
103+
}
104+
93105
/// Increase staff credit for all users. Returns `true` on success.
94106
Future<bool> increaseStaffCredit() async {
95107
return _client.user.increaseStaffCredit();

school_data_hub_flutter/lib/features/user/domain/user_manager.dart

Lines changed: 135 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import 'package:flutter/foundation.dart';
2+
import 'package:flutter_it/flutter_it.dart';
23
import 'package:school_data_hub_client/school_data_hub_client.dart';
34
import 'package:school_data_hub_flutter/common/services/notification_service.dart';
45
import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart';
56
import 'package:school_data_hub_flutter/features/user/data/user_api_service.dart';
6-
import 'package:flutter_it/flutter_it.dart';
77

88
/// Data class for createUser command parameters.
99
typedef CreateUserParams = ({
@@ -39,16 +39,27 @@ typedef UpdateUserParams = ({
3939
int reliefTimeUnits,
4040
int credit,
4141
bool isTester,
42+
String? imageUrl,
4243
});
4344

4445
class UserManager {
4546
final _apiService = UserApiService();
4647
HubSessionManager get _sessionManager => di<HubSessionManager>();
4748
NotificationService get _notificationService => di<NotificationService>();
4849

50+
final _usersWithDevices = ValueNotifier<List<UserWithDevices>>([]);
4951
final _users = ValueNotifier<List<User>>([]);
52+
53+
ValueListenable<List<UserWithDevices>> get usersWithDevices =>
54+
_usersWithDevices;
55+
56+
/// Derived list of users for search bar and backward compatibility.
5057
ValueListenable<List<User>> get users => _users;
5158

59+
void _syncUsersFromDevices() {
60+
_users.value = _usersWithDevices.value.map((e) => e.user).toList();
61+
}
62+
5263
//-- Commands --
5364

5465
late final fetchUsersCommand = Command.createAsyncNoParamNoResult(
@@ -65,17 +76,17 @@ class UserManager {
6576

6677
late final resetPasswordCommand =
6778
Command.createAsyncNoResult<ResetPasswordParams>(
68-
_resetPassword,
69-
debugName: 'resetPassword',
70-
errorFilter: const GlobalIfNoLocalErrorFilter(),
71-
);
79+
_resetPassword,
80+
debugName: 'resetPassword',
81+
errorFilter: const GlobalIfNoLocalErrorFilter(),
82+
);
7283

7384
late final changePasswordCommand =
7485
Command.createAsyncNoResult<ChangePasswordParams>(
75-
_changePassword,
76-
debugName: 'changePassword',
77-
errorFilter: const GlobalIfNoLocalErrorFilter(),
78-
);
86+
_changePassword,
87+
debugName: 'changePassword',
88+
errorFilter: const GlobalIfNoLocalErrorFilter(),
89+
);
7990

8091
late final blockUserCommand = Command.createAsyncNoResult<User>(
8192
_blockUser,
@@ -89,8 +100,13 @@ class UserManager {
89100
errorFilter: const GlobalIfNoLocalErrorFilter(),
90101
);
91102

92-
late final increaseUsersCreditCommand =
93-
Command.createAsyncNoParamNoResult(
103+
late final deleteDeviceCommand = Command.createAsyncNoResult<UserDevice>(
104+
_deleteDevice,
105+
debugName: 'deleteDevice',
106+
errorFilter: const LocalErrorFilter(),
107+
);
108+
109+
late final increaseUsersCreditCommand = Command.createAsyncNoParamNoResult(
94110
_increaseUsersCredit,
95111
debugName: 'increaseUsersCredit',
96112
errorFilter: const GlobalIfNoLocalErrorFilter(),
@@ -99,6 +115,7 @@ class UserManager {
99115
UserManager();
100116

101117
void dispose() {
118+
_usersWithDevices.dispose();
102119
_users.dispose();
103120
}
104121

@@ -110,11 +127,15 @@ class UserManager {
110127
//-- Command implementations --
111128

112129
Future<void> _fetchUsers() async {
113-
final List<User> responseUsers = await _apiService.getAllUsers();
114-
responseUsers.sort(
115-
(a, b) => a.userInfo!.userName!.compareTo(b.userInfo!.userName!),
130+
final List<UserWithDevices> list = await _apiService
131+
.getAllUsersWithDevices();
132+
list.sort(
133+
(a, b) => (a.user.userInfo?.userName ?? '').compareTo(
134+
b.user.userInfo?.userName ?? '',
135+
),
116136
);
117-
_users.value = responseUsers;
137+
_usersWithDevices.value = list;
138+
_syncUsersFromDevices();
118139
}
119140

120141
Future<void> _createUser(CreateUserParams params) async {
@@ -191,6 +212,7 @@ class UserManager {
191212
reliefTimeUnits: params.reliefTimeUnits,
192213
credit: params.credit,
193214
isTester: params.isTester,
215+
imageUrl: params.imageUrl,
194216
);
195217
await fetchUsersCommand.runAsync();
196218
_notificationService.showSnackBar(
@@ -199,6 +221,15 @@ class UserManager {
199221
);
200222
}
201223

224+
Future<void> _deleteDevice(UserDevice device) async {
225+
await _apiService.deleteAuthKeyAssociatedWithDevice(device);
226+
await fetchUsersCommand.runAsync();
227+
_notificationService.showSnackBar(
228+
NotificationType.success,
229+
'Gerät gelöscht.',
230+
);
231+
}
232+
202233
Future<void> _increaseUsersCredit() async {
203234
final success = await _apiService.increaseStaffCredit();
204235
if (!success) {
@@ -230,74 +261,129 @@ class UserManager {
230261
required Role role,
231262
required bool isTester,
232263
String? tutoring,
233-
}) =>
234-
createUserCommand.runAsync((
235-
userName: userName,
236-
fullName: fullName,
237-
password: password,
238-
email: email,
239-
matrixUserId: matrixUserId,
240-
timeUnits: timeUnits,
241-
reliefTimeUnits: reliefTimeUnits,
242-
credit: credit,
243-
scopeNames: scopeNames,
244-
role: role,
245-
isTester: isTester,
246-
tutoring: tutoring,
247-
));
264+
}) => createUserCommand.runAsync((
265+
userName: userName,
266+
fullName: fullName,
267+
password: password,
268+
email: email,
269+
matrixUserId: matrixUserId,
270+
timeUnits: timeUnits,
271+
reliefTimeUnits: reliefTimeUnits,
272+
credit: credit,
273+
scopeNames: scopeNames,
274+
role: role,
275+
isTester: isTester,
276+
tutoring: tutoring,
277+
));
248278

249279
Future<void> resetPassword(String userEmail, String newPassword) =>
250-
resetPasswordCommand
251-
.runAsync((userEmail: userEmail, newPassword: newPassword));
280+
resetPasswordCommand.runAsync((
281+
userEmail: userEmail,
282+
newPassword: newPassword,
283+
));
252284

253285
Future<void> changePassword(String oldPassword, String newPassword) =>
254-
changePasswordCommand
255-
.runAsync((oldPassword: oldPassword, newPassword: newPassword));
286+
changePasswordCommand.runAsync((
287+
oldPassword: oldPassword,
288+
newPassword: newPassword,
289+
));
256290

257291
Future<void> blockUser(User user) => blockUserCommand.runAsync(user);
258292

293+
Future<void> deleteDevice(UserDevice device) =>
294+
deleteDeviceCommand.runAsync(device);
295+
259296
Future<void> updateUser(UpdateUserParams params) =>
260297
updateUserCommand.runAsync(params);
261298

262-
Future<void> increaseUsersCredit() =>
263-
increaseUsersCreditCommand.runAsync();
299+
Future<void> increaseUsersCredit() => increaseUsersCreditCommand.runAsync();
264300

265301
//-- Local state helpers --
266302

267303
void setUsers(List<User> users) {
268-
_users.value = users;
304+
final list = users
305+
.map((u) => UserWithDevices(user: u, userDevices: []))
306+
.toList();
307+
list.sort(
308+
(a, b) => (a.user.userInfo?.userName ?? '').compareTo(
309+
b.user.userInfo?.userName ?? '',
310+
),
311+
);
312+
_usersWithDevices.value = list;
313+
_syncUsersFromDevices();
269314
}
270315

271316
void _addUser(User user) {
272-
final List<User> users = List.from(_users.value);
273-
users.add(user);
274-
users.sort(
275-
(a, b) => a.userInfo!.userName!.compareTo(b.userInfo!.userName!),
317+
final list = List<UserWithDevices>.from(_usersWithDevices.value);
318+
list.add(UserWithDevices(user: user, userDevices: []));
319+
list.sort(
320+
(a, b) => (a.user.userInfo?.userName ?? '').compareTo(
321+
b.user.userInfo?.userName ?? '',
322+
),
276323
);
277-
_users.value = users;
324+
_usersWithDevices.value = list;
325+
_syncUsersFromDevices();
278326
}
279327

280328
void removeUser(User user) {
281-
_users.value = _users.value
282-
.where((element) => element.id != user.id)
329+
_usersWithDevices.value = _usersWithDevices.value
330+
.where(
331+
(e) => e.user.id != user.id && e.user.userInfoId != user.userInfoId,
332+
)
283333
.toList();
334+
_syncUsersFromDevices();
284335
}
285336

286337
void clearUsers() {
287-
_users.value = [];
338+
_usersWithDevices.value = [];
339+
_syncUsersFromDevices();
288340
}
289341

290342
void removeUsers(List<User> users) {
291-
_users.value = _users.value
292-
.where((element) => !users.contains(element))
343+
final ids = users.map((u) => u.id).toSet();
344+
final infoIds = users.map((u) => u.userInfoId).toSet();
345+
_usersWithDevices.value = _usersWithDevices.value
346+
.where(
347+
(e) =>
348+
!ids.contains(e.user.id) && !infoIds.contains(e.user.userInfoId),
349+
)
293350
.toList();
351+
_syncUsersFromDevices();
294352
}
295353

296354
void addUsers(List<User> users) {
297-
_users.value = [..._users.value, ...users];
355+
final list = List<UserWithDevices>.from(_usersWithDevices.value);
356+
for (final u in users) {
357+
list.add(UserWithDevices(user: u, userDevices: []));
358+
}
359+
list.sort(
360+
(a, b) => (a.user.userInfo?.userName ?? '').compareTo(
361+
b.user.userInfo?.userName ?? '',
362+
),
363+
);
364+
_usersWithDevices.value = list;
365+
_syncUsersFromDevices();
298366
}
299367

300368
void updateUsers(List<User> users) {
301-
_users.value = users;
369+
final byInfoId = {
370+
for (final uwd in _usersWithDevices.value) uwd.user.userInfoId: uwd,
371+
};
372+
final newList = <UserWithDevices>[];
373+
for (final u in users) {
374+
final existing = byInfoId[u.userInfoId];
375+
newList.add(
376+
existing != null
377+
? UserWithDevices(user: u, userDevices: existing.userDevices)
378+
: UserWithDevices(user: u, userDevices: []),
379+
);
380+
}
381+
newList.sort(
382+
(a, b) => (a.user.userInfo?.userName ?? '').compareTo(
383+
b.user.userInfo?.userName ?? '',
384+
),
385+
);
386+
_usersWithDevices.value = newList;
387+
_syncUsersFromDevices();
302388
}
303389
}

0 commit comments

Comments
 (0)