Skip to content

Commit a676a90

Browse files
committed
Start schema definition
1 parent 96c9ede commit a676a90

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

firestore-schema.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Firestore schema (Spendee - inferred on 2025.08)
2+
3+
This document outlines the Firestore hierarchy and the main document/field schema used by the Spendee data export as observed from the client and example documents.
4+
5+
## Top-level hierarchy
6+
7+
- users/{userId}
8+
- wallets/{walletId}
9+
- transactions/{transactionId}
10+
- transactionLabels (collection)
11+
- categories/{categoryId}
12+
- labels/{labelId}
13+
14+
Notes:
15+
- Collections are named exactly as above. Documents often include a `path` field which repeats ids for convenience (e.g. `path.wallet`, `path.transaction`).
16+
- `transactions` documents reference categories by ID. There is a separate `transactionLabels` subcollection storing labels used for each transaction.
17+
18+
## Document structures and fields (examples / inferred types)
19+
20+
- users/{user}
21+
- path: { user: string } (document path metadata)
22+
- firestoreDataExportDone: boolean
23+
- name: string (user name)
24+
- email: string
25+
- legacyId: integer (legacy numeric id)
26+
- profile: object (profile metadata)
27+
- birthDate: string (YYYY-MM-DD)
28+
- gender: nullable string
29+
- email: string
30+
- firstname: string
31+
- lastname: string
32+
- photo: url string (profile image)
33+
- subscription: object (optional)
34+
- introOfferUsedOn: array
35+
- isNftOwner: nullable boolean
36+
- productId: string
37+
- renewalPeriod: string
38+
- pastType: string
39+
- expirationDate: nullable Timestamp
40+
- renewalDate: Timestamp
41+
- type: string (subscription tier)
42+
- platform: string
43+
- referral: object (optional)
44+
- code: string
45+
- isRegisteredViaReferral: boolean
46+
- referredUsersCount: integer
47+
- timezone: string (IANA tz, e.g. `Europe/Budapest`)
48+
- homeFeed: object (UI state)
49+
- lastDisplayedCard: string (card id)
50+
- device: object
51+
- languageCode: string (e.g. `hu`)
52+
- countryCode: string (e.g. `HU`)
53+
- registeredAt: Timestamp
54+
- updatedAt: Timestamp
55+
- globalCurrency: string (e.g. `HUF`)
56+
- modelVersion: number
57+
- visibleWallets: array of wallet id strings
58+
59+
Notes:
60+
- Many of these are optional; `subscription` and `referral` may be absent or contain nullable members.
61+
62+
- users/{user}/wallets/{wallet}
63+
- path: { wallet: string, user: string }
64+
- name: string
65+
- type: string (e.g., "cash", "bank")
66+
- currency: string (e.g., "HUF", "EUR")
67+
- status: string (e.g., "active")
68+
- startingBalance: string (decimal formatted as string; may be negative and high-precision)
69+
- updatedAt: Timestamp
70+
- visibleCategories: array of category id strings (optional)
71+
- modelVersion: number (integer or float)
72+
- sharedWithUsers: array of user ids (present for shared wallets; may be empty)
73+
- bank: object (present for `bank` type wallets)
74+
- nature: string (e.g. `account`)
75+
- connection: string (connection id)
76+
- accountNumber: string (IBAN/account number)
77+
78+
Notes and edge cases:
79+
- `currency` may differ per-wallet (HUF, EUR, etc.).
80+
- `visibleCategories` lists category IDs that are permitted/visible in that wallet; not every wallet has the same set.
81+
82+
83+
- users/{user}/categories/{category}
84+
- id: string (same as document id) -- some exports show this in list responses
85+
- name: string (e.g., "Other", "Étkezés")
86+
- type: string ("expense" or "income")
87+
88+
- users/{user}/labels/{label}
89+
- text or name: string (label text). In list output `text` appears but client maps it to `name`.
90+
- (document id) is the label id
91+
92+
- users/{user}/wallets/{wallet}/transactions/{transaction}
93+
- id: string (in many responses added by the client from `path.transaction`)
94+
- path: { user: string, wallet: string, transaction: string }
95+
- author: string (user id who created the transaction)
96+
- note: string (transaction description)
97+
- madeAt: Timestamp / DatetimeWithNanoseconds
98+
- madeAtTimezone: string (e.g., "Europe/Budapest")
99+
- madeAtTimezoneOffset: integer (seconds)
100+
- usdValue: { amount: string, exchangeRate: string } (strings in the Firestore export)
101+
- amount: number or string (local currency amount, sign indicates direction)
102+
- type: string (e.g., "regular")
103+
- isPending: boolean
104+
- category: categoryId (UUID string)
105+
- updatedAt: Timestamp
106+
- modelVersion: integer (1 currently)
107+
108+
Example (extracted):
109+
{
110+
'author': 'af150597-5b96-4f8b-9eb3-0b2e0aa72a81',
111+
'note': '20250705 00074524 1007 MAMMUT BUDAPEST 4.500,00 HUF ...',
112+
'madeAt': DatetimeWithNanoseconds(2025, 7, 7, 10, 0, tzinfo=datetime.timezone.utc),
113+
'madeAtTimezoneOffset': 7200,
114+
'usdValue': {'exchangeRate': '340.45914320', 'amount': '-13.21744500'},
115+
'type': 'regular',
116+
'modelVersion': 1,
117+
'updatedAt': DatetimeWithNanoseconds(...),
118+
'madeAtTimezone': 'Europe/Budapest',
119+
'category': '2a4df530-a79f-49f4-8623-60000d434daa',
120+
'path': {'wallet': 'b368c5c2-68fe-4f98-9d4f-08e0cdca57a7', 'transaction': 'd2b4caa7-12eb-4c04-a744-d2bf7e02bdd2', 'user': 'af150597-5b96-4f8b-9eb3-0b2e0aa72a81'},
121+
'isPending': False,
122+
'amount': '-4500'
123+
}
124+
125+
126+
## transactionLabels subcollection
127+
- users/{user}/wallets/{wallet}/transactions/{transaction}/transactionLabels
128+
- documents are small and often only contain a `label` field that references the label ID
129+
- list example (client side): `[x.get('label') for x in collection.get()]` -> `['01e9e6be-34ea-...']`
130+
- The client resolves IDs to human names using the labels collection
131+
132+
## Cross-references and client-side maps
133+
- Client keeps mappings for convenience:
134+
- wallet_name_map: { wallet_name: wallet_id }
135+
- category_name_map: { category_id: category_name }
136+
- category_type_map: { category_id: 'income'|'expense' }
137+
- label_name_map: { label_id: label_text }
138+
139+
## How to dump arbitrary documents (helper)
140+
- A helper `_get_raw_document(self, path: str, as_json: bool = False)` is useful to inspect any document by absolute path (for example `users/{userId}`, `users/{userId}/wallets/{walletId}`).
141+
- Example use (the project `run.py` now calls several examples):
142+
- `spendee._get_raw_document(f"users/{spendee.user_id}", as_json=True)`
143+
- `spendee._get_raw_document(f"users/{spendee.user_id}/wallets/{walletId}", as_json=True)`
144+
145+
Provide outputs of these helper calls and I will update this schema with exact fields and types where necessary.
146+
147+
## Next steps / tips
148+
- If you paste the JSON output of these helper calls here, I'll extend this file with precise types and optional fields, and add a complete JSON Schema if you want one.
149+
150+
---
151+
"documentChange": {
152+
"document": {
153+
"name": "projects/spendee-app/databases/(default)/documents/users/af150597-5b96-4f8b-9eb3-0b2e0aa72a81/deletedDocuments/75b406c0-a897-44f3-bce9-5191321d0507",
154+
"fields": {
155+
"modelVersion": {
156+
"integerValue": "1"
157+
},
158+
"path": {
159+
"mapValue": {
160+
"fields": {
161+
"deletedDocument": {
162+
"stringValue": "75b406c0-a897-44f3-bce9-5191321d0507"
163+
},
164+
"user": {
165+
"stringValue": "af150597-5b96-4f8b-9eb3-0b2e0aa72a81"
166+
}
167+
}
168+
}
169+
},
170+
"createdAt": {
171+
"timestampValue": "2025-08-22T13:07:07.586Z"
172+
},
173+
"deletedDocumentPath": {
174+
"stringValue": "users/af150597-5b96-4f8b-9eb3-0b2e0aa72a81/wallets/b368c5c2-68fe-4f98-9d4f-08e0cdca57a7/transactions/fbe90b02-f382-4607-808c-d37bcd17ae76/transactionLabels/0e6ec129-dc67-4fba-92db-953c4677cf76"
175+
}
176+
},
177+
"createTime": "2025-08-22T13:07:07.615660Z",
178+
"updateTime": "2025-08-22T13:07:07.615660Z"
179+
},
180+
"targetIds": [
181+
44
182+
]
183+
}
184+
}

0 commit comments

Comments
 (0)