681 lines
16 KiB
Markdown
681 lines
16 KiB
Markdown
# MotorSignal Cloud Architecture
|
|
|
|
**Status:** Planning baseline
|
|
**Date:** 2026-09-01
|
|
**Backend direction:** Firebase Authentication + Firebase cloud services, integrated with the existing local-first Flutter application
|
|
|
|
## Purpose
|
|
|
|
Define the cloud architecture required for MotorSignal accounts, ride backup, synchronization, profiles, friends, planned rides, trips, and group rides.
|
|
|
|
This document intentionally favors an **offline-first** architecture. Local data remains the immediate source for recording and ride operation; cloud services provide identity, backup, synchronization, collaboration, and cross-device continuity.
|
|
|
|
---
|
|
|
|
# Architectural Goals
|
|
|
|
1. Recording must work without internet access.
|
|
2. Cloud synchronization must be resilient to intermittent connectivity.
|
|
3. A user's data must be isolated from other users by security rules.
|
|
4. Synchronization must be idempotent and safe to retry.
|
|
5. Reinstalling the app must not require recreating a user's ride history.
|
|
6. Cloud functionality must support both Free and Pro entitlements.
|
|
7. The data model must support future social/group-riding features without a major rewrite.
|
|
8. Sensitive location information must receive stronger privacy treatment than ordinary profile information.
|
|
9. Account deletion must have a defined, testable data-deletion path.
|
|
10. Raw high-volume telemetry should not automatically be treated like ordinary profile documents.
|
|
|
|
---
|
|
|
|
# High-Level Architecture
|
|
|
|
```text
|
|
MotorSignal Flutter App
|
|
│
|
|
┌────────────────────┴────────────────────┐
|
|
│ │
|
|
Local Database Cloud Services
|
|
/ Local Files / Firebase
|
|
│ │
|
|
│ ┌──────────┼──────────┐
|
|
│ │ │ │
|
|
│ Auth Firestore Storage
|
|
│ │ │ │
|
|
└──────────────┬───────────────┴──────────┴──────────┘
|
|
│
|
|
Sync Engine
|
|
│
|
|
Entitlement
|
|
│
|
|
Pro / Free
|
|
```
|
|
|
|
---
|
|
|
|
# Identity
|
|
|
|
Firebase Authentication provides the account identity.
|
|
|
|
The internal account identifier should remain stable even if a user changes:
|
|
|
|
- Display name.
|
|
- Username.
|
|
- Profile photo.
|
|
- Optional public location.
|
|
|
|
Google sign-in should remain compatible with the existing application authentication flow.
|
|
|
|
The Firebase user ID should be treated as an internal identifier, not a public username.
|
|
|
|
---
|
|
|
|
# Proposed Data Model
|
|
|
|
The exact collection structure can be refined during implementation, but the logical model should include:
|
|
|
|
```text
|
|
users/{userId}
|
|
|
|
users/{userId}/motorcycles/{motorcycleId}
|
|
users/{userId}/rides/{rideId}
|
|
users/{userId}/trips/{tripId}
|
|
users/{userId}/events/{eventId}
|
|
users/{userId}/friends/{friendId}
|
|
```
|
|
|
|
Potential future top-level collaborative resources:
|
|
|
|
```text
|
|
groupRides/{groupRideId}
|
|
publicRoutes/{routeId}
|
|
```
|
|
|
|
These should only be introduced when query patterns and security requirements justify them.
|
|
|
|
---
|
|
|
|
# User Document
|
|
|
|
Conceptual fields:
|
|
|
|
```text
|
|
id
|
|
username
|
|
displayName
|
|
profilePhotoUrl
|
|
bio
|
|
location
|
|
createdAt
|
|
updatedAt
|
|
privacySettings
|
|
preferences
|
|
```
|
|
|
|
Do not store sensitive secrets in the user document.
|
|
|
|
Avoid storing a complete ride history or large telemetry payload directly inside the user document.
|
|
|
|
---
|
|
|
|
# Motorcycle Document
|
|
|
|
Conceptual fields:
|
|
|
|
```text
|
|
id
|
|
ownerId
|
|
year
|
|
make
|
|
model
|
|
nickname
|
|
photoUrl
|
|
photoSource
|
|
photoAttribution
|
|
createdAt
|
|
updatedAt
|
|
privacy
|
|
```
|
|
|
|
The motorcycle should have a stable ID independent of its displayed name.
|
|
|
|
---
|
|
|
|
# Ride Document
|
|
|
|
Conceptual metadata:
|
|
|
|
```text
|
|
id
|
|
ownerId
|
|
motorcycleId
|
|
tripId?
|
|
groupRideId?
|
|
startedAt
|
|
endedAt
|
|
duration
|
|
distance
|
|
summaryStatistics
|
|
routeReference?
|
|
telemetryReference?
|
|
createdAt
|
|
updatedAt
|
|
syncState
|
|
visibility
|
|
```
|
|
|
|
High-volume telemetry should not be duplicated unnecessarily across multiple Firestore documents.
|
|
|
|
Where appropriate, use compressed/structured files in Firebase Storage and keep searchable summary metadata in Firestore.
|
|
|
|
---
|
|
|
|
# Trip Document
|
|
|
|
Conceptual fields:
|
|
|
|
```text
|
|
id
|
|
ownerId
|
|
name
|
|
startDate
|
|
endDate
|
|
description
|
|
routeReferences
|
|
waypoints
|
|
participants
|
|
createdAt
|
|
updatedAt
|
|
visibility
|
|
```
|
|
|
|
Trips may contain multiple daily routes and completed rides.
|
|
|
|
---
|
|
|
|
# Event Document
|
|
|
|
For meetups and planned events:
|
|
|
|
```text
|
|
id
|
|
ownerId
|
|
name
|
|
description
|
|
startAt
|
|
location
|
|
attendees
|
|
linkedGroupRideId?
|
|
createdAt
|
|
updatedAt
|
|
visibility
|
|
```
|
|
|
|
An event does not automatically imply live location sharing.
|
|
|
|
---
|
|
|
|
# Friend Relationship
|
|
|
|
Friend relationships should have explicit state.
|
|
|
|
```text
|
|
PENDING
|
|
ACCEPTED
|
|
DECLINED
|
|
BLOCKED
|
|
```
|
|
|
|
Friendship should not automatically grant access to private ride data.
|
|
|
|
Visibility remains controlled by the owner's privacy settings.
|
|
|
|
---
|
|
|
|
# Group Ride
|
|
|
|
A group ride should be a shared activity context while each rider retains ownership of their own ride record.
|
|
|
|
Conceptually:
|
|
|
|
```text
|
|
Group Ride
|
|
├── Organizer
|
|
├── Participants
|
|
├── Route
|
|
├── Meeting Point
|
|
├── Schedule
|
|
├── Live Ride State
|
|
└── Completed Ride References
|
|
```
|
|
|
|
A rider's individual telemetry remains associated with that rider and motorcycle.
|
|
|
|
---
|
|
|
|
# Local-First Sync
|
|
|
|
The app should never require a successful cloud write before considering a ride locally saved.
|
|
|
|
### Write flow
|
|
|
|
```text
|
|
User records ride
|
|
↓
|
|
Save locally
|
|
↓
|
|
Mark PENDING_UPLOAD
|
|
↓
|
|
Continue normal app operation
|
|
↓
|
|
Connectivity available
|
|
↓
|
|
Sync engine uploads
|
|
↓
|
|
Server acknowledgement
|
|
↓
|
|
Mark SYNCED
|
|
```
|
|
|
|
### Offline flow
|
|
|
|
```text
|
|
No internet
|
|
↓
|
|
Record normally
|
|
↓
|
|
Queue cloud operation
|
|
↓
|
|
Internet returns
|
|
↓
|
|
Retry automatically
|
|
```
|
|
|
|
---
|
|
|
|
# Sync States
|
|
|
|
Recommended common state model:
|
|
|
|
```text
|
|
LOCAL_ONLY
|
|
PENDING_UPLOAD
|
|
SYNCED
|
|
PENDING_DELETE
|
|
CONFLICT
|
|
SYNC_FAILED
|
|
```
|
|
|
|
The implementation may combine or rename states, but synchronization must be observable and debuggable.
|
|
|
|
---
|
|
|
|
# Idempotency
|
|
|
|
Cloud writes must be safe to retry.
|
|
|
|
Do not create a new ride document every time an upload request is retried.
|
|
|
|
Use stable application-generated IDs so:
|
|
|
|
```text
|
|
same ride ID + repeated upload = same logical ride
|
|
```
|
|
|
|
not:
|
|
|
|
```text
|
|
same ride + repeated upload = duplicate rides
|
|
```
|
|
|
|
---
|
|
|
|
# Conflict Handling
|
|
|
|
Conflicts should be rare but must be explicitly designed.
|
|
|
|
Potential strategy:
|
|
|
|
- Server metadata determines whether an object changed.
|
|
- Immutable ride telemetry should generally not be merged field-by-field.
|
|
- User-editable metadata can use last-write-wins or explicit conflict resolution where appropriate.
|
|
- Never silently overwrite a user's ride data without a defined policy.
|
|
|
|
For example, if two devices edit a motorcycle nickname, last-write-wins may be acceptable.
|
|
|
|
If two devices produce different telemetry for the same ride ID, preserve enough information to diagnose the conflict rather than silently destroying one copy.
|
|
|
|
---
|
|
|
|
# Cloud Storage
|
|
|
|
Use Firebase Storage for large binary objects where appropriate:
|
|
|
|
- Ride telemetry files.
|
|
- Exported reports if cloud retention is required.
|
|
- Profile photos.
|
|
- Motorcycle photos where user-uploaded.
|
|
- Future ride media.
|
|
|
|
Keep Firestore documents focused on metadata, indexes, relationships, and summaries.
|
|
|
|
---
|
|
|
|
# Security Model
|
|
|
|
Every user-owned resource must enforce ownership server-side.
|
|
|
|
Conceptually:
|
|
|
|
```text
|
|
request.auth.uid == resource.ownerId
|
|
```
|
|
|
|
or an equivalent rule based on the final data model.
|
|
|
|
Never rely solely on Flutter UI restrictions to protect data.
|
|
|
|
A hidden button is not a security boundary.
|
|
|
|
---
|
|
|
|
# Social Security
|
|
|
|
Friendship and group participation must not automatically permit access to all account data.
|
|
|
|
Examples:
|
|
|
|
- Being a friend does not expose private ride history.
|
|
- Joining a group ride does not expose a user's historical rides.
|
|
- Live location is only available during explicitly authorized group activity.
|
|
- Public profile data is distinct from private account data.
|
|
|
|
---
|
|
|
|
# Location Privacy
|
|
|
|
Ride telemetry and GPS tracks can reveal a user's home, workplace, and routine.
|
|
|
|
The platform should support:
|
|
|
|
- Private rides.
|
|
- Friends-only rides.
|
|
- Public rides.
|
|
- Optional location sharing.
|
|
- Home/start/end location obfuscation for shared rides.
|
|
- No continuous background social tracking merely because a user has an account.
|
|
|
|
Live group location must require explicit participation/consent and should automatically stop at the end of the group ride.
|
|
|
|
---
|
|
|
|
# Cloud Entitlements
|
|
|
|
Cloud usage must be controlled through the centralized entitlement system.
|
|
|
|
Example capabilities:
|
|
|
|
```text
|
|
canCloudBackup
|
|
canUnlimitedCloudHistory
|
|
canMultiDeviceRestore
|
|
```
|
|
|
|
The client may use these capabilities for UX, but backend rules must also enforce limits and ownership where practical.
|
|
|
|
Do not trust a client-provided `isPro=true` field as proof of payment.
|
|
|
|
---
|
|
|
|
# Subscription / Account Separation
|
|
|
|
A user account should continue to exist even if a Pro subscription expires.
|
|
|
|
Conceptually:
|
|
|
|
```text
|
|
Firebase Account
|
|
│
|
|
├── Profile
|
|
├── Garage
|
|
├── Rides
|
|
└── Entitlement
|
|
│
|
|
┌──────┴──────┐
|
|
│ │
|
|
Free Pro Monthly/Lifetime
|
|
```
|
|
|
|
Billing provider state determines the paid entitlement. The account owns the data.
|
|
|
|
---
|
|
|
|
# Expired Pro Data Policy
|
|
|
|
The final product policy must be defined before launch.
|
|
|
|
Recommended direction:
|
|
|
|
- Do not silently delete historical rides when Pro expires.
|
|
- Preserve account and existing data.
|
|
- Restrict creation/synchronization of additional premium-only data if the user exceeds Free limits.
|
|
- Allow upgrade to restore full Pro functionality.
|
|
|
|
The exact storage allowance and retention policy must be finalized after Firebase cost modeling and legal review.
|
|
|
|
---
|
|
|
|
# Backup / Restore
|
|
|
|
Backup should be automatic where the user's entitlement allows it.
|
|
|
|
Restore should be explicit and understandable.
|
|
|
|
New device flow:
|
|
|
|
```text
|
|
Install MotorSignal
|
|
↓
|
|
Sign in
|
|
↓
|
|
Authenticate
|
|
↓
|
|
Discover cloud account
|
|
↓
|
|
Download motorcycle/profile metadata
|
|
↓
|
|
Download ride metadata
|
|
↓
|
|
Download required ride data
|
|
↓
|
|
Rebuild local database
|
|
↓
|
|
Ready
|
|
```
|
|
|
|
The restore process should be resumable if interrupted.
|
|
|
|
---
|
|
|
|
# Account Deletion
|
|
|
|
Account deletion must be a real backend workflow.
|
|
|
|
It should address:
|
|
|
|
- Firebase Authentication identity.
|
|
- User profile.
|
|
- Motorcycles.
|
|
- Rides.
|
|
- Telemetry files.
|
|
- Profile photos.
|
|
- Social relationships.
|
|
- Trips/events owned by the user.
|
|
- Group ride participation data where legally/technically appropriate.
|
|
|
|
Do not simply delete the authentication record while leaving user data orphaned indefinitely.
|
|
|
|
---
|
|
|
|
# Data Export
|
|
|
|
Provide an account-level export capability where required/appropriate.
|
|
|
|
Potential export contents:
|
|
|
|
- Profile information.
|
|
- Motorcycle data.
|
|
- Ride metadata.
|
|
- Ride telemetry.
|
|
- Routes.
|
|
- Trip data.
|
|
|
|
The existing CSV/PDF ride exports are not necessarily sufficient to satisfy an account-level data export requirement.
|
|
|
|
---
|
|
|
|
# Observability
|
|
|
|
Cloud synchronization should provide enough logging to diagnose:
|
|
|
|
- Authentication failures.
|
|
- Upload failures.
|
|
- Download failures.
|
|
- Permission-denied errors.
|
|
- Duplicate prevention failures.
|
|
- Conflict events.
|
|
- Storage failures.
|
|
- Billing/entitlement synchronization problems.
|
|
|
|
Do not log raw sensitive telemetry or credentials unnecessarily.
|
|
|
|
---
|
|
|
|
# Testing Matrix
|
|
|
|
### Authentication
|
|
|
|
- [ ] Fresh account.
|
|
- [ ] Existing account.
|
|
- [ ] Sign out.
|
|
- [ ] Sign in again.
|
|
- [ ] Multiple devices.
|
|
- [ ] Account deletion.
|
|
|
|
### Offline
|
|
|
|
- [ ] Record without internet.
|
|
- [ ] Save ride locally.
|
|
- [ ] Restore connectivity.
|
|
- [ ] Automatic upload.
|
|
- [ ] Retry after failure.
|
|
|
|
### Sync
|
|
|
|
- [ ] New motorcycle sync.
|
|
- [ ] Ride sync.
|
|
- [ ] Ride update.
|
|
- [ ] Ride deletion.
|
|
- [ ] Duplicate upload prevention.
|
|
- [ ] Conflict behavior.
|
|
|
|
### Restore
|
|
|
|
- [ ] Reinstall.
|
|
- [ ] New device.
|
|
- [ ] Large ride history.
|
|
- [ ] Interrupted restore.
|
|
- [ ] Partial network failure.
|
|
|
|
### Security
|
|
|
|
- [ ] User A cannot read User B's private rides.
|
|
- [ ] User A cannot write User B's data.
|
|
- [ ] Friend access respects privacy.
|
|
- [ ] Group membership does not grant historical access.
|
|
- [ ] Client cannot self-grant Pro entitlement.
|
|
|
|
---
|
|
|
|
# Implementation Order
|
|
|
|
1. Audit existing Firebase/auth implementation.
|
|
2. Document current local Room/database models.
|
|
3. Define cloud DTOs and stable IDs.
|
|
4. Define Firestore/Storage collections.
|
|
5. Write security rules before exposing new data.
|
|
6. Implement profile document.
|
|
7. Implement motorcycle synchronization.
|
|
8. Implement ride metadata synchronization.
|
|
9. Implement telemetry/blob synchronization.
|
|
10. Implement retry queue.
|
|
11. Implement conflict/error handling.
|
|
12. Implement backup status UI.
|
|
13. Implement restore.
|
|
14. Add account deletion/data export.
|
|
15. Connect cloud capability checks to entitlement manager.
|
|
16. Test Free/Pro storage behavior.
|
|
17. Add friends/social resources.
|
|
18. Add planned rides/trips.
|
|
19. Add group rides and explicit live-location sessions.
|
|
|
|
---
|
|
|
|
# Design Rule
|
|
|
|
The cloud system must make MotorSignal more reliable, not more fragile.
|
|
|
|
The critical riding path should remain:
|
|
|
|
```text
|
|
Sensors → Local Recording → Local Database → Ride Processing
|
|
```
|
|
|
|
Cloud synchronization should be downstream:
|
|
|
|
```text
|
|
Local Ride → Sync Queue → Cloud Backup
|
|
```
|
|
|
|
A cloud outage must not prevent a rider from recording a ride.
|
|
|
|
---
|
|
|
|
# Server-Verified Google Play Entitlements (Stage 4B-3)
|
|
|
|
## Verification Pipeline
|
|
|
|
```text
|
|
Google Play Billing Client
|
|
↓
|
|
MotorSignal BillingService (PlayBillingService)
|
|
↓
|
|
Unverified StorePurchase (productId, purchaseToken)
|
|
↓
|
|
DurableVerificationQueue (FlutterSecureStorage)
|
|
↓
|
|
PurchaseVerificationService
|
|
↓
|
|
Cloudflare Worker (POST /billing/verify-play-purchase)
|
|
↓ (Authenticated via Service Account JWT assertion)
|
|
Google Play Developer API v3 (Android Publisher)
|
|
- Subscriptions: purchases.subscriptionsv2.get
|
|
- Lifetime: purchases.products.get
|
|
↓
|
|
Verified Entitlement Payload (Plan, Status, ExpiresAt, TokenHash)
|
|
↓
|
|
EntitlementCoordinator
|
|
↓
|
|
Secure Local Entitlement Cache (FlutterSecureStorage)
|
|
↓
|
|
PlayBillingService.completePurchase() (Google Play Acknowledgement)
|
|
```
|
|
|
|
## Security & Architecture Contracts
|
|
|
|
1. **Server Authority**: The mobile client never self-asserts its entitlement tier. Entitlement is determined authoritatively by Google Play Developer API responses.
|
|
2. **Account Independence**: MotorSignal login is strictly optional. Riders without a MotorSignal account can purchase, verify, restore, and own Pro entitlements. Optional Firebase ID tokens sent in verification requests associate convenience metadata without overriding Google Play ownership.
|
|
3. **Secret Isolation**: Google Service Account credentials (`GOOGLE_SERVICE_ACCOUNT_EMAIL`, `GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY`) reside exclusively as Cloudflare Worker secrets. They are never compiled into the APK, never committed, and never exposed in logs.
|
|
4. **Token Handling**: Raw Google Play purchase tokens are never logged or stored in general application databases (Drift, SharedPreferences). Bounded retry queuing utilizes encrypted `FlutterSecureStorage` and purges tokens upon terminal verification.
|
|
5. **Rider Safety Invariant**: Active ride recording and telemetry persistence are strictly isolated from billing verification operations and network outages.
|
|
|