Database Schema
Pocket Money uses Cloudflare D1, a SQLite-based database.
Entity Relationships
erDiagram
families ||--o{ users : has
families ||--o{ children : has
families ||--|| settings : has
families ||--o{ invite_codes : has
children ||--o{ saving_targets : has
children ||--o{ recurring_rules : has
children ||--o{ transactions : has
users ||--o{ transactions : creates
users ||--o{ sessions : has
Tables
families
Core family unit that groups users and children.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
name |
TEXT | Family name |
created_at |
INTEGER | Unix timestamp |
users
Registered user accounts.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
email |
TEXT | Unique email address |
password_hash |
TEXT | bcrypt hash |
name |
TEXT | Display name |
photo_url |
TEXT | External photo URL (nullable) |
photo_data |
TEXT | Base64 encoded image (nullable) |
family_id |
TEXT | Foreign key → families |
created_at |
INTEGER | Unix timestamp |
invite_codes
Codes for inviting users to join a family.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
code |
TEXT | Unique 8-char alphanumeric code |
family_id |
TEXT | Foreign key → families |
created_by |
TEXT | Foreign key → users |
expires_at |
INTEGER | Unix timestamp (7 days from creation) |
used_by |
TEXT | Foreign key → users (nullable) |
used_at |
INTEGER | Unix timestamp (nullable) |
created_at |
INTEGER | Unix timestamp |
settings
Per-family application settings.
| Column | Type | Default | Description |
|---|---|---|---|
id |
INTEGER | Auto | Primary key |
family_id |
TEXT | Foreign key → families | |
currency |
TEXT | 'EUR' | Display currency code |
pin_enabled |
INTEGER | 0 | PIN protection enabled |
pin_hash |
TEXT | NULL | bcrypt hash of PIN |
pin_timeout_minutes |
INTEGER | 1 | Minutes before PIN required |
webhook_url |
TEXT | NULL | URL for event notifications |
webhook_secret |
TEXT | NULL | Secret for HMAC signature (40 hex) |
children
Child profiles.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
name |
TEXT | Display name |
color |
TEXT | Profile color |
photo_url |
TEXT | External photo URL (nullable) |
photo_data |
TEXT | Base64 encoded image (nullable) |
family_id |
TEXT | Foreign key → families |
sort_order |
INTEGER | Display order |
created_at |
INTEGER | Unix timestamp |
saving_targets
Savings goals for children.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
child_id |
TEXT | Foreign key → children |
name |
TEXT | Target name |
target_amount |
REAL | Goal amount |
sort_order |
INTEGER | Display order (for stacking) |
photo_data |
TEXT | Base64 encoded image (nullable) |
description |
TEXT | Target description (nullable) |
link |
TEXT | URL to purchase item (nullable) |
created_at |
INTEGER | Unix timestamp |
recurring_rules
Automatic payment rules.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
child_id |
TEXT | Foreign key → children |
amount |
REAL | Payment amount |
description |
TEXT | Optional description |
interval_days |
INTEGER | Days between payments |
next_run_at |
INTEGER | Unix timestamp of next payment |
skip_next |
INTEGER | Skip next payment flag (0/1) |
active |
INTEGER | Rule is active (0/1) |
created_at |
INTEGER | Unix timestamp |
transactions
All money movements.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
child_id |
TEXT | Foreign key → children |
user_id |
TEXT | Foreign key → users (nullable for recurring) |
amount |
REAL | Positive=deposit, negative=withdrawal |
description |
TEXT | Optional description |
is_recurring |
INTEGER | Created by recurring rule (0/1) |
recurring_rule_id |
TEXT | Foreign key → recurring_rules (nullable) |
created_at |
INTEGER | Unix timestamp |
sessions
User authentication sessions.
| Column | Type | Description |
|---|---|---|
id |
TEXT | Primary key (UUID) |
user_id |
TEXT | Foreign key → users |
pin_verified_at |
INTEGER | Unix timestamp (nullable) |
expires_at |
INTEGER | Unix timestamp |
created_at |
INTEGER | Unix timestamp |
Migrations
Migrations are stored in migrations/ and run in order:
| File | Description |
|---|---|
0001_initial.sql |
Core tables |
0002_photo_data.sql |
Photo storage columns |
0003_families.sql |
Family system tables |
0004_webhooks.sql |
Webhook URL setting |
0005_webhook_secret.sql |
Webhook secret for HMAC signatures |
0006_saving_target_fields.sql |
Target photo, description, link columns |
Running Migrations
Local:
npm run db:migrate
Production:
wrangler d1 execute pocket-money-db --remote --file=./migrations/0001_initial.sql
wrangler d1 execute pocket-money-db --remote --file=./migrations/0002_photo_data.sql
wrangler d1 execute pocket-money-db --remote --file=./migrations/0003_families.sql
wrangler d1 execute pocket-money-db --remote --file=./migrations/0004_webhooks.sql
wrangler d1 execute pocket-money-db --remote --file=./migrations/0005_webhook_secret.sql
wrangler d1 execute pocket-money-db --remote --file=./migrations/0006_saving_target_fields.sql
Admin Operations
Resetting a User's Password
If a user forgets their password and cannot use the in-app password change feature, you can reset it directly via Cloudflare D1.
Step 1: Generate a bcrypt hash
Passwords are hashed using bcrypt with 12 rounds. Generate a hash for the new password using one of these methods:
Using Node.js:
node -e "const bcrypt = require('bcryptjs'); bcrypt.hash('newpassword123', 12).then(console.log)"
Using an online bcrypt generator:
Use a tool like bcrypt-generator.com with 12 rounds.
Warning
When using online tools, ensure you trust the site and consider that you're entering a password that will be used.
Step 2: Find the user's ID
Using the Cloudflare dashboard or wrangler CLI:
wrangler d1 execute pocket-money-db --remote --command "SELECT id, email, name FROM users WHERE email = 'user@example.com'"
Step 3: Update the password
Replace USER_ID with the user's ID and BCRYPT_HASH with the generated hash:
wrangler d1 execute pocket-money-db --remote --command "UPDATE users SET password_hash = '\$2a\$12\$...' WHERE id = 'USER_ID'"
Note
The $ characters in bcrypt hashes need to be escaped as \$ when using the command line.
Alternatively, you can run the update directly from the Cloudflare D1 console in your browser, where escaping is not needed.
Changing a User's Email
wrangler d1 execute pocket-money-db --remote --command "UPDATE users SET email = 'newemail@example.com' WHERE id = 'USER_ID'"
Listing All Users
wrangler d1 execute pocket-money-db --remote --command "SELECT id, email, name, family_id FROM users"
Listing All Families
wrangler d1 execute pocket-money-db --remote --command "SELECT f.id, f.name, COUNT(u.id) as member_count FROM families f LEFT JOIN users u ON u.family_id = f.id GROUP BY f.id"