File size: 11,757 Bytes
6182c14 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | {
"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."
}
} |