Files
Eventify-frontend/lib/features/events/models/event_models.dart
Sicherhaven c40e600937 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>
2026-04-08 21:12:49 +05:30

181 lines
5.7 KiB
Dart

// lib/features/events/models/event_models.dart
import '../../../core/api/api_endpoints.dart';
class EventTypeModel {
final int id;
final String name;
final String? iconUrl;
EventTypeModel({required this.id, required this.name, this.iconUrl});
/// Resolve a relative media path (e.g. `/media/...`) to a full URL.
static String? _resolveMediaUrl(String? raw) {
if (raw == null || raw.isEmpty) return null;
if (raw.startsWith('http://') || raw.startsWith('https://')) return raw;
return '${ApiEndpoints.mediaBaseUrl}$raw';
}
factory EventTypeModel.fromJson(Map<String, dynamic> j) {
return EventTypeModel(
id: j['id'] as int,
name: (j['event_type'] ?? j['name'] ?? '') as String,
iconUrl: _resolveMediaUrl((j['event_type_icon'] ?? j['icon_url']) as String?),
);
}
}
class EventImageModel {
final bool isPrimary;
final String image;
EventImageModel({required this.isPrimary, required this.image});
factory EventImageModel.fromJson(Map<String, dynamic> j) {
return EventImageModel(
isPrimary: j['is_primary'] == true,
image: EventTypeModel._resolveMediaUrl(j['image'] as String?) ?? '',
);
}
}
class EventModel {
final int id;
final String name;
final String? title;
final String? description;
final String startDate; // YYYY-MM-DD
final String endDate;
final String? startTime;
final String? endTime;
final String? pincode;
final String? place;
final bool isBookable;
final int? eventTypeId;
final String? thumbImg;
final List<EventImageModel> images;
// NEW fields mapped from backend
final String? importantInformation;
final String? venueName;
final String? eventStatus;
final String? cancelledReason;
// Geo / location fields
final double? latitude;
final double? longitude;
final String? locationName;
// Structured important info list [{title, value}, ...]
final List<Map<String, String>> importantInfo;
// Review stats (populated when backend includes them)
final double? averageRating;
final int? reviewCount;
// Contributor fields (EVT-001)
final String? contributorId;
final String? contributorName;
final String? contributorTier;
// Curation flags
final bool isFeatured;
final bool isTopEvent;
EventModel({
required this.id,
required this.name,
this.title,
this.description,
required this.startDate,
required this.endDate,
this.startTime,
this.endTime,
this.pincode,
this.place,
this.isBookable = true,
this.eventTypeId,
this.thumbImg,
this.images = const [],
this.importantInformation,
this.venueName,
this.eventStatus,
this.cancelledReason,
this.latitude,
this.longitude,
this.locationName,
this.importantInfo = const [],
this.averageRating,
this.reviewCount,
this.contributorId,
this.contributorName,
this.contributorTier,
this.isFeatured = false,
this.isTopEvent = false,
});
/// Safely parse a double from backend (may arrive as String or num)
static double? _parseDouble(dynamic raw) {
if (raw == null) return null;
if (raw is num) return raw.toDouble();
if (raw is String) return double.tryParse(raw);
return null;
}
/// Safely parse important_info from backend (list of {title, value} maps)
static List<Map<String, String>> _parseImportantInfo(dynamic raw) {
if (raw is List) {
return raw.map<Map<String, String>>((e) {
if (e is Map) {
return {
'title': (e['title'] ?? '').toString(),
'value': (e['value'] ?? '').toString(),
};
}
return {'title': '', 'value': e.toString()};
}).toList();
}
return [];
}
factory EventModel.fromJson(Map<String, dynamic> j) {
final imgs = <EventImageModel>[];
if (j['images'] is List) {
for (final im in j['images']) {
if (im is Map<String, dynamic>) imgs.add(EventImageModel.fromJson(im));
}
}
return EventModel(
id: j['id'] is int ? j['id'] as int : int.parse(j['id'].toString()),
name: (j['name'] ?? '') as String,
title: j['title'] as String?,
description: j['description'] as String?,
startDate: (j['start_date'] ?? '') as String,
endDate: (j['end_date'] ?? '') as String,
startTime: j['start_time'] as String?,
endTime: j['end_time'] as String?,
pincode: j['pincode'] as String?,
place: (j['place'] ?? j['venue_name']) as String?,
isBookable: j['is_bookable'] == null ? true : (j['is_bookable'] == true || j['is_bookable'].toString().toLowerCase() == 'true'),
eventTypeId: j['event_type'] is int ? j['event_type'] as int : (j['event_type'] != null ? int.tryParse(j['event_type'].toString()) : null),
thumbImg: EventTypeModel._resolveMediaUrl(j['thumb_img'] as String?),
images: imgs,
importantInformation: j['important_information'] as String?,
venueName: j['venue_name'] as String?,
eventStatus: j['event_status'] as String?,
cancelledReason: j['cancelled_reason'] as String?,
latitude: _parseDouble(j['latitude']),
longitude: _parseDouble(j['longitude']),
locationName: j['location_name'] as String?,
importantInfo: _parseImportantInfo(j['important_info']),
averageRating: (j['average_rating'] as num?)?.toDouble(),
reviewCount: (j['review_count'] as num?)?.toInt(),
contributorId: j['contributor_id']?.toString(),
contributorName: j['contributor_name'] as String?,
contributorTier: j['contributor_tier'] as String?,
isFeatured: j['is_featured'] == true || j['is_featured']?.toString().toLowerCase() == 'true',
isTopEvent: j['is_top_event'] == true || j['is_top_event']?.toString().toLowerCase() == 'true',
);
}
}