feat(contribute): upload event images to OneDrive before submission
- ApiClient.uploadFile() — multipart POST to /v1/upload/file (60s timeout)
- ApiEndpoints.uploadFile — points to Node.js upload endpoint
- GamificationService.submitContribution() now uploads each picked image
to OneDrive via the server upload pipeline, then passes the returned
{ fileId, url, ... } objects as `media` in the submission body
(replaces broken behaviour of sending local device paths as strings)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,42 @@ class ApiClient {
|
||||
return _handleResponse(url, response, finalBody);
|
||||
}
|
||||
|
||||
/// Upload a single file as multipart/form-data.
|
||||
///
|
||||
/// Returns the `file` object from the server response:
|
||||
/// `{ fileId, url, name, type, mimeType, size, backend }`
|
||||
Future<Map<String, dynamic>> uploadFile(String url, String filePath) async {
|
||||
final request = http.MultipartRequest('POST', Uri.parse(url));
|
||||
request.files.add(await http.MultipartFile.fromPath('file', filePath));
|
||||
|
||||
late http.StreamedResponse streamed;
|
||||
try {
|
||||
streamed = await request.send().timeout(const Duration(seconds: 60));
|
||||
} catch (e) {
|
||||
throw Exception('Upload network error: $e');
|
||||
}
|
||||
|
||||
final body = await streamed.stream.bytesToString();
|
||||
dynamic decoded;
|
||||
try {
|
||||
decoded = jsonDecode(body);
|
||||
} catch (_) {
|
||||
throw Exception('Upload response parse error');
|
||||
}
|
||||
|
||||
if (streamed.statusCode >= 200 && streamed.statusCode < 300) {
|
||||
if (decoded is Map<String, dynamic> && decoded['file'] is Map) {
|
||||
return Map<String, dynamic>.from(decoded['file'] as Map);
|
||||
}
|
||||
return decoded is Map<String, dynamic> ? decoded : {};
|
||||
}
|
||||
|
||||
final msg = (decoded is Map && decoded['message'] is String)
|
||||
? decoded['message'] as String
|
||||
: 'Upload failed (${streamed.statusCode})';
|
||||
throw Exception(msg);
|
||||
}
|
||||
|
||||
/// GET request
|
||||
///
|
||||
/// - If requiresAuth==true, token & username will be attached as query parameters.
|
||||
|
||||
@@ -14,6 +14,7 @@ class ApiEndpoints {
|
||||
static const String login = "$baseUrl/user/login/";
|
||||
static const String logout = "$baseUrl/user/logout/";
|
||||
static const String status = "$baseUrl/user/status/";
|
||||
static const String updateProfile = "$baseUrl/user/update-profile/";
|
||||
|
||||
// Events
|
||||
static const String eventTypes = "$baseUrl/events/type-list/"; // list of event types
|
||||
@@ -22,6 +23,8 @@ class ApiEndpoints {
|
||||
static const String eventImages = "$baseUrl/events/event-images/"; // event-images
|
||||
static const String eventsByCategory = "$baseUrl/events/events-by-category/";
|
||||
static const String eventsByMonth = "$baseUrl/events/events-by-month-year/";
|
||||
static const String featuredEvents = "$baseUrl/events/featured-events/";
|
||||
static const String topEvents = "$baseUrl/events/top-events/";
|
||||
|
||||
// Bookings
|
||||
// static const String bookEvent = "$baseUrl/events/book-event/";
|
||||
@@ -38,6 +41,9 @@ class ApiEndpoints {
|
||||
// Node.js gamification server (same host as reviews)
|
||||
static const String _nodeBase = "https://app.eventifyplus.com/api";
|
||||
|
||||
// File upload (Node.js — routes to OneDrive or GDrive via STORAGE_BACKEND env)
|
||||
static const String uploadFile = "$_nodeBase/v1/upload/file";
|
||||
|
||||
// Gamification / Contributor Module
|
||||
static const String gamificationDashboard = "$_nodeBase/v1/gamification/dashboard";
|
||||
static const String leaderboard = "$_nodeBase/v1/gamification/leaderboard";
|
||||
|
||||
Reference in New Issue
Block a user