Skip to content

Commit 05017f3

Browse files
author
tevfik
committed
feat(sdk): full coverage of /auth, /sys, /admin, /admin/rules in dart+ts SDKs
Dart SDK (sdk/dart): - auth.dart: register(name?), refresh, logout, updateProfile, changePassword, forgot/resetPassword, sessions, revokeSession, keys/createKey/deleteKey, push tokens, deleteAccount, oauthProviders fallback (/auth/providers ↔ /auth/oauth/providers) - new sys.dart: health, info, time, ip, status, metrics - new admin.dart: AdminApi.{sys,users,devices,database,mqtt} covering config, registration/retention/rate-limit/alerts toggles, user CRUD, device provision/rotate/revoke, db stats/export/cleanup/ reset, mqtt stats/clients/publish - new rules.dart: list/get/create/delete/enable/disable TS SDK (sdk/ts): identical surface to Dart, same module layout. Verified: dart analyze clean, npx tsc -b clean, endpoints_test.sh 52/52 passing against local server.
1 parent 87030d3 commit 05017f3

7 files changed

Lines changed: 853 additions & 16 deletions

File tree

sdk/dart/lib/datum_sdk.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
library datum_sdk;
1414

1515
export 'src/client.dart';
16+
export 'src/admin.dart';
1617
export 'src/auth.dart';
1718
export 'src/devices.dart';
1819
export 'src/data.dart';
1920
export 'src/buckets.dart';
2021
export 'src/notify.dart';
2122
export 'src/realtime.dart';
23+
export 'src/rules.dart';
24+
export 'src/sys.dart';
2225
export 'src/db.dart';
2326
export 'src/ai.dart';
2427
export 'src/community.dart';

sdk/dart/lib/src/admin.dart

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import 'client.dart';
2+
3+
/// Admin API: covers `/admin/*` operations. Every method requires the
4+
/// authenticated principal to have role `admin`.
5+
///
6+
/// The handler tree groups operations by domain:
7+
/// - [sys]`/admin/sys/*` system configuration & logs
8+
/// - [users]`/admin/users/*` user management
9+
/// - [devices]`/admin/dev/*` cross-tenant device management
10+
/// - [database]`/admin/database/*` data ops
11+
/// - [mqtt]`/admin/mqtt/*` MQTT broker introspection
12+
class AdminApi {
13+
AdminApi(DatumClient c)
14+
: sys = AdminSysApi(c),
15+
users = AdminUsersApi(c),
16+
devices = AdminDevicesApi(c),
17+
database = AdminDatabaseApi(c),
18+
mqtt = AdminMqttApi(c);
19+
final AdminSysApi sys;
20+
final AdminUsersApi users;
21+
final AdminDevicesApi devices;
22+
final AdminDatabaseApi database;
23+
final AdminMqttApi mqtt;
24+
}
25+
26+
// ── /admin/sys/* ────────────────────────────────────────────────────────────
27+
28+
class AdminSysApi {
29+
AdminSysApi(this._c);
30+
final DatumClient _c;
31+
32+
/// GET /admin/sys/config — current system configuration.
33+
Future<Map<String, dynamic>> config() async =>
34+
Map<String, dynamic>.from(await _c.request('GET', '/admin/sys/config') as Map);
35+
36+
/// PUT /admin/sys/registration — toggle public sign-up.
37+
Future<void> setRegistration({required bool allowRegister}) async {
38+
await _c.request('PUT', '/admin/sys/registration',
39+
body: {'allow_register': allowRegister});
40+
}
41+
42+
/// PUT /admin/sys/retention — data retention policy.
43+
Future<void> setRetention({
44+
required int days,
45+
int? checkIntervalHours,
46+
}) async {
47+
await _c.request('PUT', '/admin/sys/retention', body: {
48+
'days': days,
49+
if (checkIntervalHours != null) 'check_interval_hours': checkIntervalHours,
50+
});
51+
}
52+
53+
/// PUT /admin/sys/rate-limit — global rate-limiter values.
54+
Future<void> setRateLimit({
55+
required int maxRequests,
56+
required int windowSeconds,
57+
}) async {
58+
await _c.request('PUT', '/admin/sys/rate-limit', body: {
59+
'max_requests': maxRequests,
60+
'window_seconds': windowSeconds,
61+
});
62+
}
63+
64+
/// PUT /admin/sys/alerts — alert thresholds + email enable.
65+
Future<void> setAlerts({
66+
required bool emailEnabled,
67+
required int diskThreshold,
68+
required int memoryThreshold,
69+
}) async {
70+
await _c.request('PUT', '/admin/sys/alerts', body: {
71+
'email_enabled': emailEnabled,
72+
'disk_threshold': diskThreshold,
73+
'memory_threshold': memoryThreshold,
74+
});
75+
}
76+
77+
/// GET /admin/sys/logs — recent system logs (server caps the count).
78+
Future<List<Map<String, dynamic>>> logs({String? level, String? search}) async {
79+
final res = await _c.request('GET', '/admin/sys/logs', query: {
80+
if (level != null) 'level': level,
81+
if (search != null) 'search': search,
82+
});
83+
if (res is Map && res['logs'] is List) {
84+
return List<Map<String, dynamic>>.from(
85+
(res['logs'] as List).map((e) => Map<String, dynamic>.from(e as Map)));
86+
}
87+
return const [];
88+
}
89+
90+
/// DELETE /admin/sys/logs — clears stored logs.
91+
Future<void> clearLogs() async {
92+
await _c.request('DELETE', '/admin/sys/logs');
93+
}
94+
}
95+
96+
// ── /admin/users/* ─────────────────────────────────────────────────────────
97+
98+
class AdminUsersApi {
99+
AdminUsersApi(this._c);
100+
final DatumClient _c;
101+
102+
/// GET /admin/users — list every user.
103+
Future<List<Map<String, dynamic>>> list() async {
104+
final res = await _c.request('GET', '/admin/users');
105+
if (res is Map && res['users'] is List) {
106+
return List<Map<String, dynamic>>.from(
107+
(res['users'] as List).map((e) => Map<String, dynamic>.from(e as Map)));
108+
}
109+
if (res is List) {
110+
return List<Map<String, dynamic>>.from(
111+
res.map((e) => Map<String, dynamic>.from(e as Map)));
112+
}
113+
return const [];
114+
}
115+
116+
/// GET /admin/users/:user_id — single user.
117+
Future<Map<String, dynamic>> get(String userId) async => Map<String, dynamic>.from(
118+
await _c.request('GET', '/admin/users/$userId') as Map);
119+
120+
/// PUT /admin/users/:id — change user status (`active` | `suspended`).
121+
Future<void> setStatus({required String userId, required String status}) async {
122+
await _c.request('PUT', '/admin/users/$userId', body: {'status': status});
123+
}
124+
125+
/// DELETE /admin/users/:id — permanently remove a user.
126+
Future<void> delete(String userId) async {
127+
await _c.request('DELETE', '/admin/users/$userId');
128+
}
129+
130+
/// POST /admin/users/:username/reset-password — admin-initiated reset.
131+
Future<Map<String, dynamic>> resetPassword(String username) async =>
132+
Map<String, dynamic>.from(
133+
await _c.request('POST', '/admin/users/$username/reset-password') as Map);
134+
}
135+
136+
// ── /admin/dev/* ────────────────────────────────────────────────────────────
137+
138+
class AdminDevicesApi {
139+
AdminDevicesApi(this._c);
140+
final DatumClient _c;
141+
142+
Future<List<Map<String, dynamic>>> list() async {
143+
final res = await _c.request('GET', '/admin/dev');
144+
if (res is Map && res['devices'] is List) {
145+
return List<Map<String, dynamic>>.from(
146+
(res['devices'] as List).map((e) => Map<String, dynamic>.from(e as Map)));
147+
}
148+
if (res is List) {
149+
return List<Map<String, dynamic>>.from(
150+
res.map((e) => Map<String, dynamic>.from(e as Map)));
151+
}
152+
return const [];
153+
}
154+
155+
Future<Map<String, dynamic>> get(String deviceId) async =>
156+
Map<String, dynamic>.from(
157+
await _c.request('GET', '/admin/dev/$deviceId') as Map);
158+
159+
Future<Map<String, dynamic>> provision(Map<String, dynamic> spec) async =>
160+
Map<String, dynamic>.from(
161+
await _c.request('POST', '/admin/dev', body: spec) as Map);
162+
163+
Future<void> update(String deviceId, Map<String, dynamic> patch) async {
164+
await _c.request('PUT', '/admin/dev/$deviceId', body: patch);
165+
}
166+
167+
Future<void> delete(String deviceId) async {
168+
await _c.request('DELETE', '/admin/dev/$deviceId');
169+
}
170+
171+
Future<Map<String, dynamic>> rotateKey(String deviceId) async =>
172+
Map<String, dynamic>.from(
173+
await _c.request('POST', '/admin/dev/$deviceId/rotate-key') as Map);
174+
175+
Future<void> revokeKey(String deviceId) async {
176+
await _c.request('POST', '/admin/dev/$deviceId/revoke-key');
177+
}
178+
}
179+
180+
// ── /admin/database/* ───────────────────────────────────────────────────────
181+
182+
class AdminDatabaseApi {
183+
AdminDatabaseApi(this._c);
184+
final DatumClient _c;
185+
186+
Future<Map<String, dynamic>> stats() async => Map<String, dynamic>.from(
187+
await _c.request('GET', '/admin/database/stats') as Map);
188+
189+
Future<Map<String, dynamic>> export() async => Map<String, dynamic>.from(
190+
await _c.request('POST', '/admin/database/export') as Map);
191+
192+
Future<Map<String, dynamic>> cleanup() async => Map<String, dynamic>.from(
193+
await _c.request('POST', '/admin/database/cleanup') as Map);
194+
195+
/// DELETE /admin/database/reset — DESTROYS the entire database. Requires
196+
/// `{"confirm":"RESET"}` body to actually run server-side.
197+
Future<void> reset() async {
198+
await _c.request('DELETE', '/admin/database/reset', body: {'confirm': 'RESET'});
199+
}
200+
}
201+
202+
// ── /admin/mqtt/* ───────────────────────────────────────────────────────────
203+
204+
class AdminMqttApi {
205+
AdminMqttApi(this._c);
206+
final DatumClient _c;
207+
208+
Future<Map<String, dynamic>> stats() async => Map<String, dynamic>.from(
209+
await _c.request('GET', '/admin/mqtt/stats') as Map);
210+
211+
Future<List<Map<String, dynamic>>> clients() async {
212+
final res = await _c.request('GET', '/admin/mqtt/clients');
213+
if (res is Map && res['clients'] is List) {
214+
return List<Map<String, dynamic>>.from(
215+
(res['clients'] as List).map((e) => Map<String, dynamic>.from(e as Map)));
216+
}
217+
return const [];
218+
}
219+
220+
Future<void> publish({
221+
required String topic,
222+
required String payload,
223+
int? qos,
224+
bool? retain,
225+
}) async {
226+
await _c.request('POST', '/admin/mqtt/publish', body: {
227+
'topic': topic,
228+
'payload': payload,
229+
if (qos != null) 'qos': qos,
230+
if (retain != null) 'retain': retain,
231+
});
232+
}
233+
}

0 commit comments

Comments
 (0)