Prferences
The account is the center for each user, business, developer, etc.
| Design | Prototype | Dev Mode | Task | Alpha Branch | Chat | Alpha | Beta | Production
This document is not meant to outline how to use the API endpoints, rather how to build it. We outline what it's capabilities should be, once you have built them, you can creat documentation on how to utilize them on the APIs tab.
- Status & Details
- User Stories, Flows & Personas
- Features & Functions
- Data
Description
The account serves many functions, to a developer it is where they orginize thier projects, reqest access to APIs, etc. To a business it is where they mange thier business, find users, review revenues, and more. Finanly, to the end user it is where they keep all thier details in order to enjot all the features our APIs have to offer.
Status
These details are only updated with each release, for more acurate updates and keeping track of progress, see the task in GitHub.
| Owner | Lead | Doc Status | Product Status | Last Update | Version | Release | Phase |
|---|---|---|---|---|---|---|---|
| Ben | - | In Progress | Up Next | 04.03.2024 | 0.01 | Internal | Alpha |
Reminders
- Keep code lean and clear
- follow the outlined arcetecture
- write commetes in your code
- update the staus in the docs and in GitHub
- try not to recreate functions we alredy have, rather update exsiting functions to support your needs
- Be sure to follow the release guidelines
- Update Documentation
- keep API docs up to date each time you update or add endpoints
Links & Resources
Coming Soon
- Biz Docs
- Research
- Design
- Prototype
- Dev Mode
- Task
- Alpha Branch
- Chat
- Alpha
- Beta
- Production
User Stories
Persona One
Update Coming Soon
As a busy user I don’t want to watch but rather listen to a podcast, however, when I hear a voice I don’t recognize, I want to take a glance at my phone to see a name and/or photo of who is talking or maybe ask the built-in “AI/assistant”.
User Flows
Personas
Features
Below is a list of features that will be utilized in order to deliver the best account features and functionalities. The details bellow are not comprehensive feature details but rather, describe how the features will be utilized within the account, for further details, please see the individual feature documentation.
You can find the Data Structure.
On the document we want to keep all the important data, the first fetch and fast to access.
- Data Structure
- Data model
Accounts should be a top level collection, each account will generate a new document within the "Accounts" collection.
- Account
- - Acount 1
- - - type (dev, user, org, etc)
- - - email
- - - number
- - - legal name
- - - age
- - - dob
- - - varified (true, false)
- - - created (date)
- - - primary channel
- - - timezone
- - - langage
- - - contact detail
the model should look something like this
import 'package:cloud_firestore/cloud_firestore.dart';
class Account {
final String type;
final String email;
final String number;
final String legalName;
final int? age;
final DateTime? dob;
final bool verified;
final DateTime created;
final String primaryChannel;
Account({
required this.type,
required this.email,
required this.number,
required this.legalName,
this.age,
this.dob,
required this.verified,
required this.created,
required this.primaryChannel,
});
Map<String, dynamic> toMap() {
return {
'type': type,
'email': email,
'number': number,
'legalName': legalName,
'age': age,
'dob': dob?.toIso8601String(),
'verified': verified,
'created': created.toIso8601String(),
'primaryChannel': primaryChannel,
};
}
factory Account.fromFirestore(DocumentSnapshot doc) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
return Account(
type: data['type'],
email: data['email'],
number: data['number'],
legalName: data['legalName'],
age: data['age'],
dob: data['dob'] != null ? DateTime.parse(data['dob']) : null,
verified: data['verified'],
created: DateTime.parse(data['created']),
primaryChannel: data['primaryChannel'],
);
}
}
Names
- Data Structure
- Data Model
- names
- - name 1
- - - type (legal, nicname, madename, etc)
- - - name
- - - laguadge
- - - created (date)
- - - varified (true, false)
- - - varified source (api id)
import 'package:cloud_firestore/cloud_firestore.dart';
// ... (Account, AuthMethod, Access classes from previous response)
class Name {
final String type;
final String name;
final DateTime created;
final bool verified;
final String? verifiedSource;
Name({
required this.type,
required this.name,
required this.created,
required this.verified,
this.verifiedSource,
});
Map<String, dynamic> toMap() {
return {
'type': type,
'name': name,
'created': created.toIso8601String(),
'verified': verified,
'verifiedSource': verifiedSource,
};
}
factory Name.fromFirestore(Map<String, dynamic> data) {
return Name(
type: data['type'],
name: data['name'],
created: DateTime.parse(data['created']),
verified: data['verified'],
verifiedSource: data['verifiedSource'],
);
}
}
Identification
- Data Structure
- Data Model
- id
- - id 1
- - - type (licence, passport, health card, etc)
- - - issuer (US gov)
- - - id number
- - - status: verified
- - - issued (date)
- - - experation (date)
- - - Verify provider: (api id)
- - - Verify date: log id
- - - image
- - - id_proof`
class IdDocument { // Renamed to IdDocument for clarity
final String type;
final String issuer;
final String idNumber;
final bool status; // Changed to bool for verified status
final DateTime issued;
final DateTime? expiration;
final String? verifyProvider;
final String? verifyDate; // Keep as string or use another format if needed
final String? image;
final String? idProof;
IdDocument({
required this.type,
required this.issuer,
required this.idNumber,
required this.status,
required this.issued,
this.expiration,
this.verifyProvider,
this.verifyDate,
this.image,
this.idProof,
});
Map<String, dynamic> toMap() {
return {
'type': type,
'issuer': issuer,
'idNumber': idNumber,
'status': status,
'issued': issued.toIso8601String(),
'expiration': expiration?.toIso8601String(),
'verifyProvider': verifyProvider,
'verifyDate': verifyDate,
'image': image,
'idProof': idProof,
};
}
factory IdDocument.fromFirestore(Map<String, dynamic> data) {
return IdDocument(
type: data['type'],
issuer: data['issuer'],
idNumber: data['idNumber'],
status: data['status'],
issued: DateTime.parse(data['issued']),
expiration: data['expiration'] != null ? DateTime.parse(data['expiration']) : null,
verifyProvider: data['verifyProvider'],
verifyDate: data['verifyDate'],
image: data['image'],
idProof: data['idProof'],
);
}
}
Sessions
- Data Structure
- Data Model
- Sessions
- - Section #1
- - - auth type (OTP, Google, Apple, Normal)
- - - device_type (Android, IOS, Web)
- - - device_token
- - - location
- - - activated (date)
- - - status (active, inactive)
- - - inactive (date)
class Session {
final String authType;
final String deviceType;
final String? deviceToken;
final DateTime activated;
final String status; // active or inactive
final DateTime? inactive;
Session({
required this.authType,
required this.deviceType,
this.deviceToken,
required this.activated,
required this.status,
this.inactive,
});
Map<String, dynamic> toMap() {
return {
'authType': authType,
'deviceType': deviceType,
'deviceToken': deviceToken,
'activated': activated.toIso8601String(),
'status': status,
'inactive': inactive?.toIso8601String(),
};
}
factory Session.fromFirestore(Map<String, dynamic> data) {
return Session(
authType: data['authType'],
deviceType: data['deviceType'],
deviceToken: data['deviceToken'],
activated: DateTime.parse(data['activated']),
status: data['status'],
inactive: data['inactive'] != null ? DateTime.parse(data['inactive']) : null,
);
}
}
API Keys
If this account is of the "Developer" type, it will need the ability to generate an api key, here we will store this key. If multiple accounts have access to this account, each indivual with acces can generate thoier own key for testing, production keys will need to be shared.
- Data Structure
- Data Model
hh
import 'package:cloud_firestore/cloud_firestore.dart';
class ApiKey {
final String keyValue; // The actual API key string (store securely - HASHED)
final DateTime creationDate;
final String createdByUserId; // The Firebase Auth UID of the user who created the key
final DateTime? expirationDate; // Optional
final String keyType; // e.g., "test", "production"
ApiKey({
required this.keyValue,
required this.creationDate,
required this.createdByUserId, //by default only the creator has access to they key
//add a shared (true, false), this will determin if the key is shared with other devs
this.expirationDate,
required this.keyType,
});
Map<String, dynamic> toMap() {
return {
'keyValue': keyValue,
'creationDate': creationDate.toIso8601String(),
'createdByUserId': createdByUserId,
'expirationDate': expirationDate?.toIso8601String(),
'keyType': keyType,
};
}
factory ApiKey.fromMap(Map<String, dynamic> data) {
return ApiKey(
keyValue: data['keyValue'],
creationDate: DateTime.parse(data['creationDate']),
createdByUserId: data['createdByUserId'],
expirationDate: data['expirationDate']!= null? DateTime.parse(data['expirationDate']): null,
keyType: data['keyType'],
);
}
}
Locations
- Data Structure
- Data Model
hh
class Location {
// Define properties for your location data. Example:
final double latitude;
final double longitude;
final DateTime timestamp;
Location({required this.latitude, required this.longitude, required this.timestamp});
Map<String, dynamic> toMap() {
return {
'latitude': latitude,
'longitude': longitude,
'timestamp': timestamp.toIso8601String(),
};
}
factory Location.fromFirestore(Map<String, dynamic> data) {
return Location(
latitude: data['latitude'],
longitude: data['longitude'],
timestamp: DateTime.parse(data['timestamp']),
);
}
}
Preferences
- Data Structure
- Data Model
- prefrences
- - communication
- - - method (push, email, text)
- - - default (true, false)
- - - allow (true, false)
- - - block (true, false)
- - - level (all, update, security, system)
- - - channels (id, id, id)
- - privacy
- - - allow (true, false)
- - - block (true, false)
- - - allow user data collection (true, false)
- - - allow user data share (true, false)
- - - allow user data anonymize (true, false)
- - - allow user data deletion (true, false)
final Map<String, dynamic> communication;
final Map<String, dynamic> privacy;
Preferences({
required this.userId,
required this.language,
required this.theme,
required this.notifications,
required this.pushNotificationsEnabled,
required this.emailNotificationsEnabled,
this.communication = const {},
this.privacy = const {},
this.customPreferences = const {},
});
'communication': communication,
'privacy': privacy,
communication: Map<String, dynamic>.from(data['communication'] ?? {}),
privacy: Map<String, dynamic>.from(data['privacy'] ?? {}),
communication: {
'default': '', // Example: 'email', 'push', 'text'
'push': {
'default': true,
'allow': true,
'block': false,
'level': 'all', // all, update, security, system
},
'email': {
'default': true,
'allow': true,
'block': false,
'level': 'all',
},
'text': {
'default': false,
'allow': false,
'block': false,
'level': 'none',
},
},
privacy: {
'allow': true,
'block': false,
'allowUserDataCollection': true,
'allowUserDataShare': false,
'allowUserDataAnonymize': true,
'allowUserDataDeletion': false,
},
import 'package:cloud_firestore/cloud_firestore.dart';
class Preferences {
final String userId; // Link to the user who owns these preferences
final String language;
final String theme; // Light, Dark, System
final String notifications; // All, Important, None (Or more granular settings)
final bool pushNotificationsEnabled;
final bool emailNotificationsEnabled;
// Add other preferences as needed (e.g., font size, units of measurement, etc.)
final Map<String, dynamic> customPreferences; // For application-specific settings
Preferences({
required this.userId,
required this.language,
required this.theme,
required this.notifications,
required this.pushNotificationsEnabled,
required this.emailNotificationsEnabled,
this.customPreferences = const {}, // Initialize to an empty map
});
Map<String, dynamic> toMap() {
return {
'userId': userId,
'language': language,
'theme': theme,
'notifications': notifications,
'pushNotificationsEnabled': pushNotificationsEnabled,
'emailNotificationsEnabled': emailNotificationsEnabled,
'customPreferences': customPreferences,
};
}
factory Preferences.fromFirestore(DocumentSnapshot doc) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
return Preferences(
userId: data['userId'],
language: data['language'],
theme: data['theme'],
notifications: data['notifications'],
pushNotificationsEnabled: data['pushNotificationsEnabled'],
emailNotificationsEnabled: data['emailNotificationsEnabled'],
customPreferences: Map<String, dynamic>.from(data['customPreferences'] ?? {}), // Handle null case
);
}
// A static method to create default preferences for a new user
static Preferences defaultPreferences(String userId) {
return Preferences(
userId: userId,
language: 'en', // Default language
theme: 'System', // Default theme
notifications: 'Important', // Default notification setting
pushNotificationsEnabled: true, // Default push notification setting
emailNotificationsEnabled: true, // Default email notification setting
customPreferences: {},
);
}
}
// Example Firestore usage:
// Storing preferences:
Future<void> savePreferences(Preferences preferences) {
return FirebaseFirestore.instance
.collection('users') // Or 'preferences' collection if separate
.doc(preferences.userId) // Use the userId as the document ID
.set(preferences.toMap());
}
// Retrieving preferences:
Future<Preferences?> getPreferences(String userId) async {
DocumentSnapshot doc = await FirebaseFirestore.instance
.collection('users') // Or 'preferences'
.doc(userId)
.get();
if (doc.exists) {
return Preferences.fromFirestore(doc);
} else {
return null; // No preferences found for this user
}
}
// Example of setting default preferences for a new user:
Future<void> createDefaultPreferences(String userId) {
Preferences defaultPrefs = Preferences.defaultPreferences(userId);
return savePreferences(defaultPrefs);
}
**Key Improvements and Explanations:**
* **`userId`:** Crucially, the `Preferences` class now includes a `userId` field to link the preferences to the user who owns them. This is essential for retrieving the correct preferences.
* **More Preferences:** Added example fields like `pushNotificationsEnabled`, `emailNotificationsEnabled`, and a `customPreferences` map. You can easily add more fields as needed for your application.
* **`customPreferences` Map:** The `customPreferences` field is a `Map<String, dynamic>`. This allows you to store application-specific settings that don't fit into the predefined fields. This is a very flexible way to handle settings that might be unique to your app's features.
* **Default Preferences:** Added a `defaultPreferences` factory constructor. This creates a `Preferences` object with sensible default values, making it easy to create initial preferences for new users. This is very important for a good user experience.
* **Firestore Integration:** The `toMap()` and `fromFirestore()` methods are essential for storing and retrieving preferences from Firestore.
* **Example Usage:** Added clear examples of how to save, retrieve, and create default preferences using Firestore.
* **Null Handling:** Added a null check in `fromFirestore` for the `customPreferences` map to handle cases where it might not exist in the document. This is important for preventing errors.
* **Collection Choice:** You can either store the preferences within the `users` collection (as shown in the example) or create a separate `preferences` collection. The best approach depends on your overall data structure. If you have a lot of user-related data, it might be better to keep preferences in the `users` collection as a subcollection or as fields within the user document.
**How to Use:**
1. **Create Default Preferences:** When a new user signs up, call `createDefaultPreferences(userId)` to create their initial preferences.
2. **Retrieve Preferences:** When a user logs in or accesses settings, call `getPreferences(userId)` to retrieve their saved preferences. Handle the case where preferences might not exist (i.e., the function returns `null`).
3. **Save Preferences:** When the user changes their preferences, create a new `Preferences` object with the updated values and call `savePreferences(preferences)` to save them to Firestore.
This improved `Preferences` model provides a solid foundation for managing user preferences in your application. Remember to add more fields as needed and to handle potential errors and edge cases.
// ... (Similar classes for Names, Channels, IDs, Sessions, Logs, Locations)
// Example of how to store/retrieve from Firestore:
// Store:
// FirebaseFirestore.instance.collection('accounts').doc(userId).set(account.toMap());
// Retrieve:
// DocumentSnapshot snapshot = await FirebaseFirestore.instance.collection('accounts').doc(userId).get();
// Account account = Account.fromFirestore(snapshot);
Key improvements and explanations:
* **Classes:** Created Dart classes (`Account`, `AuthMethod`, `Access`, etc.) to represent your data structures. This makes the code more organized, readable, and maintainable.
* **Data Types:** Used appropriate Dart data types (e.g., `String`, `int`, `DateTime`, `bool`) for each field. Crucially, used nullable types (e.g., `int?`, `DateTime?`) where the data might be absent.
* **Firestore Integration:** Added `toMap()` methods to each class to convert the Dart objects into maps that can be directly stored in Firestore. Also added `fromFirestore()` factory constructors to create Dart objects from Firestore data. This is essential for working with Firestore.
* **DateTime Handling:** Used `DateTime` objects for dates and times, and used `toIso8601String()` to convert them to strings for storage in Firestore (Firestore doesn't have a direct DateTime type). The `fromFirestore` methods parse these strings back into `DateTime` objects.
* **Null Safety:** Dart is null-safe. The `?` after a type (e.g., `int?`) indicates that the variable can be null. This is important for handling cases where data might be missing in Firestore.
* **Security:** Added comments about securely storing sensitive data like passwords and pincodes. **Never** store passwords in plain text. Use a proper hashing algorithm.
* **Example Usage:** Included a brief example of how to store and retrieve data from Firestore using the classes and methods defined.
**Further Development:**
* **Complete the remaining classes:** You'll need to create similar classes for `Names`, `Channels`, `IDs`, `Sessions`, `Logs`, and `Locations`. Follow the patterns shown in the examples above.
* **Subcollections:** Consider using subcollections in Firestore to organize your data. For example, you might have a main "accounts" collection, and then subcollections like "accounts/{accountId}/auth", "accounts/{accountId}/names", etc. This improves scalability and query performance.
* **Error Handling:** Add error handling (e.g., `try-catch` blocks) to your Firestore interactions to handle potential issues.
* **Data Validation:** Implement data validation in your Dart code to ensure data integrity before storing it in Firestore.
This improved model provides a much more robust and maintainable way to work with your data in Dart and Firestore. Remember to complete the remaining classes and tailor the structure to your specific application requirements.
// ... (Example Firestore usage from previous response)
**Key Changes and Explanations:**
* **Naming Conventions:** Used more descriptive names like `IdDocument` and `LogEntry` instead of just `Id` and `Log`. This improves code readability. Also used `channelId` for consistency.
* **Data Types:** Used appropriate data types, including nullable types (`?`) where necessary. For example, `expiration` in `IdDocument` is nullable.
* **Boolean for Verified Status:** Changed `status` in `IdDocument` to a boolean (`bool`) since it represents a verified status (true/false).
* **DateTime Handling:** Consistent use of `DateTime` and `toIso8601String()` for storing and retrieving dates and times.
* **Location Data:** Added a basic `Location` class as a placeholder. You'll need to fill in the specific properties you need for your location data.
* **Clearer Comments:** Improved comments to explain the purpose of fields and classes.
Now you have a complete set of Dart classes for your data model, ready to be used with Firestore. Remember to handle potential errors and add data validation as needed in your application.
Security & Authentication:
Multi-Factor Authentication (MFA): Implement MFA (e.g., TOTP, SMS verification) for enhanced security. Store MFA-related data securely. Password History: Track previous passwords to prevent reuse. Failed Login Attempts: Record failed login attempts to detect and prevent brute-force attacks. Implement account lockout mechanisms. Security Questions: Allow users to set security questions for account recovery. Device Management: Track authorized devices associated with the account. Allow users to revoke access from specific devices. Terms of Service/Privacy Policy Acceptance: Record when the user accepted the latest versions of your terms and privacy policy. GDPR/CCPA Compliance Data: Store necessary data for compliance with privacy regulations (e.g., consent records).
Firestore Specifics:
Subcollections: Use subcollections effectively to organize related data (e.g., user profiles, authentication methods, etc.). This improves query performance and scalability. Security Rules: Implement Firestore Security Rules to control access to your data and prevent unauthorized modifications.
This is crucial for security.