Server-Authoritative • Offline-First • Production-Ready
A three-tier architecture designed for scalability, security, and offline capability. The backend owns game state, the mobile app provides responsive UI, and PostgreSQL ensures data integrity.
Offline Cache
Provider state management with local SQLite database (baseera.db). Supports offline lesson completion for Pro/Lifetime subscribers with background sync queue.
Server Authority
RESTful API with Sanctum authentication. Calculates all XP, streaks, and levels server-side. Eloquent Observers auto-trigger game mechanics on state changes.
Source of Truth
Normalized schema with separate assets table for high-frequency updates. Daily XP logs preserve streak integrity during multi-day offline periods.
Three interrelated problems that shaped Baseera's technical architecture. Each solution demonstrates production-grade engineering principles.
The subscription tier controls which features work offline. Free users require internet; paid users cache content locally. This architectural decision manages infrastructure costs while providing premium value.
// User.dart - Offline eligibility check
class User {
String subscriptionType;
bool get isPro => subscriptionType.toLowerCase() == 'pro';
bool get isLifetime => subscriptionType.toLowerCase() == 'lifetime';
bool get canUseOffline => isPro || isLifetime;
bool get allowCachePopulate => canUseOffline;
}
XP, daily streaks, and level progression are calculated server-side to prevent tampering. The challenge: reconcile multi-day offline activity when paid users eventually reconnect.
// AssetsObserver.php - Streak calculation on XP change
public function updating(Assets $assets): void
{
if ($assets->isDirty('xp')) {
$this->updateDaysOfHemma($assets);
$assets->last_xp_date = now();
}
}
// Streak logic: strict daily, no grace period
if ($dateDiff == 1) {
$assets->days_of_hemma += 1;
} else if ($dateDiff > 1) {
$assets->days_of_hemma = 1; // Streak broken
}
Pro users can go offline for days. When reconnecting, the app syncs daily XP logs to preserve streak integrity. The backend reconstructs the current streak by counting consecutive days backward.
// UserProvider.dart - Optimistic completion with retry
Future<void> completeLesson(int lessonId, double score) async {
// 1. Update UI immediately (optimistic)
_updateLocalProgress(lessonId, score);
// 2. Add to pending queue
await _cacheDao.addPendingCompletion(lessonId, score);
// 3. Attempt sync (with exponential backoff)
await _syncPendingLessons();
}
// Background sync every 3 minutes
Timer.periodic(Duration(minutes: 3), (_) {
_syncPendingLessons();
});
A four-level curriculum hierarchy optimized for query performance and content delivery. Separate assets table isolates high-frequency writes from authentication queries.
Why separate? Assets update multiple times per day. Storing in the main users table would cause lock contention on authentication queries.
Two-stage authentication: OAuth for identity verification, then Sanctum tokens for API access. Supports Firebase, Google, and Meta OAuth providers.
User signs in through Firebase, Google, or Meta OAuth. The provider returns an OAuth token containing user identity and email.
The Flutter app captures the OAuth token from the provider's callback and stores it temporarily for verification.
App sends OAuth token to backend. Laravel validates it with Kreait Firebase SDK, then issues a Sanctum token tied to the user's database record.
The mobile app includes the Sanctum token in the Authorization header for all subsequent API calls. Token persists until logout or expiration.