diff --git a/go/firestore.rules b/go/firestore.rules new file mode 100644 index 0000000..e7b0ce1 --- /dev/null +++ b/go/firestore.rules @@ -0,0 +1,19 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + + // Per-human membership mirror maintained by Orion's service account. + // Locked from clients so the membership list cannot leak or be mutated. + match /humans/{humanId} { + allow read, write: if false; + } + + // Particles: caller must be a current member of the network in the path. + // get() on the mirror is cached across rule evaluations within a single + // request, so this costs 1 billed read per request regardless of query size. + match /networks/{networkId}/children/{particleId=**} { + allow read, write: if request.auth != null + && networkId in get(/databases/$(database)/documents/humans/$(request.auth.uid)).data.networks; + } + } +} diff --git a/go/firestore.tf b/go/firestore.tf new file mode 100644 index 0000000..66a78fd --- /dev/null +++ b/go/firestore.tf @@ -0,0 +1,109 @@ +resource "google_firebase_project" "default" { + provider = google-beta + project = var.project_id + + depends_on = [module.project-services] +} + +# Provisions the Firestore database instance. +resource "google_firestore_database" "firestore" { + provider = google-beta + project = google_firebase_project.default.project + name = "(default)" + # See available locations: https://firebase.google.com/docs/firestore/locations + location_id = var.location + # "FIRESTORE_NATIVE" is required to use Firestore with Firebase SDKs, authentication, and Firebase Security Rules. + type = "FIRESTORE_NATIVE" + concurrency_mode = "OPTIMISTIC" +} + +resource "google_firebaserules_ruleset" "default" { + provider = google-beta + project = google_firestore_database.firestore.project + source { + files { + name = "firestore.rules" + # Write security rules in a local file named "firestore.rules". + # Learn more: https://firebase.google.com/docs/firestore/security/get-started + content = file("firestore.rules") + } + } +} + +resource "google_firebaserules_release" "firestore" { + provider = google-beta + name = "cloud.firestore" + ruleset_name = google_firebaserules_ruleset.default.name + project = google_firestore_database.firestore.project +} + +resource "google_firebase_web_app" "llink" { + provider = google-beta + project = google_firebase_project.default.project + display_name = "Llink app" +} + +data "google_firebase_web_app_config" "llink" { + provider = google-beta + project = var.project_id + web_app_id = google_firebase_web_app.llink.app_id +} + +// Below indices support the new structure where all subcollections (including network root particles) live under a collection of name "children" +// instead of having `network/n_xxx/particles/p_yyy/children` where the top level subcollection of particles has a distinct name + +// Collection index for particles in a single subcollection +resource "google_firestore_index" "list_particles_index" { + project = var.project_id + collection = "children" + query_scope = "COLLECTION" + + fields { + field_path = "visible_to" + array_config = "CONTAINS" + } + + fields { + field_path = "status" + order = "ASCENDING" + } + + fields { + field_path = "last_child_created_at" + order = "DESCENDING" + } + + fields { + field_path = "__name__" + order = "DESCENDING" + } + + depends_on = [google_firestore_database.firestore] +} + +// Collection group index for particles across all subcollections +resource "google_firestore_field" "particle_children_created_at" { + database = google_firestore_database.firestore.name + project = var.project_id + collection = "children" + field = "created_at" + + index_config { + indexes { + order = "DESCENDING" + query_scope = "COLLECTION_GROUP" + } + indexes { + order = "ASCENDING" + query_scope = "COLLECTION_GROUP" + } + indexes { + order = "DESCENDING" + query_scope = "COLLECTION" + } + indexes { + order = "ASCENDING" + query_scope = "COLLECTION" + } + } +}