init
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
class Account {
|
||||
const Account({
|
||||
required this.id,
|
||||
required this.username,
|
||||
required this.isAdmin,
|
||||
required this.permissions,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String username;
|
||||
final bool isAdmin;
|
||||
final Map<String, dynamic> permissions;
|
||||
|
||||
bool get canReadSupport => isAdmin || _supportFlag('read');
|
||||
|
||||
bool get canWriteSupport => isAdmin || _supportFlag('write');
|
||||
|
||||
bool _supportFlag(String key) {
|
||||
final support = permissions['support'];
|
||||
if (support is Map) {
|
||||
return support[key] == true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
factory Account.fromJson(Map<String, dynamic> json) {
|
||||
return Account(
|
||||
id: json['id']?.toString() ?? '',
|
||||
username: json['username'] as String? ?? '',
|
||||
isAdmin: json['isAdmin'] == true,
|
||||
permissions: _stringKeyMap(json['permissions']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'username': username,
|
||||
'isAdmin': isAdmin,
|
||||
'permissions': permissions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _stringKeyMap(Object? value) {
|
||||
if (value is! Map) {
|
||||
return {};
|
||||
}
|
||||
return value.map((key, item) => MapEntry(key.toString(), item));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
class ApiException implements Exception {
|
||||
const ApiException({
|
||||
this.statusCode,
|
||||
required this.message,
|
||||
this.retryAfterSeconds,
|
||||
});
|
||||
|
||||
final int? statusCode;
|
||||
final String message;
|
||||
final int? retryAfterSeconds;
|
||||
|
||||
bool get isUnauthorized => statusCode == 401;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
|
||||
static ApiException fromBody({
|
||||
int? statusCode,
|
||||
Object? body,
|
||||
String fallback = 'Something went wrong.',
|
||||
}) {
|
||||
var message = fallback;
|
||||
int? retryAfterSeconds;
|
||||
if (body is Map) {
|
||||
final error = body['error'];
|
||||
if (error is String && error.trim().isNotEmpty) {
|
||||
message = error.trim();
|
||||
}
|
||||
final retry = body['retryAfterSeconds'];
|
||||
if (retry is int) {
|
||||
retryAfterSeconds = retry;
|
||||
} else if (retry is num) {
|
||||
retryAfterSeconds = retry.toInt();
|
||||
}
|
||||
}
|
||||
if (retryAfterSeconds != null) {
|
||||
message = '$message Retry after $retryAfterSeconds seconds.';
|
||||
}
|
||||
return ApiException(
|
||||
statusCode: statusCode,
|
||||
message: message,
|
||||
retryAfterSeconds: retryAfterSeconds,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'account.dart';
|
||||
|
||||
class LoginResult {
|
||||
const LoginResult({required this.token, required this.account});
|
||||
|
||||
final String token;
|
||||
final Account account;
|
||||
|
||||
factory LoginResult.fromJson(Map<String, dynamic> json) {
|
||||
final token = json['token'];
|
||||
final account = json['account'];
|
||||
if (token is! String || token.isEmpty || account is! Map) {
|
||||
throw const FormatException('Unexpected login payload');
|
||||
}
|
||||
return LoginResult(
|
||||
token: token,
|
||||
account: Account.fromJson(Map<String, dynamic>.from(account)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'support_ticket.dart';
|
||||
|
||||
const maxReplyLength = 4000;
|
||||
|
||||
class SupportMessage {
|
||||
const SupportMessage({
|
||||
required this.id,
|
||||
required this.ticketId,
|
||||
required this.authorKind,
|
||||
required this.authorName,
|
||||
required this.body,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String ticketId;
|
||||
final String authorKind;
|
||||
final String authorName;
|
||||
final String body;
|
||||
final DateTime createdAt;
|
||||
|
||||
bool get isStaff => authorKind == 'staff';
|
||||
|
||||
factory SupportMessage.fromJson(Map<String, dynamic> json) {
|
||||
return SupportMessage(
|
||||
id: json['id'] as String,
|
||||
ticketId: json['ticketId'] as String,
|
||||
authorKind: json['authorKind'] as String? ?? 'player',
|
||||
authorName: json['authorName'] as String? ?? '',
|
||||
body: json['body'] as String? ?? '',
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SupportThread {
|
||||
const SupportThread({required this.ticket, required this.messages});
|
||||
|
||||
final SupportTicket ticket;
|
||||
final List<SupportMessage> messages;
|
||||
|
||||
factory SupportThread.fromJson(Map<String, dynamic> json) {
|
||||
final ticketJson = json['ticket'];
|
||||
final messagesJson = json['messages'];
|
||||
if (ticketJson is! Map || messagesJson is! List) {
|
||||
throw const FormatException('Unexpected thread payload');
|
||||
}
|
||||
return SupportThread(
|
||||
ticket: SupportTicket.fromJson(Map<String, dynamic>.from(ticketJson)),
|
||||
messages: [
|
||||
for (final item in messagesJson)
|
||||
if (item is Map)
|
||||
SupportMessage.fromJson(Map<String, dynamic>.from(item)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an error message when [raw] is not 1–4000 characters after trim.
|
||||
String? validateReplyBody(String raw) {
|
||||
final body = raw.trim();
|
||||
if (body.isEmpty) {
|
||||
return 'Enter a message.';
|
||||
}
|
||||
if (body.length > maxReplyLength) {
|
||||
return 'Message must be $maxReplyLength characters or fewer.';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
class SupportTicket {
|
||||
const SupportTicket({
|
||||
required this.id,
|
||||
required this.userId,
|
||||
required this.username,
|
||||
required this.category,
|
||||
required this.categoryLabel,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.lastMessageAt,
|
||||
required this.lastMessagePreview,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final int userId;
|
||||
final String username;
|
||||
final String category;
|
||||
final String categoryLabel;
|
||||
final String status;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final DateTime lastMessageAt;
|
||||
final String lastMessagePreview;
|
||||
|
||||
bool get isClosed => status == 'closed';
|
||||
|
||||
String get displayCategory =>
|
||||
categoryLabel.isNotEmpty ? categoryLabel : category;
|
||||
|
||||
String get statusLabel => isClosed ? 'Closed' : 'Open';
|
||||
|
||||
factory SupportTicket.fromJson(Map<String, dynamic> json) {
|
||||
return SupportTicket(
|
||||
id: json['id'] as String,
|
||||
userId: (json['userId'] as num).toInt(),
|
||||
username: json['username'] as String? ?? '',
|
||||
category: json['category'] as String? ?? '',
|
||||
categoryLabel: json['categoryLabel'] as String? ?? '',
|
||||
status: json['status'] as String? ?? 'open',
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
lastMessageAt: DateTime.parse(json['lastMessageAt'] as String),
|
||||
lastMessagePreview: json['lastMessagePreview'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user