Initial commit: Flutter app + PHP/MySQL backend on Hostinger
Replaces Firebase with a self-hosted PHP/MySQL API served from winded.prymsolutions.com. Includes full backend (schema, auth, events, teams, brackets, suggestions, stats, media, file upload) and updated Flutter repositories and domain models. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'api_client.g.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Configuration — set this to your Hostinger domain before building.
|
||||
// ---------------------------------------------------------------------------
|
||||
const String kApiBase = 'https://winded.prymsolutions.com/api';
|
||||
|
||||
const String _tokenKey = 'winded_auth_token';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ApiClient
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class ApiClient {
|
||||
ApiClient(this._storage);
|
||||
|
||||
final FlutterSecureStorage _storage;
|
||||
|
||||
// --- Token management ---
|
||||
|
||||
Future<String?> get token => _storage.read(key: _tokenKey);
|
||||
|
||||
Future<void> saveToken(String token) =>
|
||||
_storage.write(key: _tokenKey, value: token);
|
||||
|
||||
Future<void> clearToken() => _storage.delete(key: _tokenKey);
|
||||
|
||||
// --- HTTP helpers ---
|
||||
|
||||
Future<Map<String, String>> _headers({bool auth = true}) async {
|
||||
final headers = <String, String>{'Content-Type': 'application/json'};
|
||||
if (auth) {
|
||||
final t = await token;
|
||||
if (t != null) headers['Authorization'] = 'Bearer $t';
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
Uri _uri(String path, [Map<String, String>? params]) {
|
||||
final uri = Uri.parse('$kApiBase$path');
|
||||
return params != null ? uri.replace(queryParameters: params) : uri;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> get(
|
||||
String path, {
|
||||
Map<String, String>? params,
|
||||
bool auth = true,
|
||||
}) async {
|
||||
final res = await http.get(_uri(path, params), headers: await _headers(auth: auth));
|
||||
return _parse(res);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> post(
|
||||
String path,
|
||||
Map<String, dynamic> body, {
|
||||
bool auth = true,
|
||||
}) async {
|
||||
final res = await http.post(
|
||||
_uri(path),
|
||||
headers: await _headers(auth: auth),
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
return _parse(res);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> put(
|
||||
String path,
|
||||
Map<String, dynamic> body, {
|
||||
Map<String, String>? params,
|
||||
}) async {
|
||||
final res = await http.put(
|
||||
_uri(path, params),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
return _parse(res);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> delete(
|
||||
String path, {
|
||||
Map<String, String>? params,
|
||||
}) async {
|
||||
final res = await http.delete(_uri(path, params), headers: await _headers());
|
||||
return _parse(res);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _parse(http.Response res) {
|
||||
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
||||
if (res.statusCode >= 400) {
|
||||
throw ApiException(
|
||||
message: (body['error'] as String?) ?? 'Unknown error',
|
||||
statusCode: res.statusCode,
|
||||
);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
class ApiException implements Exception {
|
||||
const ApiException({required this.message, required this.statusCode});
|
||||
final String message;
|
||||
final int statusCode;
|
||||
|
||||
@override
|
||||
String toString() => 'ApiException($statusCode): $message';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Providers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
ApiClient apiClient(ApiClientRef ref) {
|
||||
return ApiClient(const FlutterSecureStorage());
|
||||
}
|
||||
Reference in New Issue
Block a user