Setting up basic API Structure

This commit is contained in:
2026-08-28 14:54:54 +02:00
parent 968228cbc3
commit b3a13cd5a2
18 changed files with 974 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
import 'package:ac_web/Api/Dtos/AgentTaskDto.dart';
import 'package:ac_web/Api/Dtos/EAgentStatus.dart';
import 'package:ac_web/Api/Dtos/EAgentType.dart';
import 'package:ac_web/Api/JsonReader.dart';
/// Dart counterpart of the backend `AgentData` returned by `GET /agents`.
class AgentDto {
const AgentDto({
required this.name,
required this.type,
required this.lastSeen,
required this.tasks,
required this.status,
});
final String name;
final EAgentType type;
final DateTime lastSeen;
final List<AgentTaskDto> tasks;
final EAgentStatus status;
bool get hasTasks => tasks.isNotEmpty;
factory AgentDto.fromJson(Map<String, dynamic> json) {
return AgentDto(
name: JsonReader.requireString(json, 'Name'),
type: JsonReader.enumValue(
json,
'Type',
EAgentType.values,
(value) => value.name,
EAgentType.unknown,
),
lastSeen: JsonReader.requireDateTime(json, 'LastSeen'),
tasks: JsonReader.list(json, 'Tasks', AgentTaskDto.fromJson),
status: JsonReader.enumValue(
json,
'Status',
EAgentStatus.values,
(value) => value.name,
EAgentStatus.unknown,
),
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'Name': name,
'Type': type.name,
'LastSeen': lastSeen.toIso8601String(),
'Tasks': tasks.map((task) => task.toJson()).toList(growable: false),
'Status': status.name,
};
}
@override
String toString() => 'AgentDto(name: $name, type: ${type.name}, status: ${status.name})';
}
+58
View File
@@ -0,0 +1,58 @@
import 'package:ac_web/Api/JsonReader.dart';
/// Dart counterpart of the backend `AgentTaskDto`.
///
/// The exact shape of the backend dto is not final yet, therefore everything
/// except the name is treated as optional. Unknown properties are simply
/// ignored, so this dto keeps working while the api grows.
class AgentTaskDto {
const AgentTaskDto({
required this.name,
this.id,
this.description,
this.status,
this.progress,
this.createdAt,
this.finishedAt,
});
final String name;
final String? id;
final String? description;
final String? status;
/// Completion between 0 and 100 if the api reports it.
final int? progress;
final DateTime? createdAt;
final DateTime? finishedAt;
bool get isFinished => finishedAt != null;
factory AgentTaskDto.fromJson(Map<String, dynamic> json) {
return AgentTaskDto(
name: JsonReader.optionalString(json, 'Name') ?? '',
id: JsonReader.optionalString(json, 'Id'),
description: JsonReader.optionalString(json, 'Description'),
status: JsonReader.optionalString(json, 'Status'),
progress: JsonReader.optionalInt(json, 'Progress'),
createdAt: JsonReader.optionalDateTime(json, 'CreatedAt'),
finishedAt: JsonReader.optionalDateTime(json, 'FinishedAt'),
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'Name': name,
if (id != null) 'Id': id,
if (description != null) 'Description': description,
if (status != null) 'Status': status,
if (progress != null) 'Progress': progress,
if (createdAt != null) 'CreatedAt': createdAt!.toIso8601String(),
if (finishedAt != null) 'FinishedAt': finishedAt!.toIso8601String(),
};
}
@override
String toString() => 'AgentTaskDto(name: $name, status: $status)';
}
+27
View File
@@ -0,0 +1,27 @@
/// Mirror of the backend `EAgentStatus` enum.
///
/// See the note in `EAgentType.dart` about ordering and [unknown].
enum EAgentStatus {
offline,
idle,
working,
error,
unknown;
bool get isOnline => this == EAgentStatus.idle || this == EAgentStatus.working;
String get displayName {
switch (this) {
case EAgentStatus.offline:
return 'Offline';
case EAgentStatus.idle:
return 'Idle';
case EAgentStatus.working:
return 'Working';
case EAgentStatus.error:
return 'Error';
case EAgentStatus.unknown:
return 'Unknown';
}
}
}
+29
View File
@@ -0,0 +1,29 @@
/// Mirror of the backend `EAgentType` enum.
///
/// The order of the values has to match the order in the .Net enum, because
/// the api may serialize enums as their numeric value. [unknown] is kept last
/// so that it never shadows a real backend value and acts as fallback for
/// values this client does not know yet.
enum EAgentType {
developer,
tester,
reviewer,
manager,
unknown;
/// Human readable label, e.g. for chips or table cells.
String get displayName {
switch (this) {
case EAgentType.developer:
return 'Developer';
case EAgentType.tester:
return 'Tester';
case EAgentType.reviewer:
return 'Reviewer';
case EAgentType.manager:
return 'Manager';
case EAgentType.unknown:
return 'Unknown';
}
}
}
+45
View File
@@ -0,0 +1,45 @@
import 'package:ac_web/Api/JsonReader.dart';
/// Dart counterpart of the backend `ReportDto` returned by `GET /reports`.
class ReportDto {
const ReportDto({
required this.id,
required this.author,
required this.timestamp,
required this.type,
required this.text,
});
final String id;
final String author;
final DateTime timestamp;
/// Free form report type. The backend sends this as a plain string, so it is
/// intentionally not modelled as an enum here.
final String type;
final String text;
factory ReportDto.fromJson(Map<String, dynamic> json) {
return ReportDto(
id: JsonReader.requireString(json, 'Id'),
author: JsonReader.requireString(json, 'Author'),
timestamp: JsonReader.requireDateTime(json, 'Timestamp'),
type: JsonReader.optionalString(json, 'Type') ?? '',
text: JsonReader.optionalString(json, 'Text') ?? '',
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'Id': id,
'Author': author,
'Timestamp': timestamp.toIso8601String(),
'Type': type,
'Text': text,
};
}
@override
String toString() => 'ReportDto(id: $id, author: $author, type: $type)';
}