feat: add data models and api service
This commit is contained in:
parent
a85c5e833e
commit
32fd7da5be
2 changed files with 242 additions and 0 deletions
135
lib/models/models.dart
Normal file
135
lib/models/models.dart
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
class AuthResponse {
|
||||||
|
final String token;
|
||||||
|
final String userId;
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
AuthResponse({required this.token, required this.userId, required this.email});
|
||||||
|
|
||||||
|
factory AuthResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AuthResponse(
|
||||||
|
token: json['token'] as String,
|
||||||
|
userId: json['userId'] as String,
|
||||||
|
email: json['email'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppsResponse {
|
||||||
|
final List<TeamWithApps> teams;
|
||||||
|
|
||||||
|
AppsResponse({required this.teams});
|
||||||
|
|
||||||
|
factory AppsResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AppsResponse(
|
||||||
|
teams: (json['teams'] as List)
|
||||||
|
.map((e) => TeamWithApps.fromJson(e))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AppInfo> get allApps => teams.expand((t) => t.apps).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TeamWithApps {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final List<AppInfo> apps;
|
||||||
|
|
||||||
|
TeamWithApps({required this.id, required this.name, required this.apps});
|
||||||
|
|
||||||
|
factory TeamWithApps.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TeamWithApps(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
apps: (json['apps'] as List).map((e) => AppInfo.fromJson(e)).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppInfo {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String packageName;
|
||||||
|
final String icon;
|
||||||
|
final Track release;
|
||||||
|
final Track debug;
|
||||||
|
final Track profile;
|
||||||
|
|
||||||
|
AppInfo({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.packageName,
|
||||||
|
required this.icon,
|
||||||
|
required this.release,
|
||||||
|
required this.debug,
|
||||||
|
required this.profile,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AppInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AppInfo(
|
||||||
|
id: json['id'],
|
||||||
|
title: json['title'],
|
||||||
|
packageName: json['packageName'],
|
||||||
|
icon: json['icon'],
|
||||||
|
release: Track.fromJson(json['release']),
|
||||||
|
debug: Track.fromJson(json['debug']),
|
||||||
|
profile: Track.fromJson(json['profile']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Track track(String name) {
|
||||||
|
switch (name) {
|
||||||
|
case 'release':
|
||||||
|
return release;
|
||||||
|
case 'debug':
|
||||||
|
return debug;
|
||||||
|
case 'profile':
|
||||||
|
return profile;
|
||||||
|
default:
|
||||||
|
return release;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Track {
|
||||||
|
final VersionInfo? current;
|
||||||
|
final VersionInfo? pending;
|
||||||
|
|
||||||
|
Track({this.current, this.pending});
|
||||||
|
|
||||||
|
factory Track.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Track(
|
||||||
|
current: json['current'] != null ? VersionInfo.fromJson(json['current']) : null,
|
||||||
|
pending: json['pending'] != null ? VersionInfo.fromJson(json['pending']) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VersionInfo {
|
||||||
|
final String id;
|
||||||
|
final String version;
|
||||||
|
final String name;
|
||||||
|
final bool public;
|
||||||
|
final bool hasFile;
|
||||||
|
final DateTime created;
|
||||||
|
|
||||||
|
VersionInfo({
|
||||||
|
required this.id,
|
||||||
|
required this.version,
|
||||||
|
required this.name,
|
||||||
|
required this.public,
|
||||||
|
required this.hasFile,
|
||||||
|
required this.created,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory VersionInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
return VersionInfo(
|
||||||
|
id: json['id'],
|
||||||
|
version: json['version'],
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
public: json['public'] ?? false,
|
||||||
|
hasFile: json['hasFile'] ?? false,
|
||||||
|
created: DateTime.parse(json['created']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
107
lib/services/api_service.dart
Normal file
107
lib/services/api_service.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import '../models/models.dart';
|
||||||
|
|
||||||
|
class ApiService {
|
||||||
|
String baseUrl;
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
ApiService({required this.baseUrl});
|
||||||
|
|
||||||
|
String? get token => _token;
|
||||||
|
bool get isAuthenticated => _token != null;
|
||||||
|
|
||||||
|
void setToken(String token) {
|
||||||
|
_token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> get _headers => {
|
||||||
|
if (_token != null) 'Authorization': 'Bearer $_token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<AuthResponse> login(String email, String password) async {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('$baseUrl/api/v1/auth'),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode({'email': email, 'password': password}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = AuthResponse.fromJson(jsonDecode(response.body));
|
||||||
|
setToken(data.token);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
final body = jsonDecode(response.body);
|
||||||
|
throw ApiException(
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
message: body['error'] ?? 'Unknown error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AppsResponse> getApps() async {
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse('$baseUrl/api/v1/apps'),
|
||||||
|
headers: _headers,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return AppsResponse.fromJson(jsonDecode(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode == 401) {
|
||||||
|
throw AuthException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final body = jsonDecode(response.body);
|
||||||
|
throw ApiException(
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
message: body['error'] ?? 'Unknown error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.StreamedResponse> downloadVersion(String versionId) async {
|
||||||
|
final request = http.Request(
|
||||||
|
'GET',
|
||||||
|
Uri.parse('$baseUrl/api/v1/versions/$versionId/download'),
|
||||||
|
);
|
||||||
|
request.headers.addAll(_headers);
|
||||||
|
|
||||||
|
final response = await http.Client().send(request);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode == 401) {
|
||||||
|
throw AuthException();
|
||||||
|
}
|
||||||
|
|
||||||
|
response.stream.drain();
|
||||||
|
final body = await response.stream.bytesToString();
|
||||||
|
final parsed = jsonDecode(body);
|
||||||
|
throw ApiException(
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
message: parsed['error'] ?? 'Download failed',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String iconUrl(String iconPath) {
|
||||||
|
if (iconPath.startsWith('http')) return iconPath;
|
||||||
|
return '$baseUrl$iconPath';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApiException implements Exception {
|
||||||
|
final int statusCode;
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
ApiException({required this.statusCode, required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthException implements Exception {}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue