Files
website/src/lib/server/db/schema/money-tracker.ts
Stan Runge 64fb8df262 db: Drizzle schemas on domain Postgres schemas, baseline migration, reorg SQL
- pgSchema tables for auth, money, house, ref, games, cs, animaldex, site, meta
  (lists excluded via schemaFilter; runtime list tables are managed by meta.*)
- drizzle/ baseline matches the reorged DB exactly (catalog diff = 0 on rehearsal)
- db/reorg: one-time SQL that moves the flat public schema (rehearsed on a prod copy)
- scripts/db-migrate.ts: migrator with --init to record the baseline and a guard
  against running the baseline on a non-empty, unrecorded database
2026-10-23 22:03:42 +00:00

183 lines
5.3 KiB
TypeScript

import { boolean, check, date, index, integer, numeric, text, unique } from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
import { moneySchema } from './schemas';
import { id, timestamps } from './helpers';
import { user } from './auth';
export const moneyAccounts = moneySchema.table(
'accounts',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
openingBalanceCents: integer('opening_balance_cents').notNull().default(0),
kind: text('kind').notNull().default('spendable'),
color: text('color'),
...timestamps
},
(table) => [
unique().on(table.userId, table.name),
index().on(table.userId),
check('accounts_kind_check', sql`kind IN ('spendable','investment')`)
]
);
export const moneyTransactions = moneySchema.table(
'transactions',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
accountId: integer('account_id')
.notNull()
.references(() => moneyAccounts.id),
date: date('date', { mode: 'string' }).notNull(),
description: text('description').notNull(),
amountCents: integer('amount_cents').notNull(),
...timestamps
},
(table) => [index().on(table.userId, table.date, table.id), index().on(table.accountId)]
);
export const moneyRecurring = moneySchema.table(
'recurring',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
amountCents: integer('amount_cents').notNull(),
cadence: text('cadence').notNull(),
startsOn: date('starts_on', { mode: 'string' }),
endsOn: date('ends_on', { mode: 'string' }),
accountId: integer('account_id').references(() => moneyAccounts.id, { onDelete: 'set null' }),
...timestamps
},
(table) => [
index().on(table.userId),
check('recurring_cadence_check', sql`cadence IN ('weekly','monthly','yearly')`)
]
);
export const moneyForecasts = moneySchema.table(
'forecasts',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
date: date('date', { mode: 'string' }).notNull(),
description: text('description').notNull(),
amountCents: integer('amount_cents').notNull(),
accountId: integer('account_id').references(() => moneyAccounts.id, { onDelete: 'set null' }),
...timestamps
},
(table) => [index().on(table.userId, table.date)]
);
export const moneyUtilitySettings = moneySchema.table(
'utility_settings',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
kind: text('kind').notNull(),
ratePerUnit: numeric('rate_per_unit', { precision: 12, scale: 4 }).notNull().default('0'),
monthlyAdvanceCents: integer('monthly_advance_cents').notNull().default(0),
billingStart: date('billing_start', { mode: 'string' }),
...timestamps
},
(table) => [
unique().on(table.userId, table.kind),
check('utility_settings_kind_check', sql`kind IN ('electricity','gas','water')`)
]
);
export const moneyUtilityReadings = moneySchema.table(
'utility_readings',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
kind: text('kind').notNull(),
date: date('date', { mode: 'string' }).notNull(),
reading: numeric('reading', { precision: 12, scale: 3 }).notNull(),
...timestamps
},
(table) => [
index().on(table.userId, table.kind, table.date),
check('utility_readings_kind_check', sql`kind IN ('electricity','gas','water')`)
]
);
export const moneyWishlist = moneySchema.table(
'wishlist',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
estimatedCents: integer('estimated_cents'),
priority: integer('priority').notNull().default(3),
notes: text('notes'),
...timestamps
},
(table) => [index().on(table.userId)]
);
export const moneyProjectionSettings = moneySchema.table(
'projection_settings',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
investmentAccountId: integer('investment_account_id').references(() => moneyAccounts.id, {
onDelete: 'set null'
}),
reserveCents: integer('reserve_cents').notNull().default(100000),
annualReturnPercent: numeric('annual_return_percent', { precision: 5, scale: 2 })
.notNull()
.default('5.00'),
contributionMode: text('contribution_mode').notNull().default('excess'),
includeRecurring: boolean('include_recurring').notNull().default(true),
fixedMonthlyCents: integer('fixed_monthly_cents').notNull().default(0),
endDate: date('end_date', { mode: 'string' }),
...timestamps
},
(table) => [
unique().on(table.userId),
check(
'projection_settings_contribution_mode_check',
sql`contribution_mode IN ('excess','fixed')`
)
]
);
export const moneyInsurances = moneySchema.table(
'insurances',
{
id: id(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
provider: text('provider'),
coverage: text('coverage'),
premiumCents: integer('premium_cents'),
cadence: text('cadence').notNull().default('monthly'),
...timestamps
},
(table) => [
index().on(table.userId),
check('insurances_cadence_check', sql`cadence IN ('weekly','monthly','yearly')`)
]
);