feat: type schedule and auth models

This commit is contained in:
Artem Kokos
2026-04-23 20:57:15 +07:00
parent fa403bfcce
commit 0fdaf0bac4
11 changed files with 531 additions and 243 deletions

View File

@@ -0,0 +1,105 @@
class ApiKeyInfo {
final String key;
final String name;
final bool isAdmin;
final bool isActive;
final DateTime? createdAt;
const ApiKeyInfo({
required this.key,
required this.name,
required this.isAdmin,
required this.isActive,
this.createdAt,
});
String get formattedCreatedAt {
final value = createdAt;
if (value == null) return '';
String pad(int n) => n.toString().padLeft(2, '0');
return '${pad(value.day)}.${pad(value.month)}.${value.year}';
}
static ApiKeyInfo fromApi(Object? data, {String? fallbackKey}) {
if (data is! Map) {
final key = data?.toString() ?? fallbackKey;
if (key == null || key.isEmpty) {
throw const FormatException('api key должен быть объектом или токеном');
}
return ApiKeyInfo(key: key, name: key, isAdmin: false, isActive: true);
}
final map = Map<String, dynamic>.from(data);
final key =
_stringValue(map, const ['key', 'token', 'api_key']) ?? fallbackKey;
if (key == null || key.isEmpty) {
throw const FormatException('api key не содержит токен');
}
return ApiKeyInfo(
key: key,
name: _stringValue(map, const ['name', 'label']) ?? 'Без имени',
isAdmin: _boolValue(map['is_admin']),
isActive: _boolValue(map['is_active'] ?? map['active'], fallback: true),
createdAt: DateTime.tryParse(map['created_at']?.toString() ?? ''),
);
}
static List<ApiKeyInfo> listFromApi(Object? data) {
final values = _collectionValues(data, const ['data', 'keys']);
return values.map((value) {
if (value.entryKey == null) return ApiKeyInfo.fromApi(value.value);
return ApiKeyInfo.fromApi(value.value, fallbackKey: value.entryKey);
}).toList();
}
}
bool _boolValue(Object? value, {bool fallback = false}) {
if (value is bool) return value;
if (value is num) return value != 0;
if (value is String) {
final normalized = value.trim().toLowerCase();
if (normalized == 'true' || normalized == '1') return true;
if (normalized == 'false' || normalized == '0') return false;
}
return fallback;
}
String? _stringValue(Map<String, dynamic> map, List<String> keys) {
for (final key in keys) {
final value = map[key];
if (value != null && value.toString().isNotEmpty) {
return value.toString();
}
}
return null;
}
List<_CollectionValue> _collectionValues(Object? data, List<String> wrappers) {
if (data is List) {
return data.map((value) => _CollectionValue(value)).toList();
}
if (data is Map) {
final map = Map<String, dynamic>.from(data);
for (final wrapper in wrappers) {
final value = map[wrapper];
if (value is List) {
return value.map((item) => _CollectionValue(item)).toList();
}
}
return map.entries
.map((entry) => _CollectionValue(entry.value, entryKey: entry.key))
.toList();
}
throw const FormatException('ожидался список или объект');
}
class _CollectionValue {
final Object? value;
final String? entryKey;
const _CollectionValue(this.value, {this.entryKey});
}

28
lib/models/auth_info.dart Normal file
View File

@@ -0,0 +1,28 @@
class AuthInfo {
final bool isAdmin;
final String? name;
const AuthInfo({required this.isAdmin, this.name});
static AuthInfo fromApi(Object? data) {
if (data is! Map) {
throw const FormatException('auth/me должен быть объектом');
}
final map = Map<String, dynamic>.from(data);
return AuthInfo(
isAdmin: _boolValue(map['is_admin']),
name: map['name']?.toString(),
);
}
}
bool _boolValue(Object? value) {
if (value is bool) return value;
if (value is num) return value != 0;
if (value is String) {
final normalized = value.trim().toLowerCase();
return normalized == 'true' || normalized == '1';
}
return false;
}

View File

@@ -0,0 +1,136 @@
class ScheduleTask {
final String jobId;
final String targetId;
final bool? targetState;
final String type;
final String? runAt;
final String? cron;
final String? hour;
final String? minute;
final String? dayOfWeek;
const ScheduleTask({
required this.jobId,
required this.targetId,
required this.type,
this.targetState,
this.runAt,
this.cron,
this.hour,
this.minute,
this.dayOfWeek,
});
bool get isCron => type == 'cron' || cron != null;
String get actionText {
return targetState == true
? 'Включить'
: targetState == false
? 'Выключить'
: '?';
}
String get title => '$actionText - $targetId';
String get subtitle {
final lines = <String>['Цель: $targetId'];
if (runAt != null && runAt!.isNotEmpty) lines.add('Запуск: $runAt');
if (cron != null && cron!.isNotEmpty) lines.add('Cron: $cron');
if (hour != null && minute != null) lines.add('Время: $hour:$minute');
if (dayOfWeek != null && dayOfWeek != '*') {
lines.add('Дни: $dayOfWeek');
}
return lines.join('\n');
}
static ScheduleTask fromApi(Object? data, {String? fallbackId}) {
if (data is! Map) {
final id = data?.toString() ?? fallbackId;
if (id == null || id.isEmpty) {
throw const FormatException('schedule task должен быть объектом');
}
return ScheduleTask(jobId: id, targetId: '', type: 'once');
}
final map = Map<String, dynamic>.from(data);
final jobId = _stringValue(map, const ['id', 'job_id']) ?? fallbackId;
if (jobId == null || jobId.isEmpty) {
throw const FormatException('schedule task не содержит id/job_id');
}
final cron = map['cron']?.toString();
final type =
_stringValue(map, const ['type']) ?? (cron != null ? 'cron' : 'once');
return ScheduleTask(
jobId: jobId,
targetId: _stringValue(map, const ['target_id', 'target']) ?? '',
targetState: _boolValue(map['state']),
type: type,
runAt: _stringValue(map, const ['run_at', 'next_run', 'next_run_time']),
cron: cron,
hour: map['hour']?.toString(),
minute: map['minute']?.toString(),
dayOfWeek: map['day_of_week']?.toString(),
);
}
static List<ScheduleTask> listFromApi(Object? data) {
final values = _collectionValues(data, const ['tasks', 'data']);
return values.map((value) {
if (value.entryKey == null) return ScheduleTask.fromApi(value.value);
return ScheduleTask.fromApi(value.value, fallbackId: value.entryKey);
}).toList();
}
}
bool? _boolValue(Object? value) {
if (value is bool) return value;
if (value is num) return value != 0;
if (value is String) {
final normalized = value.trim().toLowerCase();
if (normalized == 'true' || normalized == '1') return true;
if (normalized == 'false' || normalized == '0') return false;
}
return null;
}
String? _stringValue(Map<String, dynamic> map, List<String> keys) {
for (final key in keys) {
final value = map[key];
if (value != null && value.toString().isNotEmpty) {
return value.toString();
}
}
return null;
}
List<_CollectionValue> _collectionValues(Object? data, List<String> wrappers) {
if (data is List) {
return data.map((value) => _CollectionValue(value)).toList();
}
if (data is Map) {
final map = Map<String, dynamic>.from(data);
for (final wrapper in wrappers) {
final value = map[wrapper];
if (value is List) {
return value.map((item) => _CollectionValue(item)).toList();
}
}
return map.entries
.map((entry) => _CollectionValue(entry.value, entryKey: entry.key))
.toList();
}
throw const FormatException('ожидался список или объект');
}
class _CollectionValue {
final Object? value;
final String? entryKey;
const _CollectionValue(this.value, {this.entryKey});
}