- Database: Add login_history, ai_recommendation_log tables; is_platform_owner column on users; subscription fields on organizations (payment_date, confirmation_number, renewal_date) - Backend: New AdminAnalyticsService with platform metrics, tenant detail, and health score calculations (0-100 based on activity, budget, transactions, members, AI usage) - Backend: Login/org-switch now records to login_history; AI recommendations logged to ai_recommendation_log; platform owner protected from superadmin toggle - Frontend: 4-tab admin panel (Dashboard, Organizations, Users, Tenant Health) with tenant detail drawer, subscription management, health scoring visualization - Platform owner account (admin@hoaledgeriq.com) auto-redirects to admin panel - Seed data includes platform owner account and sample login history Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
129 lines
5.1 KiB
SQL
129 lines
5.1 KiB
SQL
-- HOA LedgerIQ - Database Initialization
|
|
-- Creates shared schema and base tables for multi-tenant architecture
|
|
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
-- Shared schema for cross-tenant data
|
|
CREATE SCHEMA IF NOT EXISTS shared;
|
|
|
|
-- Organizations (HOAs)
|
|
CREATE TABLE shared.organizations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name VARCHAR(255) NOT NULL,
|
|
schema_name VARCHAR(63) NOT NULL UNIQUE,
|
|
subdomain VARCHAR(63) UNIQUE,
|
|
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'trial', 'archived')),
|
|
settings JSONB DEFAULT '{}',
|
|
contract_number VARCHAR(100),
|
|
plan_level VARCHAR(50) DEFAULT 'standard' CHECK (plan_level IN ('standard', 'premium', 'enterprise')),
|
|
address_line1 VARCHAR(255),
|
|
address_line2 VARCHAR(255),
|
|
city VARCHAR(100),
|
|
state VARCHAR(2),
|
|
zip_code VARCHAR(10),
|
|
phone VARCHAR(20),
|
|
email VARCHAR(255),
|
|
tax_id VARCHAR(20),
|
|
fiscal_year_start_month INTEGER DEFAULT 1 CHECK (fiscal_year_start_month BETWEEN 1 AND 12),
|
|
payment_date DATE,
|
|
confirmation_number VARCHAR(100),
|
|
renewal_date DATE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Users (global, cross-tenant)
|
|
CREATE TABLE shared.users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
email VARCHAR(255) NOT NULL UNIQUE,
|
|
password_hash VARCHAR(255),
|
|
first_name VARCHAR(100),
|
|
last_name VARCHAR(100),
|
|
phone VARCHAR(20),
|
|
is_email_verified BOOLEAN DEFAULT FALSE,
|
|
mfa_enabled BOOLEAN DEFAULT FALSE,
|
|
mfa_secret VARCHAR(255),
|
|
oauth_provider VARCHAR(50),
|
|
oauth_provider_id VARCHAR(255),
|
|
last_login_at TIMESTAMPTZ,
|
|
is_superadmin BOOLEAN DEFAULT FALSE,
|
|
is_platform_owner BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- User-Organization memberships with roles
|
|
CREATE TABLE shared.user_organizations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES shared.users(id) ON DELETE CASCADE,
|
|
organization_id UUID NOT NULL REFERENCES shared.organizations(id) ON DELETE CASCADE,
|
|
role VARCHAR(50) NOT NULL CHECK (role IN ('president', 'treasurer', 'secretary', 'member_at_large', 'manager', 'homeowner', 'admin', 'viewer')),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
joined_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(user_id, organization_id)
|
|
);
|
|
|
|
-- Invitations
|
|
CREATE TABLE shared.invitations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
organization_id UUID NOT NULL REFERENCES shared.organizations(id) ON DELETE CASCADE,
|
|
email VARCHAR(255) NOT NULL,
|
|
role VARCHAR(50) NOT NULL,
|
|
invited_by UUID NOT NULL REFERENCES shared.users(id),
|
|
token VARCHAR(255) NOT NULL UNIQUE,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
accepted_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- CD Rates (cross-tenant market data for investment recommendations)
|
|
CREATE TABLE shared.cd_rates (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
bank_name VARCHAR(255) NOT NULL,
|
|
apy DECIMAL(6,4) NOT NULL,
|
|
min_deposit DECIMAL(15,2),
|
|
term VARCHAR(100) NOT NULL,
|
|
term_months INTEGER,
|
|
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
source_url VARCHAR(500),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Login history (track logins/org-switches for platform analytics)
|
|
CREATE TABLE shared.login_history (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES shared.users(id) ON DELETE CASCADE,
|
|
organization_id UUID REFERENCES shared.organizations(id) ON DELETE SET NULL,
|
|
logged_in_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
ip_address VARCHAR(45),
|
|
user_agent TEXT
|
|
);
|
|
|
|
-- AI recommendation log (track AI usage per tenant)
|
|
CREATE TABLE shared.ai_recommendation_log (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_schema VARCHAR(63),
|
|
organization_id UUID REFERENCES shared.organizations(id) ON DELETE SET NULL,
|
|
user_id UUID REFERENCES shared.users(id) ON DELETE SET NULL,
|
|
recommendation_count INTEGER,
|
|
response_time_ms INTEGER,
|
|
status VARCHAR(20),
|
|
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_user_orgs_user ON shared.user_organizations(user_id);
|
|
CREATE INDEX idx_user_orgs_org ON shared.user_organizations(organization_id);
|
|
CREATE INDEX idx_users_email ON shared.users(email);
|
|
CREATE INDEX idx_orgs_schema ON shared.organizations(schema_name);
|
|
CREATE INDEX idx_invitations_token ON shared.invitations(token);
|
|
CREATE INDEX idx_invitations_email ON shared.invitations(email);
|
|
CREATE INDEX idx_cd_rates_fetched ON shared.cd_rates(fetched_at DESC);
|
|
CREATE INDEX idx_cd_rates_apy ON shared.cd_rates(apy DESC);
|
|
CREATE INDEX idx_login_history_org_time ON shared.login_history(organization_id, logged_in_at DESC);
|
|
CREATE INDEX idx_login_history_user ON shared.login_history(user_id);
|
|
CREATE INDEX idx_login_history_time ON shared.login_history(logged_in_at DESC);
|
|
CREATE INDEX idx_ai_rec_log_org ON shared.ai_recommendation_log(organization_id);
|
|
CREATE INDEX idx_ai_rec_log_time ON shared.ai_recommendation_log(requested_at DESC);
|