feat(flutter): admin DTOs (request, quarantine item, user, invite)
Hand-rolled to match the server's actual JSON shapes: - AdminRequest carries user_id (UUID, not username); kind switches which title field is the display name - AdminQuarantineItem aggregates reports per track with reason_counts and a nested reports[] list, mirroring adminQueueRowView - AdminUser includes display_name + auto_approve_requests - Invite uses invited_by UUID + note (server hardcodes 24h TTL) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/// One user's report under an aggregated quarantine row.
|
||||
class AdminQuarantineReport {
|
||||
const AdminQuarantineReport({
|
||||
required this.userId,
|
||||
required this.username,
|
||||
required this.reason,
|
||||
this.notes,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
final String userId;
|
||||
final String username;
|
||||
final String reason;
|
||||
final String? notes;
|
||||
final String createdAt;
|
||||
|
||||
factory AdminQuarantineReport.fromJson(Map<String, dynamic> j) =>
|
||||
AdminQuarantineReport(
|
||||
userId: j['user_id'] as String? ?? '',
|
||||
username: j['username'] as String? ?? '',
|
||||
reason: j['reason'] as String? ?? '',
|
||||
notes: j['notes'] as String?,
|
||||
createdAt: j['created_at'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// Mirrors `adminQueueRowView` from internal/api/admin_quarantine.go.
|
||||
/// One row aggregates all reports against a single track, with
|
||||
/// per-reason counts and the underlying per-user reports nested.
|
||||
class AdminQuarantineItem {
|
||||
const AdminQuarantineItem({
|
||||
required this.trackId,
|
||||
required this.trackTitle,
|
||||
required this.artistName,
|
||||
required this.albumTitle,
|
||||
required this.albumId,
|
||||
this.lidarrAlbumMbid,
|
||||
required this.reportCount,
|
||||
required this.latestAt,
|
||||
required this.reasonCounts,
|
||||
required this.reports,
|
||||
});
|
||||
|
||||
final String trackId;
|
||||
final String trackTitle;
|
||||
final String artistName;
|
||||
final String albumTitle;
|
||||
final String albumId;
|
||||
final String? lidarrAlbumMbid;
|
||||
final int reportCount;
|
||||
final String latestAt;
|
||||
|
||||
/// Map of reason → number of reports citing that reason.
|
||||
final Map<String, int> reasonCounts;
|
||||
|
||||
final List<AdminQuarantineReport> reports;
|
||||
|
||||
/// One-line summary of the most-cited reason. Returns "<top>" when
|
||||
/// only one reason exists or "<top> (+N more)" when there are
|
||||
/// additional distinct reasons.
|
||||
String get topReasonSummary {
|
||||
if (reasonCounts.isEmpty) return '';
|
||||
final entries = reasonCounts.entries.toList()
|
||||
..sort((a, b) => b.value.compareTo(a.value));
|
||||
final top = entries.first.key;
|
||||
return entries.length > 1
|
||||
? '$top (+${entries.length - 1} more)'
|
||||
: top;
|
||||
}
|
||||
|
||||
factory AdminQuarantineItem.fromJson(Map<String, dynamic> j) {
|
||||
final reasonCountsRaw =
|
||||
(j['reason_counts'] as Map?)?.cast<String, dynamic>() ?? const {};
|
||||
final reportsRaw = (j['reports'] as List?) ?? const [];
|
||||
return AdminQuarantineItem(
|
||||
trackId: j['track_id'] as String? ?? '',
|
||||
trackTitle: j['track_title'] as String? ?? '',
|
||||
artistName: j['artist_name'] as String? ?? '',
|
||||
albumTitle: j['album_title'] as String? ?? '',
|
||||
albumId: j['album_id'] as String? ?? '',
|
||||
lidarrAlbumMbid: j['lidarr_album_mbid'] as String?,
|
||||
reportCount: (j['report_count'] as int?) ?? 0,
|
||||
latestAt: j['latest_at'] as String? ?? '',
|
||||
reasonCounts:
|
||||
reasonCountsRaw.map((k, v) => MapEntry(k, (v as int?) ?? 0)),
|
||||
reports: reportsRaw
|
||||
.map((e) =>
|
||||
AdminQuarantineReport.fromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(growable: false),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user