Initial commit: Uruk Prize Platform architecture, backend core, mobile app, gateway caller & deployment pipeline

This commit is contained in:
Hamza-Ayed
2026-09-18 17:12:05 +03:00
commit 879dd36f1b
1149 changed files with 97122 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:uuid/uuid.dart';
import '../constants/app_constants.dart';
import '../security/fingerprint_service.dart';
class ApiClient {
final String baseUrl;
final http.Client _httpClient;
final FingerprintService _fingerprintService;
ApiClient({
String? baseUrl,
http.Client? httpClient,
FingerprintService? fingerprintService,
}) : baseUrl = baseUrl ?? AppConstants.defaultBaseUrl,
_httpClient = httpClient ?? http.Client(),
_fingerprintService = fingerprintService ?? FingerprintService();
/// Executes an HTTP request with automatic header, device fingerprint, and HMAC signature injection.
Future<Map<String, dynamic>> sendRequest({
required String method,
required String path,
Map<String, dynamic>? body,
Map<String, String>? queryParams,
bool requiresAuth = true,
}) async {
Uri uri = Uri.parse('$baseUrl$path');
if (queryParams != null && queryParams.isNotEmpty) {
uri = uri.replace(queryParameters: queryParams);
}
final headers = <String, String>{
'Content-Type': 'application/json',
'Accept': 'application/json',
};
final rawBody = body != null ? jsonEncode(body) : '';
if (requiresAuth) {
final userId = await _fingerprintService.getUserId();
final token = await _fingerprintService.getSessionToken();
final deviceSecret = await _fingerprintService.getDeviceSecret();
final fingerprint = await _fingerprintService.getDeviceFingerprint();
final timestamp = (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
final nonce = const Uuid().v4();
if (userId != null) {
headers['X-User-Id'] = userId.toString();
}
headers['X-Device-Fingerprint'] = fingerprint;
headers['X-Timestamp'] = timestamp;
headers['X-Nonce'] = nonce;
if (token != null) {
headers['Authorization'] = 'Bearer $token';
}
// Calculate HMAC-SHA256 signature
if (deviceSecret != null) {
final bodyHash = sha256.convert(utf8.encode(rawBody)).toString();
final signaturePayload = '$method\n$path\n$timestamp\n$nonce\n$bodyHash';
final hmacSha256 = Hmac(sha256, utf8.encode(deviceSecret));
final signature = hmacSha256.convert(utf8.encode(signaturePayload)).toString();
headers['X-Signature'] = signature;
}
}
http.Response response;
switch (method.toUpperCase()) {
case 'GET':
response = await _httpClient.get(uri, headers: headers);
break;
case 'POST':
response = await _httpClient.post(uri, headers: headers, body: rawBody.isNotEmpty ? rawBody : null);
break;
case 'PUT':
response = await _httpClient.put(uri, headers: headers, body: rawBody.isNotEmpty ? rawBody : null);
break;
case 'DELETE':
response = await _httpClient.delete(uri, headers: headers, body: rawBody.isNotEmpty ? rawBody : null);
break;
default:
throw UnsupportedError('HTTP method $method not supported');
}
final decoded = jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300) {
return decoded;
} else {
final msg = decoded['message'] ?? 'Request failed with status ${response.statusCode}';
throw ApiException(msg, response.statusCode);
}
}
Future<Map<String, dynamic>> get(String path, {Map<String, String>? queryParams, bool requiresAuth = true}) {
return sendRequest(method: 'GET', path: path, queryParams: queryParams, requiresAuth: requiresAuth);
}
Future<Map<String, dynamic>> post(String path, {Map<String, dynamic>? body, bool requiresAuth = true}) {
return sendRequest(method: 'POST', path: path, body: body, requiresAuth: requiresAuth);
}
}
class ApiException implements Exception {
final String message;
final int statusCode;
ApiException(this.message, this.statusCode);
@override
String toString() => 'ApiException [$statusCode]: $message';
}