Pipeline registrazione/upload con storage S3, archivio web e app, proxy replay per il browser, OAuth YouTube sulla pagina club (Premium Full) e footer legale. Co-authored-by: Cursor <cursoragent@cursor.com>
291 lines
8.8 KiB
Dart
291 lines
8.8 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../core/config.dart';
|
|
import '../providers/auth_provider.dart';
|
|
import 'models/invite_preview.dart';
|
|
import 'models/match.dart';
|
|
import 'models/recording_item.dart';
|
|
import 'models/stream_session.dart';
|
|
import 'models/team.dart';
|
|
import 'models/user.dart';
|
|
|
|
final apiClientProvider = Provider<ApiClient>((ref) {
|
|
final auth = ref.watch(authProvider);
|
|
return ApiClient(accessToken: auth.accessToken);
|
|
});
|
|
|
|
class ApiClient {
|
|
ApiClient({this.accessToken}) {
|
|
_dio = Dio(
|
|
BaseOptions(
|
|
baseUrl: AppConfig.apiV1,
|
|
connectTimeout: const Duration(seconds: 15),
|
|
receiveTimeout: const Duration(seconds: 15),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
),
|
|
);
|
|
|
|
_dio.interceptors.add(
|
|
InterceptorsWrapper(
|
|
onRequest: (options, handler) {
|
|
if (accessToken != null && accessToken!.isNotEmpty) {
|
|
options.headers['Authorization'] = 'Bearer $accessToken';
|
|
}
|
|
handler.next(options);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
late final Dio _dio;
|
|
final String? accessToken;
|
|
|
|
// --- Auth ---
|
|
|
|
Future<({User user, AuthTokens tokens})> login({
|
|
required String email,
|
|
required String password,
|
|
}) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/auth/login',
|
|
data: {'email': email, 'password': password},
|
|
);
|
|
final data = response.data!;
|
|
return (
|
|
user: User.fromJson(data['user'] as Map<String, dynamic>),
|
|
tokens: AuthTokens.fromJson(data),
|
|
);
|
|
}
|
|
|
|
Future<AuthTokens> refresh(String refreshToken) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/auth/refresh',
|
|
data: {'refresh_token': refreshToken},
|
|
);
|
|
return AuthTokens.fromJson(response.data!);
|
|
}
|
|
|
|
Future<User> me() async {
|
|
final response = await _dio.get<Map<String, dynamic>>('/auth/me');
|
|
return User.fromJson(response.data!);
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
await _dio.post('/auth/logout');
|
|
}
|
|
|
|
// --- Teams ---
|
|
|
|
Future<List<Team>> fetchTeams() async {
|
|
final response = await _dio.get<List<dynamic>>('/teams');
|
|
return response.data!
|
|
.map((e) => Team.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
Future<InvitePreview> fetchInvitePreview(String token) async {
|
|
final response = await _dio.get<Map<String, dynamic>>('/invitations/$token');
|
|
return InvitePreview.fromJson(response.data!);
|
|
}
|
|
|
|
Future<String> acceptInvitation(String token) async {
|
|
final response = await _dio.post<Map<String, dynamic>>('/invitations/$token/accept');
|
|
return response.data!['message'] as String? ?? 'Invito accettato';
|
|
}
|
|
|
|
Future<String> youtubeAuthorizeUrl(String teamId, {bool returnApp = true}) async {
|
|
final response = await _dio.get<Map<String, dynamic>>(
|
|
'/teams/$teamId/youtube/authorize',
|
|
queryParameters: returnApp ? {'return_app': '1'} : null,
|
|
);
|
|
return response.data!['authorization_url'] as String;
|
|
}
|
|
|
|
Future<List<RecordingItem>> fetchRecordings(String teamId) async {
|
|
final response = await _dio.get<List<dynamic>>('/teams/$teamId/recordings');
|
|
return response.data!
|
|
.map((e) => RecordingItem.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
Future<String> fetchRecordingDownloadUrl(String recordingId) async {
|
|
final response = await _dio.get<Map<String, dynamic>>(
|
|
'/recordings/$recordingId/download',
|
|
);
|
|
return response.data!['download_url'] as String;
|
|
}
|
|
|
|
// --- Matches ---
|
|
|
|
Future<List<MatchModel>> fetchMatches(String teamId) async {
|
|
final response =
|
|
await _dio.get<List<dynamic>>('/teams/$teamId/matches');
|
|
return response.data!
|
|
.map((e) => MatchModel.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
Future<MatchModel> fetchMatch(String matchId) async {
|
|
final response =
|
|
await _dio.get<Map<String, dynamic>>('/matches/$matchId');
|
|
return MatchModel.fromJson(response.data!);
|
|
}
|
|
|
|
Future<MatchModel> updateMatch(String matchId, Map<String, dynamic> body) async {
|
|
final response = await _dio.patch<Map<String, dynamic>>(
|
|
'/matches/$matchId',
|
|
data: {'match': body},
|
|
);
|
|
return MatchModel.fromJson(response.data!);
|
|
}
|
|
|
|
Future<void> deleteMatch(String matchId) async {
|
|
await _dio.delete<void>('/matches/$matchId');
|
|
}
|
|
|
|
Future<MatchModel> createMatch(String teamId, Map<String, dynamic> body) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/teams/$teamId/matches',
|
|
data: {'match': body},
|
|
);
|
|
return MatchModel.fromJson(response.data!);
|
|
}
|
|
|
|
// --- Sessions ---
|
|
|
|
Future<StreamSession> createSession(
|
|
String matchId, {
|
|
String platform = 'matchlivetv',
|
|
String? youtubeChannel,
|
|
String privacyStatus = 'public',
|
|
String qualityPreset = '720p_30_2.5mbps',
|
|
int? targetBitrate,
|
|
int? targetFps,
|
|
}) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/matches/$matchId/sessions',
|
|
data: {
|
|
'platform': platform,
|
|
'privacy_status': privacyStatus,
|
|
'quality_preset': qualityPreset,
|
|
if (youtubeChannel != null) 'youtube_channel': youtubeChannel,
|
|
if (targetBitrate != null) 'target_bitrate': targetBitrate,
|
|
if (targetFps != null) 'target_fps': targetFps,
|
|
},
|
|
);
|
|
return StreamSession.fromJson(response.data!);
|
|
}
|
|
|
|
Future<StreamSession> fetchSession(String sessionId) async {
|
|
final response =
|
|
await _dio.get<Map<String, dynamic>>('/sessions/$sessionId');
|
|
return StreamSession.fromJson(response.data!);
|
|
}
|
|
|
|
Future<StreamSession> startSession(String sessionId) async {
|
|
final response =
|
|
await _dio.patch<Map<String, dynamic>>('/sessions/$sessionId/start');
|
|
return StreamSession.fromJson(response.data!);
|
|
}
|
|
|
|
Future<StreamSession> stopSession(String sessionId) async {
|
|
final response =
|
|
await _dio.patch<Map<String, dynamic>>('/sessions/$sessionId/stop');
|
|
return StreamSession.fromJson(response.data!);
|
|
}
|
|
|
|
Future<StreamSession> pauseSession(String sessionId) async {
|
|
final response =
|
|
await _dio.patch<Map<String, dynamic>>('/sessions/$sessionId/pause');
|
|
return StreamSession.fromJson(response.data!);
|
|
}
|
|
|
|
Future<StreamSession> resumeSession(String sessionId) async {
|
|
final response =
|
|
await _dio.patch<Map<String, dynamic>>('/sessions/$sessionId/resume');
|
|
return StreamSession.fromJson(response.data!);
|
|
}
|
|
|
|
Future<RegiaLinkResponse> createRegiaLink(String sessionId) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/sessions/$sessionId/regia_link',
|
|
);
|
|
return RegiaLinkResponse.fromJson(response.data!);
|
|
}
|
|
|
|
Future<PairingTokenResponse> createPairingToken(String sessionId) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/sessions/$sessionId/pairing_token',
|
|
);
|
|
return PairingTokenResponse.fromJson(response.data!);
|
|
}
|
|
|
|
Future<StreamSession> claimPairing({
|
|
required String sessionId,
|
|
required String pairingToken,
|
|
required String deviceRole,
|
|
}) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/sessions/$sessionId/claim_pairing',
|
|
data: {
|
|
'pairing_token': pairingToken,
|
|
'device_role': deviceRole,
|
|
},
|
|
);
|
|
return StreamSession.fromJson(response.data!);
|
|
}
|
|
|
|
Future<NetworkTestResult> networkTest({
|
|
required String sessionId,
|
|
required double downloadMbps,
|
|
required double uploadMbps,
|
|
required int latencyMs,
|
|
required String networkType,
|
|
}) async {
|
|
final response = await _dio.post<Map<String, dynamic>>(
|
|
'/sessions/$sessionId/network_test',
|
|
data: {
|
|
'download_mbps': downloadMbps,
|
|
'upload_mbps': uploadMbps,
|
|
'latency_ms': latencyMs,
|
|
'network_type': networkType,
|
|
},
|
|
);
|
|
return NetworkTestResult.fromJson(response.data!);
|
|
}
|
|
|
|
Future<YoutubeStats> youtubeStats(String sessionId) async {
|
|
final response =
|
|
await _dio.get<Map<String, dynamic>>('/sessions/$sessionId/youtube_stats');
|
|
return YoutubeStats.fromJson(response.data!);
|
|
}
|
|
|
|
Future<void> postTelemetry({
|
|
required String sessionId,
|
|
required String deviceRole,
|
|
int? batteryLevel,
|
|
String? networkType,
|
|
int? signalStrength,
|
|
int? currentBitrate,
|
|
int? targetBitrate,
|
|
int? fps,
|
|
}) async {
|
|
await _dio.post(
|
|
'/sessions/$sessionId/telemetry',
|
|
data: {
|
|
'device_role': deviceRole,
|
|
if (batteryLevel != null) 'battery_level': batteryLevel,
|
|
if (networkType != null) 'network_type': networkType,
|
|
if (signalStrength != null) 'signal_strength': signalStrength,
|
|
if (currentBitrate != null) 'current_bitrate': currentBitrate,
|
|
if (targetBitrate != null) 'target_bitrate': targetBitrate,
|
|
if (fps != null) 'fps': fps,
|
|
},
|
|
);
|
|
}
|
|
}
|