337 lines
14 KiB
TypeScript
337 lines
14 KiB
TypeScript
// db/schema.ts
|
|
import {
|
|
bigint,
|
|
boolean,
|
|
doublePrecision,
|
|
integer,
|
|
json,
|
|
pgTable,
|
|
primaryKey,
|
|
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").notNull().references(() => users.id, {
|
|
onDelete: "cascade",
|
|
}),
|
|
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" }),
|
|
}, (table) => ({
|
|
// Composite primary key to support multiple sessions per user
|
|
pk: primaryKey({
|
|
name: "user_tokens_pkey",
|
|
columns: [table.user_id, table.state_id],
|
|
}),
|
|
}));
|
|
|
|
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),
|
|
approval_status: text("approval_status").default("pending_approval"), // 'pending_approval', 'approved', 'rejected'
|
|
approval_requested_at: bigint("approval_requested_at", { mode: "number" }), // Timestamp when tenant requested approval
|
|
approved_at: bigint("approved_at", { mode: "number" }), // Timestamp when landlord approved
|
|
approved_by: text("approved_by").references(() => users.id), // User ID of landlord who approved
|
|
rejection_reason: text("rejection_reason"), // Reason for rejection if rejected
|
|
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),
|
|
lease_id: text("lease_id").references(() => leases.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" }),
|
|
active_route: text("active_route"),
|
|
page_data: text("page_data"),
|
|
});
|