integratedhub/db/schema.ts
open-swe-dev-andrinoid[bot] b509f63932 Apply patch [skip ci]
2026-01-22 18:01:41 +00:00

397 lines
17 KiB
TypeScript
Executable File

// db/schema.ts
import {
bigint,
boolean,
doublePrecision,
integer,
json,
pgTable,
text,
varchar,
} from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: text("id").primaryKey(), // OAuth2 user ID
name: text("name"),
email: text("email"),
phone: text("phone"),
address_1: text("address_1"),
address_2: text("address_2"),
city: text("city"),
state: text("state"),
zip: text("zip"),
country: text("country"),
user_type: text("user_type"), // 'landlord', 'tenant', 'admin'
onboarded_landlord: boolean("onboarded_landlord").default(false),
onboarded_tenant: boolean("onboarded_tenant").default(false),
email_verified: boolean("email_verified").default(false),
banking_setup_complete: boolean("banking_setup_complete").default(false),
notification_preferences_json: json("notification_preferences_json"),
payment_preferences_json: json("payment_preferences_json"),
onboarding_step: integer("onboarding_step").default(0), // Track onboarding progress
encrypted_data: text("encrypted_data"), // Encrypted personal information
started_on: bigint("started_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
export const roles = pgTable("roles", {
id: text("id").primaryKey(), // e.g., 'admin', 'guest', 'client', 'owner'
description: text("description"),
});
export const userRoles = pgTable("user_roles", {
id: text("id").primaryKey(),
userId: text("user_id").references(() => users.id),
roleId: text("role_id").references(() => roles.id),
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
export const stateKeys = pgTable("state_keys", {
seal_key: varchar("seal_key", { length: 32 }).primaryKey(),
state_id: varchar("state_id", { length: 36 }).notNull(),
cookie_hash: varchar("cookie_hash", { length: 64 }).notNull(),
created_on: bigint("created_on", { mode: "number" }).notNull(),
});
export const userTokens = pgTable("user_tokens", {
user_id: text("user_id").primaryKey().references(() => users.id),
state_id: text("state_id").notNull(),
access_token: text("access_token").notNull(),
refresh_token: text("refresh_token").notNull(),
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
export const webTokens = pgTable("web_tokens", {
token_id: varchar("token_id", { length: 64 }).primaryKey(),
state_id: varchar("state_id", { length: 36 }).notNull(),
user_id: text("user_id").notNull().references(() => users.id),
token_type: text("token_type").notNull(),
token_value: text("token_value").notNull(),
expires_on: bigint("expires_on", { mode: "number" }).notNull(),
created_on: bigint("created_on", { mode: "number" }).notNull(),
updated_on: bigint("updated_on", { mode: "number" }).notNull(),
last_used_on: bigint("last_used_on", { mode: "number" }),
is_active: boolean("is_active").default(true),
metadata: json("metadata"),
});
// Obligations table
export const obligations = pgTable("obligations", {
id: text("id").primaryKey(),
user_id: text("user_id").references(() => users.id),
description: text("description"),
amount: doublePrecision("amount"),
frequency: text("frequency"), // e.g., 'monthly', 'yearly'
due_date: bigint("due_date", { mode: "number" }), // unix timestamp
encrypted_data: text("encrypted_data"), // Encrypted financial information
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
// Incomes table
export const incomes = pgTable("incomes", {
id: text("id").primaryKey(),
user_id: text("user_id").references(() => users.id),
source: text("source"),
amount: doublePrecision("amount"),
frequency: text("frequency"), // e.g., 'monthly', 'yearly'
received_date: bigint("received_date", { mode: "number" }), // unix timestamp
encrypted_data: text("encrypted_data"), // Encrypted financial information
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
// Financial balances table - stores balance-form data from FinancialCalendar
export const financial_balances = pgTable("financial_balances", {
id: text("id").primaryKey(),
user_id: text("user_id").references(() => users.id),
balance: doublePrecision("balance"), // balance amount
balance_date: bigint("balance_date", { mode: "number" }), // date when balance was set (YYYY-MM-DD format)
encrypted_data: text("encrypted_data"), // Encrypted financial information
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
export const properties = pgTable("properties", {
id: text("id").primaryKey(),
title: text("title"),
description: text("description"),
address_1: text("address_1"),
address_2: text("address_2"),
city: text("city"),
state: text("state"),
zip: text("zip"),
country: text("country"),
bedrooms: integer("bedrooms"),
bathrooms: integer("bathrooms"),
square_footage: integer("square_footage"),
lot_size: integer("lot_size"),
year_built: integer("year_built"),
price: doublePrecision("price"),
property_type: text("property_type"),
listing_status: text("listing_status"),
rental_status: text("rental_status"), // 'available', 'occupied', 'maintenance', 'draft'
monthly_rent: doublePrecision("monthly_rent"),
security_deposit: doublePrecision("security_deposit"),
default_payment_method_id: text("default_payment_method_id").references(() =>
payment_methods.id
),
lease_duration_months: integer("lease_duration_months"),
amenities: text("amenities"), // JSON string of amenities
property_photos_count: integer("property_photos_count").default(0),
image_url: text("image_url"),
encrypted_data: text("encrypted_data"), // Encrypted property information
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
owner_id: text("owner_id").references(() => users.id),
});
// Rent Schedules & Rules
export const rent_schedules = pgTable("rent_schedules", {
id: text("id").primaryKey(),
property_id: text("property_id").references(() => properties.id),
rent_amount: doublePrecision("rent_amount"),
due_day: integer("due_day"), // Day of month rent is due (1-31)
late_fee_amount: doublePrecision("late_fee_amount"),
grace_period_days: integer("grace_period_days").default(0),
payment_methods_allowed: text("payment_methods_allowed"), // JSON array of allowed methods
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
export const leases = pgTable("leases", {
id: text("id").primaryKey(),
title: text("title"),
property_id: text("property_id").references(() => properties.id),
property_address: text("property_address"),
tenant_id: text("tenant_id").references(() => users.id),
owner_id: text("owner_id").references(() => users.id),
start_date: bigint("start_date", { mode: "number" }),
end_date: bigint("end_date", { mode: "number" }),
rent: doublePrecision("rent"),
security_deposit: doublePrecision("security_deposit"),
default_payment_method_id: text("default_payment_method_id").references(() =>
payment_methods.id
),
lease_status: text("lease_status"),
lease_document_url: text("lease_document_url"),
payment_schedule_id: text("payment_schedule_id").references(() =>
rent_schedules.id
),
late_fee_rules: text("late_fee_rules"), // JSON string of late fee rules
auto_renewal: boolean("auto_renewal").default(false),
encrypted_data: text("encrypted_data"), // Encrypted lease information
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
// Payment Methods & Banking
export const payment_methods = pgTable("payment_methods", {
id: text("id").primaryKey(),
user_id: text("user_id").references(() => users.id),
method_type: text("method_type"), // 'bank_account', 'credit_card'
bank_name: text("bank_name"),
account_type: text("account_type"), // 'checking', 'savings'
routing_number_encrypted: text("routing_number_encrypted"),
account_number_encrypted: text("account_number_encrypted"),
is_default: boolean("is_default").default(false),
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
// Property Photos & Documents
export const property_media = pgTable("property_media", {
id: text("id").primaryKey(),
property_id: text("property_id").references(() => properties.id),
media_type: text("media_type"), // 'photo', 'video', 'document'
file_url: text("file_url"),
file_name: text("file_name"),
display_order: integer("display_order").default(0),
created_on: bigint("created_on", { mode: "number" }),
});
// Lease Documents & Templates
export const lease_documents = pgTable("lease_documents", {
id: text("id").primaryKey(),
lease_id: text("lease_id").references(() => leases.id),
document_type: text("document_type"), // 'lease', 'addendum', 'receipt'
file_url: text("file_url"),
file_name: text("file_name"),
signed_by_landlord: boolean("signed_by_landlord").default(false),
signed_by_tenant: boolean("signed_by_tenant").default(false),
signature_date: bigint("signature_date", { mode: "number" }),
created_on: bigint("created_on", { mode: "number" }),
});
// Tenant Applications
export const tenant_applications = pgTable("tenant_applications", {
id: text("id").primaryKey(),
property_id: text("property_id").references(() => properties.id),
applicant_id: text("applicant_id").references(() => users.id),
application_status: text("application_status"), // 'pending', 'approved', 'rejected'
application_data_encrypted: text("application_data_encrypted"), // Encrypted application details
document_urls_json: json("document_urls_json"), // JSON array of document URLs
submitted_on: bigint("submitted_on", { mode: "number" }),
reviewed_on: bigint("reviewed_on", { mode: "number" }),
});
export const tenant_invites = pgTable("tenant_invites", {
id: text("id").primaryKey(),
landlord_id: text("landlord_id").references(() => users.id),
property_id: text("property_id").references(() => properties.id),
available_on: bigint("available_on", { mode: "number" }),
invited_emails: text("invited_emails"), // JSON array of invited emails
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
// Payment Transactions
export const payment_transactions = pgTable("payment_transactions", {
id: text("id").primaryKey(),
lease_id: text("lease_id").references(() => leases.id),
amount: doublePrecision("amount"),
payment_date: bigint("payment_date", { mode: "number" }),
payment_method: text("payment_method"), // 'card', 'ach', 'check', 'cash'
transaction_status: text("transaction_status"), // 'pending', 'completed', 'failed'
stripe_payment_id: text("stripe_payment_id"), // External payment processor ID
created_on: bigint("created_on", { mode: "number" }),
});
// Reminders
export const reminders = pgTable("reminders", {
id: text("id").primaryKey(),
user_id: text("user_id").references(() => users.id).notNull(),
lease_id: text("lease_id").references(() => leases.id),
reminder_type: text("reminder_type").notNull(), // 'rent_payment', 'maintenance_followup', 'custom', 'document_expiration'
title: text("title").notNull(),
description: text("description"),
due_date: bigint("due_date", { mode: "number" }).notNull(),
is_completed: boolean("is_completed").default(false),
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});
// Communication/Messages
export const messages = pgTable("messages", {
id: text("id").primaryKey(),
lease_id: text("lease_id").references(() => leases.id),
sender_id: text("sender_id").references(() => users.id),
recipient_id: text("recipient_id").references(() => users.id),
message_type: text("message_type"), // 'general', 'maintenance', 'payment', 'notice'
subject: text("subject"),
parent_message_id: text("parent_message_id"), // For threading
attachments_json: json("attachments_json"), // JSON array of file URLs/metadata
content: text("content"), // Encrypted message content
content_encrypted: text("content_encrypted"), // Encrypted message content
is_read: boolean("is_read").default(false),
created_on: bigint("created_on", { mode: "number" }),
});
// Maintenance Requests
export const maintenance_requests = pgTable("maintenance_requests", {
id: text("id").primaryKey(),
property_id: text("property_id").references(() => properties.id),
tenant_id: text("tenant_id").references(() => users.id),
request_type: text("request_type"), // 'plumbing', 'electrical', 'hvac', 'general'
description: text("description"), // Encrypted request details
status: text("status"), // 'open', 'in_progress', 'completed', 'cancelled'
priority: text("priority"), // 'low', 'medium', 'high', 'urgent'
created_on: bigint("created_on", { mode: "number" }),
resolved_on: bigint("resolved_on", { mode: "number" }),
});
// User Pins
export const user_pins = pgTable("user_pins", {
user_id: text("user_id").primaryKey().references(() => users.id),
pin: text("pin").notNull(), // Encrypted pin/password
created_on: bigint("created_on", { mode: "number" }).notNull(),
modified_on: bigint("modified_on", { mode: "number" }).notNull(),
});
// Session Config table
export const session_configs = pgTable("session_configs", {
device_id: text("device_id").primaryKey(),
user_id: text("user_id").references(() => users.id),
other_data: text("other_data"),
active_date: bigint("active_date", { mode: "number" }),
quiz_configs: json("quiz_configs"), // Array<Array<string>>
active_route: text("active_route"),
page_data: text("page_data"),
active_quiz_index: integer("active_quiz_index"),
active_question_index: integer("active_question_index"),
});
// Quiz Config table
export const quiz_configs = pgTable("quiz_configs", {
quiz_config_id: text("quiz_config_id").primaryKey(),
quiz_name: text("quiz_name"),
owner_id: text("owner_id").references(() => users.id),
quiz_mode: text("quiz_mode"),
active_date: bigint("active_date", { mode: "number" }),
tab_name: text("tab_name"),
tab_index: integer("tab_index").default(0),
list_index: integer("list_index").default(0),
questions: json("questions"), // Array of question objects
results: json("results"), // Array of result objects
});
// Quiz Questions table (normalized for better querying)
export const quiz_questions = pgTable("quiz_questions", {
id: text("id").primaryKey(),
quiz_id: text("quiz_id").references(() => quiz_configs.quiz_config_id),
owner_id: text("owner_id").references(() => users.id),
question_index: integer("question_index").default(0),
language_1: text("language_1"),
language_2: text("language_2"),
text_1: text("text_1"),
audio_file_1: text("audio_file_1"),
text_2: text("text_2"),
audio_file_2: text("audio_file_2"),
order_num: integer("order_num"),
background_file: text("background_file"),
background_image_data: text("background_image_data"),
video_file: text("video_file"),
video_file_data: text("video_file_data"),
});
// Quiz Results table (normalized for better querying)
export const quiz_results = pgTable("quiz_results", {
id: text("id").primaryKey(),
quiz_config_id: text("quiz_config_id").references(() =>
quiz_configs.quiz_config_id
),
user_id: text("user_id").references(() => users.id),
success: boolean("success"),
total_time: bigint("total_time", { mode: "number" }),
help_count: integer("help_count"),
completed_on: bigint("completed_on", { mode: "number" }),
});
// Brain Question Progress table (for spaced repetition learning with mode progression)
export const brain_question_progress = pgTable("brain_question_progress", {
id: text("id").primaryKey(), // composite: `${user_id}_${question_id}` (mode-agnostic)
user_id: text("user_id").references(() => users.id).notNull(),
quiz_id: text("quiz_id").references(() => quiz_configs.quiz_config_id),
question_id: text("question_id").notNull(),
current_mode: text("current_mode").notNull().default("photo-see-bisaya"), // Current mode in progression
mode_index: integer("mode_index").default(0), // Index in QUIZ_MODES array (0-5)
attempts: integer("attempts").default(0),
first_try_successes: integer("first_try_successes").default(0),
consecutive_first_try: integer("consecutive_first_try").default(0), // Streak of first-try correct
last_result: boolean("last_result"), // true = correct on first try
next_due_at: bigint("next_due_at", { mode: "number" }), // timestamp for spaced repetition
interval_days: integer("interval_days").default(1), // current spacing interval
ease_factor: doublePrecision("ease_factor").default(2.5), // SM-2 algorithm factor
is_review_only: boolean("is_review_only").default(false), // Card has mastered all modes
created_on: bigint("created_on", { mode: "number" }),
updated_on: bigint("updated_on", { mode: "number" }),
});