docs: add MotorSignal cloud architecture
This commit is contained in:
parent
8288229d78
commit
6bc15fb179
|
|
@ -0,0 +1,639 @@
|
||||||
|
# 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.
|
||||||
Loading…
Reference in New Issue