23 KiB
Life Management System — Database Schema
Overview
PostgreSQL 16 on port 25470. Database name: life_manager. All entities use UUID primary keys and timestamptz timestamps via BaseEntity from @lilith/typeorm-entities. Sensitive fields encrypted at rest via pgcrypto extension using @lilith/typeorm-pgcrypto.
Extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
Entity Hierarchy
BaseEntity (abstract)
├── id: UUID (auto-generated)
├── createdAt: timestamptz
└── updatedAt: timestamptz
SoftDeletableEntity extends BaseEntity (abstract)
└── deletedAt: timestamptz | null
All entities extend BaseEntity. Entities with soft delete support extend SoftDeletableEntity (Task, Contact).
Core Entities
domains
Life domains — the top-level organizational unit for everything in the system.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK, default uuid_generate_v4() |
|
name |
varchar(100) |
NOT NULL | Display name |
type |
varchar(30) |
NOT NULL | Enum: client_work, software, side_project, life_admin, modeling, escort, content, physical |
slug |
varchar(50) |
UNIQUE, NOT NULL | URL-safe identifier |
color |
varchar(7) |
NOT NULL | Hex color code (e.g., #4A90D9) |
icon |
varchar(30) |
NOT NULL | Icon identifier |
description |
text |
Optional description | |
is_active |
boolean |
NOT NULL, default true |
Soft toggle |
sort_order |
integer |
NOT NULL, default 0 |
Display ordering |
config |
jsonb |
default '{}' |
Domain-specific configuration |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_domains_slugUNIQUE onslugidx_domains_typeontypeidx_domains_is_activeonis_active
goals
Hierarchical goals from yearly down to weekly. Self-referencing parent for nesting.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
Owning domain |
parent_id |
uuid |
FK → goals.id, nullable |
Parent goal (self-ref) |
title |
varchar(255) |
NOT NULL | Goal title |
description |
text |
Detailed description | |
level |
varchar(20) |
NOT NULL | Enum: yearly, quarterly, monthly, weekly |
status |
varchar(20) |
NOT NULL, default 'active' |
Enum: active, completed, abandoned, paused |
target_date |
date |
Target completion date | |
progress_pct |
integer |
default 0 |
0–100 progress percentage |
sort_order |
integer |
NOT NULL, default 0 |
|
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_goals_domain_idondomain_ididx_goals_parent_idonparent_ididx_goals_levelonlevelidx_goals_statusonstatus
Relations:
domain→domains(ManyToOne)parent→goals(ManyToOne, self-ref)children→goals[](OneToMany, self-ref)tasks→tasks[](OneToMany)habits→habits[](OneToMany)
tasks
Tasks with subtask support, domain scoping, energy level tagging, and recurrence.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
Owning domain |
goal_id |
uuid |
FK → goals.id, nullable |
Linked goal |
sprint_id |
uuid |
FK → sprints.id, nullable |
Linked sprint |
parent_id |
uuid |
FK → tasks.id, nullable |
Parent task (subtasks) |
title |
varchar(500) |
NOT NULL | Task title |
description |
text |
Detailed description | |
status |
varchar(20) |
NOT NULL, default 'todo' |
Enum: backlog, todo, in_progress, blocked, done, cancelled |
priority |
varchar(10) |
NOT NULL, default 'medium' |
Enum: critical, high, medium, low |
energy_level |
varchar(10) |
NOT NULL, default 'medium' |
Enum: high, medium, low |
estimated_minutes |
integer |
Time estimate | |
actual_minutes |
integer |
Actual time spent | |
due_date |
date |
Hard deadline | |
scheduled_date |
date |
Planned execution date | |
is_quick_win |
boolean |
NOT NULL, default false |
Quick win flag (< 5 min) |
recurrence_rule |
varchar(255) |
iCal RRULE for recurring tasks | |
tags |
text[] |
default '{}' |
Freeform tags |
sort_order |
integer |
NOT NULL, default 0 |
|
deleted_at |
timestamptz |
Soft delete (SoftDeletableEntity) | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_tasks_domain_idondomain_ididx_tasks_goal_idongoal_ididx_tasks_sprint_idonsprint_ididx_tasks_parent_idonparent_ididx_tasks_statusonstatusidx_tasks_priorityonpriorityidx_tasks_energy_levelonenergy_levelidx_tasks_due_dateondue_dateidx_tasks_scheduled_dateonscheduled_dateidx_tasks_is_quick_winonis_quick_winWHEREstatus NOT IN ('done', 'cancelled')idx_tasks_deleted_atondeleted_at
Relations:
domain→domains(ManyToOne)goal→goals(ManyToOne)sprint→sprints(ManyToOne)parent→tasks(ManyToOne, self-ref)subtasks→tasks[](OneToMany, self-ref)timeBlocks→time_blocks[](OneToMany)
habits
Recurring habits with frequency configuration and streak tracking.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
Owning domain |
goal_id |
uuid |
FK → goals.id, nullable |
Linked goal |
name |
varchar(255) |
NOT NULL | Habit name |
description |
text |
Description | |
frequency |
varchar(20) |
NOT NULL, default 'daily' |
Enum: daily, weekly, custom |
frequency_config |
jsonb |
default '{}' |
Custom frequency config (e.g., specific days of week) |
time_of_day |
varchar(20) |
NOT NULL, default 'anytime' |
Enum: morning, afternoon, evening, anytime |
estimated_minutes |
integer |
Time estimate | |
is_active |
boolean |
NOT NULL, default true |
Whether habit is currently active |
streak_current |
integer |
NOT NULL, default 0 |
Current consecutive streak |
streak_best |
integer |
NOT NULL, default 0 |
Best streak ever |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_habits_domain_idondomain_ididx_habits_is_activeonis_activeidx_habits_frequencyonfrequencyidx_habits_time_of_dayontime_of_day
frequency_config JSONB examples:
// Weekly on Mon, Wed, Fri
{ "daysOfWeek": [1, 3, 5] }
// Every 3 days
{ "intervalDays": 3 }
// Custom: 5 times per week (any days)
{ "timesPerWeek": 5 }
habit_check_ins
Individual check-in records for habit completion.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
habit_id |
uuid |
FK → habits.id, NOT NULL, ON DELETE CASCADE |
|
date |
date |
NOT NULL | Check-in date |
status |
varchar(10) |
NOT NULL | Enum: done, skipped, partial |
quality_rating |
integer |
1–5 quality self-rating | |
notes |
text |
Optional notes | |
completed_at |
timestamptz |
When marked complete | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_habit_check_ins_habit_dateUNIQUE on(habit_id, date)idx_habit_check_ins_dateondateidx_habit_check_ins_statusonstatus
time_blocks
Calendar time blocks for daily scheduling.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, nullable |
Associated domain |
task_id |
uuid |
FK → tasks.id, nullable |
Linked task |
habit_id |
uuid |
FK → habits.id, nullable |
Linked habit |
title |
varchar(255) |
NOT NULL | Block title |
date |
date |
NOT NULL | Calendar date |
start_time |
time |
NOT NULL | Start time |
end_time |
time |
NOT NULL | End time |
block_type |
varchar(20) |
NOT NULL | Enum: work, personal, health, break, transit |
status |
varchar(20) |
NOT NULL, default 'planned' |
Enum: planned, in_progress, completed, skipped, rescheduled |
notes |
text |
||
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_time_blocks_dateondateidx_time_blocks_domain_idondomain_ididx_time_blocks_date_starton(date, start_time)
daily_plans
One per day — morning assessment and evening reflection.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
date |
date |
UNIQUE, NOT NULL | Plan date |
energy_level |
varchar(10) |
Enum: high, medium, low |
|
focus_domains |
uuid[] |
default '{}' |
Domain IDs to focus on today |
morning_intention |
text |
Morning intention text | |
evening_reflection |
text |
Evening reflection text | |
mood_morning |
integer |
1–5 mood rating | |
mood_evening |
integer |
1–5 mood rating | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_daily_plans_dateUNIQUE ondate
Domain-Specific Entities
income_entries (ENCRYPTED)
Revenue tracking across income-generating domains.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
|
date |
date |
NOT NULL | Income date |
amount_encrypted |
bytea |
NOT NULL | ENCRYPTED — monetary amount |
currency |
varchar(3) |
NOT NULL, default 'GBP' |
ISO currency code |
source_type |
varchar(20) |
NOT NULL | Enum: session, subscription, tip, content_sale, invoice, other |
description_encrypted |
bytea |
ENCRYPTED — description | |
duration_minutes |
integer |
Duration (for sessions) | |
contact_id |
uuid |
FK → contacts.id, nullable |
Associated contact |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_income_entries_domain_idondomain_ididx_income_entries_dateondateidx_income_entries_source_typeonsource_type
Encryption columns:
amount↔amount_encrypted(via@EncryptedColumn())description↔description_encrypted(via@EncryptedColumn())
billable_entries
Billable hours for client/contract work.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
|
task_id |
uuid |
FK → tasks.id, nullable |
Linked task |
date |
date |
NOT NULL | |
hours |
decimal(5,2) |
NOT NULL | Billable hours |
rate |
decimal(10,2) |
NOT NULL | Hourly rate |
currency |
varchar(3) |
NOT NULL, default 'GBP' |
|
description |
text |
Work description | |
is_invoiced |
boolean |
NOT NULL, default false |
|
invoice_ref |
varchar(100) |
Invoice reference | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_billable_entries_domain_idondomain_ididx_billable_entries_dateondateidx_billable_entries_is_invoicedonis_invoiced
measurements
Body measurements for physical/modeling domains.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
|
date |
date |
NOT NULL | |
type |
varchar(20) |
NOT NULL | Enum: weight, bust, waist, hips, height, body_fat_pct, custom |
value |
decimal(10,2) |
NOT NULL | Measurement value |
unit |
varchar(10) |
NOT NULL | Unit (kg, cm, %, etc.) |
notes |
text |
||
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_measurements_domain_idondomain_ididx_measurements_type_dateon(type, date)
medical_entries (ENCRYPTED)
HRT doses, appointments, lab results, prescriptions.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
|
date |
date |
NOT NULL | |
type |
varchar(20) |
NOT NULL | Enum: hrt_dose, appointment, lab_result, prescription, note |
title |
varchar(255) |
NOT NULL | Entry title |
details_encrypted |
bytea |
ENCRYPTED — detailed notes | |
provider |
varchar(255) |
Provider/clinic name | |
next_date |
date |
Next scheduled date | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_medical_entries_domain_idondomain_ididx_medical_entries_typeontypeidx_medical_entries_dateondateidx_medical_entries_next_dateonnext_date
Encryption columns:
details↔details_encrypted(via@EncryptedColumn())
contacts (ENCRYPTED)
Clients, agencies, collaborators with encrypted sensitive fields.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
|
name |
varchar(255) |
NOT NULL | Contact name |
contact_type |
varchar(20) |
NOT NULL | Enum: client, agency, collaborator, provider, other |
phone_encrypted |
bytea |
ENCRYPTED — phone number | |
email |
varchar(255) |
Email address | |
notes_encrypted |
bytea |
ENCRYPTED — private notes | |
rating |
integer |
1–5 rating | |
is_active |
boolean |
NOT NULL, default true |
|
last_interaction |
timestamptz |
Last interaction date | |
metadata |
jsonb |
default '{}' |
Flexible metadata |
deleted_at |
timestamptz |
Soft delete | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_contacts_domain_idondomain_ididx_contacts_contact_typeoncontact_typeidx_contacts_is_activeonis_activeidx_contacts_deleted_atondeleted_at
Encryption columns:
phone↔phone_encrypted(via@EncryptedColumn())notes↔notes_encrypted(via@EncryptedColumn())
sprints
Sprint boards for software development domains.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
|
name |
varchar(255) |
NOT NULL | Sprint name |
start_date |
date |
NOT NULL | Sprint start |
end_date |
date |
NOT NULL | Sprint end |
status |
varchar(20) |
NOT NULL, default 'planned' |
Enum: planned, active, completed, cancelled |
goal |
text |
Sprint goal | |
retrospective |
text |
Sprint retro notes | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_sprints_domain_idondomain_ididx_sprints_statusonstatusidx_sprints_date_rangeon(start_date, end_date)
content_calendar
Content calendar for OnlyFans and social media.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
domain_id |
uuid |
FK → domains.id, NOT NULL |
|
title |
varchar(255) |
NOT NULL | Content title |
content_type |
varchar(20) |
NOT NULL | Enum: photo_set, video, story, post |
scheduled_date |
date |
Planned publish date | |
platform |
varchar(30) |
Target platform (onlyfans, instagram, twitter, etc.) | |
status |
varchar(20) |
NOT NULL, default 'idea' |
Enum: idea, planned, created, scheduled, published |
notes |
text |
Production notes | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_content_calendar_domain_idondomain_ididx_content_calendar_scheduled_dateonscheduled_dateidx_content_calendar_statusonstatusidx_content_calendar_platformonplatform
Chat Entities
conversations
Chat conversations with the AI assistant.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
title |
varchar(255) |
Conversation title (auto-generated or manual) | |
model |
varchar(50) |
LLM model used | |
is_voice |
boolean |
NOT NULL, default false |
Whether this was a voice conversation |
started_at |
timestamptz |
NOT NULL | |
archived_at |
timestamptz |
When archived | |
created_at |
timestamptz |
NOT NULL | |
updated_at |
timestamptz |
NOT NULL |
Indexes:
idx_conversations_started_atonstarted_atDESCidx_conversations_archived_atonarchived_at
messages
Individual messages within conversations.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
uuid |
PK | |
conversation_id |
uuid |
FK → conversations.id, NOT NULL, ON DELETE CASCADE |
|
role |
varchar(10) |
NOT NULL | Enum: system, user, assistant |
content |
text |
NOT NULL | Message content |
model |
varchar(50) |
LLM model (for assistant messages) | |
token_count |
integer |
Token usage | |
duration_ms |
integer |
Generation duration | |
metadata |
jsonb |
default '{}' |
Tool calls, context used, etc. |
parent_id |
uuid |
FK → messages.id, nullable |
For conversation branching |
created_at |
timestamptz |
NOT NULL |
Indexes:
idx_messages_conversation_idonconversation_ididx_messages_conversation_createdon(conversation_id, created_at)idx_messages_parent_idonparent_id
Encryption Strategy
How It Works
- Extension: PostgreSQL
pgcryptoextension enabled ininit.sql - Key management:
@lilith/typeorm-pgcryptoPgCryptoModuleinjects encryption key as a PostgreSQL session variable (app.column_key) on each connection - Transparent:
@EncryptedColumn()decorator marks properties; the underlying subscriber handlespgp_sym_encrypt()on write andpgp_sym_decrypt()on read - Storage: Encrypted columns are
byteatype in PostgreSQL (binary) - Key source:
ENCRYPTION_KEYenvironment variable (32-byte hex)
Encrypted Columns Summary
| Entity | Property | DB Column | Content |
|---|---|---|---|
IncomeEntry |
amount |
amount_encrypted |
Monetary amounts |
IncomeEntry |
description |
description_encrypted |
Income descriptions |
MedicalEntry |
details |
details_encrypted |
Medical details |
Contact |
phone |
phone_encrypted |
Phone numbers |
Contact |
notes |
notes_encrypted |
Private notes |
Entity Code Pattern
import { Entity, Column } from 'typeorm';
import { BaseEntity } from '@lilith/typeorm-entities';
import { EncryptedColumn } from '@lilith/typeorm-pgcrypto';
@Entity('income_entries')
export class IncomeEntry extends BaseEntity {
@Column({ type: 'uuid' })
domainId: string;
@Column({ type: 'date' })
date: string;
@Column({ type: 'bytea', name: 'amount_encrypted' })
@EncryptedColumn()
amount: string;
@Column({ type: 'varchar', length: 3, default: 'GBP' })
currency: string;
@Column({ type: 'bytea', name: 'description_encrypted', nullable: true })
@EncryptedColumn({ nullable: true })
description: string;
}
Seed Data
Initial Domains (8)
INSERT INTO domains (name, type, slug, color, icon, description, is_active, sort_order) VALUES
('Christine''s Startup', 'client_work', 'christine', '#4A90D9', 'briefcase', 'Primary client contract', true, 0),
('Lilith Platform', 'software', 'lilith-platform', '#9B59B6', 'code', 'Internal platform development', true, 1),
('Side Projects', 'side_project', 'side-projects', '#2ECC71', 'flask', 'Experimental and personal projects', true, 2),
('Life Admin', 'life_admin', 'life', '#F39C12', 'home', 'Household, bills, errands', true, 3),
('Modeling', 'modeling', 'modeling', '#E91E63', 'camera', 'Modeling gigs and portfolio', true, 4),
('Escort', 'escort', 'escort', '#FF6B6B', 'heart', 'Escort work', true, 5),
('OnlyFans', 'content', 'onlyfans', '#00AFF0', 'film', 'OnlyFans content creation', true, 6),
('Physical Goals', 'physical', 'physical', '#1ABC9C', 'dumbbell', 'Fitness, body goals, health', true, 7);
Relationship Diagram
domains ─────────────┬──────────────────────────────────────────────
│ │ │
├── goals ├── tasks ├── habits │
│ │ │ │ │ │ │
│ └── goals │ ├── tasks │ └── habit_check_ins │
│ (children)│ │ (subtasks) │ │
│ │ └── time_blocks │
│ │ │
├── income_entries ├── billable_entries │
│ │ │ │
│ └── contacts │ │
│ │ │
├── measurements ├── medical_entries │
│ │ │
├── sprints ├── content_calendar │
│ │ │ │
│ └── tasks │ │
│ │
└── time_blocks ── daily_plans │
│
conversations ── messages │