{ "entities": { "User": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "User", "type": "object", "description": "Represents an individual user of the ByteBrief application, managing their uploaded documents and generated summaries. User authentication details are handled by an external system, with only non-sensitive profile information stored here.", "properties": { "id": { "type": "string", "description": "Unique identifier for the User entity." }, "email": { "type": "string", "description": "The primary email address of the user, used for identification within the application.", "format": "email" }, "firstName": { "type": "string", "description": "The first name of the user." }, "lastName": { "type": "string", "description": "The last name of the user." }, "createdAt": { "type": "string", "description": "Timestamp indicating when the user account was created.", "format": "date-time" }, "updatedAt": { "type": "string", "description": "Timestamp indicating when the user account was last updated.", "format": "date-time" } }, "required": [ "id", "email", "firstName", "lastName", "createdAt", "updatedAt" ] }, "Document": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Document", "type": "object", "description": "Represents a document or file uploaded by a user for summarization, securely stored in a cloud-based storage solution.", "properties": { "id": { "type": "string", "description": "Unique identifier for the Document entity." }, "userId": { "type": "string", "description": "Reference to the User who uploaded this document. (Relationship: User 1:N Document)" }, "fileName": { "type": "string", "description": "The original name of the file when it was uploaded by the user." }, "cloudStoragePath": { "type": "string", "description": "The unique path or key identifying the document's location within the cloud storage system (e.g., S3 object key, Blob storage path)." }, "fileType": { "type": "string", "description": "The MIME type or file extension of the uploaded document (e.g., 'application/pdf', 'text/plain', 'image/jpeg')." }, "fileSize": { "type": "number", "description": "The size of the document in bytes." }, "uploadDate": { "type": "string", "description": "Timestamp indicating when the document was uploaded by the user.", "format": "date-time" }, "status": { "type": "string", "description": "The current processing status of the document (e.g., 'uploaded', 'processing', 'summarized', 'failed', 'deleted')." }, "content": { "type": "string", "description": "The full text content of the document." }, "originalContentHash": { "type": "string", "description": "A cryptographic hash (e.g., SHA256) of the document's content, useful for integrity checks or duplicate detection." } }, "required": [ "id", "userId", "fileName", "fileType", "fileSize", "uploadDate", "status" ] }, "Summary": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Summary", "type": "object", "description": "Represents an AI-generated summary of a specific document, linked to the original document and the owning user.", "properties": { "id": { "type": "string", "description": "Unique identifier for the Summary entity." }, "documentId": { "type": "string", "description": "Reference to the Document for which this summary was generated. (Relationship: Document 1:N Summary)" }, "userId": { "type": "string", "description": "Reference to the User who owns this summary (derived from the document's owner). (Relationship: User 1:N Summary)" }, "summaryText": { "type": "string", "description": "The actual text content of the AI-generated summary." }, "summaryLength": { "type": "string", "description": "Describes the intended length or verbosity of the summary (e.g., 'concise', 'detailed', 'keypoints')." }, "generatedAt": { "type": "string", "description": "Timestamp indicating when the summary was generated by the AI.", "format": "date-time" }, "aiModelUsed": { "type": "string", "description": "Identifier for the specific AI model or configuration that was used to generate this summary." }, "version": { "type": "number", "description": "A version number for the summary, useful if multiple summaries are generated or re-generated for the same document over time." } }, "required": [ "id", "documentId", "userId", "summaryText", "generatedAt" ] } }, "auth": { "providers": [ "password" ] }, "firestore": { "structure": [ { "path": "/users/{userId}", "definition": { "entityName": "User", "schema": { "$ref": "#/backend/entities/User" }, "description": "Stores individual user profiles. This collection is owned by the user, and access is restricted to the authenticated user matching 'userId'.", "params": [ { "name": "userId", "description": "The unique identifier of the user, corresponding to Firebase Authentication UID." } ] } }, { "path": "/users/{userId}/documents/{documentId}", "definition": { "entityName": "Document", "schema": { "$ref": "#/backend/entities/Document" }, "description": "A subcollection under a specific user, containing metadata for documents uploaded by that user. Each document includes the denormalized 'userId' field for authorization independence.", "params": [ { "name": "userId", "description": "The unique identifier of the user who owns this document, corresponding to Firebase Authentication UID." }, { "name": "documentId", "description": "The unique identifier for the specific document." } ] } }, { "path": "/users/{userId}/documents/{documentId}/summaries/{summaryId}", "definition": { "entityName": "Summary", "schema": { "$ref": "#/backend/entities/Summary" }, "description": "A nested subcollection under a specific document, containing AI-generated summaries for that document. Each summary includes denormalized 'userId' and 'documentId' fields for authorization independence.", "params": [ { "name": "userId", "description": "The unique identifier of the user who owns this summary, corresponding to Firebase Authentication UID." }, { "name": "documentId", "description": "The unique identifier of the document this summary belongs to." }, { "name": "summaryId", "description": "The unique identifier for the specific summary." } ] } } ], "reasoning": "The Firestore structure for ByteBrief prioritizes secure, scalable, and debuggable access to user-owned documents and their summaries. It adheres strictly to the core design principles, especially Authorization Independence and the enforcement of Query-Authentic Paths (QAPs).\n\n1. **Users Collection (`/users/{userId}`):** This top-level collection stores individual user profiles. Access is strictly private, meaning only the authenticated user (`request.auth.uid`) can read or write their own user document. This path establishes path-based ownership, making user profile rules straightforward (e.g., `allow read, write: if request.auth.uid == userId;`).\n\n2. **Documents Subcollection (`/users/{userId}/documents/{documentId}`):** Each user's uploaded documents are stored as a subcollection under their respective user profile. This hierarchical structure inherently links a document to its owner. Critical for **Authorization Independence**, each `Document` document will explicitly contain a `userId` field (denormalized from the parent user). This allows security rules to verify ownership directly within the document (`resource.data.userId`) without requiring a `get()` call to the parent user document. This is crucial for enabling atomic operations and simplifying rule logic. This structure supports QAPs by allowing clients to query for documents belonging to `request.auth.uid` within the specific user's path, and rules can directly validate `request.auth.uid == userId` (the path wildcard).\n\n3. **Summaries Subcollection (`/users/{userId}/documents/{documentId}/summaries/{summaryId}`):** AI-generated summaries are stored as a further nested subcollection, under the specific document they relate to. This continues the strong hierarchical ownership model. For **Authorization Independence**, each `Summary` document will explicitly contain both `userId` and `documentId` fields. This denormalization allows security rules to verify both the user's ownership and the summary's linkage to the correct document without any `get()` operations on parent documents. This enables `allow read, write: if request.auth.uid == userId;` to be applied efficiently and securely. QAPs are supported by the nested path structure, allowing specific user and document-scoped queries where rules can directly check `request.auth.uid == userId` (the path wildcard).\n\n**Authorization Independence via Denormalization:** This design achieves Authorization Independence by explicitly embedding `userId` in every `Document` document and both `userId` and `documentId` in every `Summary` document. This means that any security rule for a `Document` or `Summary` can evaluate ownership (`request.auth.uid == resource.data.userId`) directly on the document being accessed, without needing to perform `get()` operations on parent collections to determine the owner. This is fundamental for robust and efficient security rules, particularly in transactions and batched writes.\n\n**Support for QAPs (Query-Authentic Paths):** The entire structure is designed around path-based ownership (`/users/{userId}/...`). This inherently makes `list` operations secure and efficient. When a user queries for their documents or summaries, they will naturally be querying within their specific `/users/{request.auth.uid}/` path. The security rules can then simply verify that `request.auth.uid` matches the `{userId}` wildcard in the path, ensuring that users can only query their own data. The explicit `userId` and `documentId` fields in the documents further reinforce this, allowing for rules like `allow read: if request.auth.uid == userId;` which are naturally query-authentic." } }