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:
2026-05-14 20:13:57 -07:00
commit b239ae3e5f
208 changed files with 19187 additions and 0 deletions
@@ -0,0 +1,108 @@
enum SuggestionStatus { pending, reviewed, implemented }
class Suggestion {
const Suggestion({
required this.id,
required this.text,
required this.isAnonymous,
required this.submittedAt,
required this.status,
this.userId,
this.displayName,
});
final String id;
final String text;
final bool isAnonymous;
final String? userId;
final String? displayName;
final DateTime submittedAt;
final SuggestionStatus status;
Suggestion copyWith({
String? id,
String? text,
bool? isAnonymous,
String? userId,
String? displayName,
DateTime? submittedAt,
SuggestionStatus? status,
}) {
return Suggestion(
id: id ?? this.id,
text: text ?? this.text,
isAnonymous: isAnonymous ?? this.isAnonymous,
userId: userId ?? this.userId,
displayName: displayName ?? this.displayName,
submittedAt: submittedAt ?? this.submittedAt,
status: status ?? this.status,
);
}
factory Suggestion.fromJson(Map<String, dynamic> data) {
return Suggestion(
id: (data['id'] as String?) ?? '',
text: (data['text'] as String?) ?? '',
isAnonymous: _parseBool(data['is_anonymous']),
userId: data['user_id'] as String?,
displayName: data['display_name'] as String?,
submittedAt: _parseDate(data['submitted_at']) ?? DateTime.now(),
status: _parseStatus(data['status'] as String?),
);
}
Map<String, Object?> toJson() {
return <String, Object?>{
'text': text,
'is_anonymous': isAnonymous,
'user_id': isAnonymous ? null : userId,
'display_name': isAnonymous ? null : displayName,
'status': status.name,
};
}
static DateTime? _parseDate(Object? v) {
if (v is String && v.isNotEmpty) return DateTime.tryParse(v);
return null;
}
static bool _parseBool(Object? v) {
if (v is bool) return v;
if (v is int) return v != 0;
if (v is String) return v == '1' || v.toLowerCase() == 'true';
return false;
}
static SuggestionStatus _parseStatus(String? raw) {
switch (raw) {
case 'reviewed':
return SuggestionStatus.reviewed;
case 'implemented':
return SuggestionStatus.implemented;
default:
return SuggestionStatus.pending;
}
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Suggestion &&
other.id == id &&
other.text == text &&
other.isAnonymous == isAnonymous &&
other.userId == userId &&
other.displayName == displayName &&
other.submittedAt == submittedAt &&
other.status == status;
}
@override
int get hashCode => Object.hash(
id, text, isAnonymous, userId, displayName, submittedAt, status,
);
@override
String toString() =>
'Suggestion(id: $id, status: ${status.name}, anonymous: $isAnonymous)';
}