Flamelink uses a flat collection structure for all data managed in your Cloud Firestore database.
All the Flamelink specific collection names start with a fl_
to make them easily recognisable.
Why does Flamelink store all my content in one collection?
If you take a look at your database, you'll see that Flamelink stores all the content specific data in one collection, called fl_content
. You might wonder about this. Won't it create performance problems? The great news is that Cloud Firestore's query performance is based on the size of the result set, ie. the number of documents in the result of your query and not based on the size of the collection itself. For this reason, there is not a problem with performance using a single collection. Furthermore, using a single collection makes keep your content in sync a much easier task.
If you need to query the content for a specific schema in a specific environment for a given language, you can construct your query with numerous where
clauses using
the specific properties in each document's _fl_meta_
map.
For arguments sake, let's say you want all the entries for a categories
schema for the default environment and language, production
and en-US
respectively, your constructed query could look something like this (JavaScript example - see the Firebase documentation for your language of choice):
firebase
.firestore()
.collection('fl_content')
.where('_fl_meta_.env', '==', 'production')
.where('_fl_meta_.locale', '==', 'en-US')
.where('_fl_meta_.schema', '==', 'categories')
.get()
If you use one of our official SDK's to query your content, this is handled for you, so you do not need to worry about it.
I want to set custom security rules, what are all the Flamelink collections?
If you want to specify custom rules for your Firestore collections, you'll have to set the rules for each of the Flamelink collections. Unfortunately, Firebase does not currently support specifying rules with wildcards in the collection name, because then you would be able to easily set a rule for all collections starting with fl_
. Hopefully we'll get that soon.
In the mean time, here is a list of all the current Flamelink collections. If your project login starts to fail after a new feature has been released, it might mean we have a new collection and your rules are not allowing us to read it yet.
fl_backups
fl_content
fl_environments
fl_files
fl_folders
fl_locales
fl_navigation
fl_permissions
fl_schemas
fl_settings
fl_users
fl_webhooks
fl_webhooks/{document}/activityLog
fl_workflows
An example rule would look like this:
match /fl_content/{document} {
allow read, write: if true;
}
You might have noticed for the Webhooks collection, we also have an activityLog
subcollection. If you're using custom rules, you'll have to explicitly set read rules for this subcollection to access it from within the app.
match /fl_webhooks/{document} {
allow read, write: if true;
}
match /fl_webhooks/{document}/activityLog/{log} {
allow read, write: if true;
}
Example Custom Database Security Rules
The following Firestore Security rules serves as an example, a starting point, to write your rules for your specific use case.
A huge thanks to Jakob Danelund who provided the example and Ribal Nasr who created the initial idea here.
The example makes 2 assumptions:
Guest permissions are located at
fl_permissions/guest
(manually created)Super admin permissions are located at
fl_permissions/1
(Flamelink created)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ...can be combined with other custom rules for your app
function guestPermissions() {
return get(/databases/$(database)/documents/fl_permissions/guest).data
}
function userPermissions(){
return get(get(/databases/$(database)/documents/fl_users/$(request.auth.uid)).data.permissions).data
}
function isContentPermitted(schema, action) {
return guestPermissions().content.production[schema][action] == true || (request.auth != null && (userPermissions().content.production[schema][action] == true || userPermissions().id == '1'))
}
function isCollectionPermitted(collection, action) {
return guestPermissions()[collection][action] == true || (request.auth != null && userPermissions()[collection][action] == true)
}
function isCollectionPermittedProduction(collection, action) {
return guestPermissions()[collection].production[action] == true || (request.auth != null && userPermissions()[collection].production[action] == true)
}
function isSettingsPermitted(settings, action) {
return guestPermissions().settings[settings][action] == true || (request.auth != null && userPermissions().settings[settings][action] == true)
}
match /fl_content/{document=**} {
allow read: if isContentPermitted(resource.data._fl_meta_.schema, 'view') ;
allow update: if isContentPermitted(request.resource.data._fl_meta_.schema, 'update');
allow create: if isContentPermitted(request.resource.data._fl_meta_.schema, 'create');
allow delete: if isContentPermitted(resource.data._fl_meta_.schema, 'delete');
}
match /fl_environments/{document} {
allow read: if isSettingsPermitted('environments', 'view');
allow update: if isSettingsPermitted('environments', 'update');
allow create: if isSettingsPermitted('environments', 'create');
allow delete: if isSettingsPermitted('environments', 'delete');
}
match /fl_files/{document=**} {
allow read: if isCollectionPermitted('media', 'view');
allow update: if isCollectionPermitted('media', 'update');
allow create: if isCollectionPermitted('media', 'create');
allow delete: if isCollectionPermitted('media', 'delete');
}
match /fl_folders/{document=**} {
allow read: if isCollectionPermitted('media', 'view');
allow update: if isCollectionPermitted('media', 'update');
allow create: if isCollectionPermitted('media', 'create');
allow delete: if isCollectionPermitted('media', 'delete');
}
match /fl_locales/{document=**} {
allow read: if isSettingsPermitted('locales', 'view');
allow update: if isSettingsPermitted('locales', 'update');
allow create: if isSettingsPermitted('locales', 'create');
allow delete: if isSettingsPermitted('locales', 'delete');
}
match /fl_navigation/{document=**} {
allow read: if isCollectionPermittedProduction('navigation', 'view');
allow update: if isCollectionPermittedProduction('navigation', 'update');
allow create: if isCollectionPermittedProduction('navigation', 'create');
allow delete: if isCollectionPermittedProduction('navigation', 'delete');
}
match /fl_permissions/{document=**} {
allow read: if isCollectionPermitted('permissions', 'view');
allow update: if isCollectionPermitted('permissions', 'update');
allow create: if isCollectionPermitted('permissions', 'create');
allow delete: if isCollectionPermitted('permissions', 'delete');
}
match /fl_schemas/{document=**} {
allow read: if isCollectionPermittedProduction('schemas', 'view');
allow update: if isCollectionPermittedProduction('schemas', 'update');
allow create: if isCollectionPermittedProduction('schemas', 'create');
allow delete: if isCollectionPermittedProduction('schemas', 'delete');
}
match /fl_settings/{document=**} {
allow read: if isSettingsPermitted('general', 'view');
allow update: if isSettingsPermitted('general', 'update');
allow create: if isSettingsPermitted('general', 'create');
allow delete: if isSettingsPermitted('general', 'delete');
}
match /fl_users/{document=**} {
allow read: if request.auth.uid == resource.id || isCollectionPermitted('users', 'view');
allow update: if request.auth.uid == resource.id || isCollectionPermitted('users', 'update');
allow create: if isCollectionPermitted('users', 'create');
allow delete: if isCollectionPermitted('users', 'delete');
}
match /fl_backups/{document=**} {
allow read: if isSettingsPermitted('backups', 'view');
allow update: if isSettingsPermitted('backups', 'update');
allow create: if isSettingsPermitted('backups', 'create');
allow delete: if isSettingsPermitted('backups', 'delete');
}
match /fl_workflows/{document} {
allow read: if isSettingsPermitted('workflows', 'view');
allow update: if isSettingsPermitted('workflows', 'update');
allow create: if isSettingsPermitted('workflows', 'create');
allow delete: if isSettingsPermitted('workflows', 'delete');
}
match /fl_webhooks/{document} {
allow read: if isCollectionPermitted('webhooks', 'view');
allow update: if isCollectionPermitted('webhooks', 'update');
allow create: if isCollectionPermitted('webhooks', 'create');
allow delete: if isCollectionPermitted('webhooks', 'delete');
}
match /fl_webhooks/{document}/activityLog/{log} {
allow read: if isCollectionPermitted('webhooks', 'view');
allow update: if isCollectionPermitted('webhooks', 'update');
allow create: if isCollectionPermitted('webhooks', 'create');
allow delete: if isCollectionPermitted('webhooks', 'delete');
}
}
}