Add comprehensive platform administration panel
- 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>
This commit is contained in:
@@ -26,6 +26,9 @@ CREATE TABLE shared.organizations (
|
||||
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()
|
||||
);
|
||||
@@ -45,6 +48,7 @@ CREATE TABLE shared.users (
|
||||
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()
|
||||
);
|
||||
@@ -86,6 +90,28 @@ CREATE TABLE shared.cd_rates (
|
||||
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);
|
||||
@@ -95,3 +121,8 @@ 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);
|
||||
|
||||
52
db/migrations/006-admin-platform.sql
Normal file
52
db/migrations/006-admin-platform.sql
Normal file
@@ -0,0 +1,52 @@
|
||||
-- ============================================================
|
||||
-- Migration 006: Platform Administration Features
|
||||
-- Adds: is_platform_owner, subscription fields, login_history, ai_recommendation_log
|
||||
-- ============================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. Add is_platform_owner to users
|
||||
ALTER TABLE shared.users
|
||||
ADD COLUMN IF NOT EXISTS is_platform_owner BOOLEAN DEFAULT FALSE;
|
||||
|
||||
-- 2. Add subscription fields to organizations
|
||||
ALTER TABLE shared.organizations
|
||||
ADD COLUMN IF NOT EXISTS payment_date DATE,
|
||||
ADD COLUMN IF NOT EXISTS confirmation_number VARCHAR(100),
|
||||
ADD COLUMN IF NOT EXISTS renewal_date DATE;
|
||||
|
||||
-- 3. Create login_history table
|
||||
CREATE TABLE IF NOT EXISTS 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
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_login_history_org_time
|
||||
ON shared.login_history(organization_id, logged_in_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_login_history_user
|
||||
ON shared.login_history(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_login_history_time
|
||||
ON shared.login_history(logged_in_at DESC);
|
||||
|
||||
-- 4. Create ai_recommendation_log table
|
||||
CREATE TABLE IF NOT EXISTS 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()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_rec_log_org
|
||||
ON shared.ai_recommendation_log(organization_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_rec_log_time
|
||||
ON shared.ai_recommendation_log(requested_at DESC);
|
||||
|
||||
COMMIT;
|
||||
@@ -16,6 +16,31 @@
|
||||
-- Enable UUID generation
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- ============================================================
|
||||
-- 0. Create platform owner account (admin@hoaledgeriq.com)
|
||||
-- ============================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
v_platform_owner_id UUID;
|
||||
BEGIN
|
||||
SELECT id INTO v_platform_owner_id FROM shared.users WHERE email = 'admin@hoaledgeriq.com';
|
||||
IF v_platform_owner_id IS NULL THEN
|
||||
INSERT INTO shared.users (id, email, password_hash, first_name, last_name, is_superadmin, is_platform_owner)
|
||||
VALUES (
|
||||
uuid_generate_v4(),
|
||||
'admin@hoaledgeriq.com',
|
||||
-- bcrypt hash of platform owner password (cost 12)
|
||||
'$2b$12$QRJEJYsjy.24Va.57h13Te7UX7nMTN9hWhW19bwuCAkr1Dm0FWqrm',
|
||||
'Platform',
|
||||
'Admin',
|
||||
true,
|
||||
true
|
||||
) RETURNING id INTO v_platform_owner_id;
|
||||
END IF;
|
||||
-- Platform owner has NO org memberships — admin-only account
|
||||
RAISE NOTICE 'Platform Owner: admin@hoaledgeriq.com (id: %)', v_platform_owner_id;
|
||||
END $$;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. Create test user and organization
|
||||
-- ============================================================
|
||||
@@ -836,7 +861,42 @@ EXECUTE format('INSERT INTO %I.capital_projects (name, description, estimated_co
|
||||
(''Perimeter Fence Repair'', ''Replace damaged fence sections and repaint'', 8000, $1 + 4, 8, ''planned'', ''reserve'', 4)
|
||||
', v_schema) USING v_year;
|
||||
|
||||
-- Add subscription data to the organization
|
||||
UPDATE shared.organizations
|
||||
SET payment_date = (CURRENT_DATE - INTERVAL '15 days')::DATE,
|
||||
confirmation_number = 'PAY-2026-SVH-001',
|
||||
renewal_date = (CURRENT_DATE + INTERVAL '350 days')::DATE
|
||||
WHERE schema_name = v_schema;
|
||||
|
||||
-- ============================================================
|
||||
-- 13. Seed login_history for demo analytics
|
||||
-- ============================================================
|
||||
-- Admin user: regular logins over the past 30 days
|
||||
FOR v_month IN 0..29 LOOP
|
||||
INSERT INTO shared.login_history (user_id, organization_id, logged_in_at, ip_address, user_agent)
|
||||
VALUES (
|
||||
v_user_id,
|
||||
v_org_id,
|
||||
NOW() - (v_month || ' days')::INTERVAL - (random() * 8 || ' hours')::INTERVAL,
|
||||
'192.168.1.' || (10 + (random() * 50)::INT),
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'
|
||||
);
|
||||
END LOOP;
|
||||
|
||||
-- Viewer user: occasional logins (every 3-5 days)
|
||||
FOR v_month IN 0..9 LOOP
|
||||
INSERT INTO shared.login_history (user_id, organization_id, logged_in_at, ip_address, user_agent)
|
||||
VALUES (
|
||||
(SELECT id FROM shared.users WHERE email = 'viewer@sunrisevalley.org'),
|
||||
v_org_id,
|
||||
NOW() - ((v_month * 3) || ' days')::INTERVAL - (random() * 12 || ' hours')::INTERVAL,
|
||||
'10.0.0.' || (100 + (random() * 50)::INT),
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)'
|
||||
);
|
||||
END LOOP;
|
||||
|
||||
RAISE NOTICE 'Seed data created successfully for Sunrise Valley HOA!';
|
||||
RAISE NOTICE 'Platform Owner: admin@hoaledgeriq.com (SuperAdmin + Platform Owner)';
|
||||
RAISE NOTICE 'Admin Login: admin@sunrisevalley.org / password123 (SuperAdmin + President)';
|
||||
RAISE NOTICE 'Viewer Login: viewer@sunrisevalley.org / password123 (Homeowner)';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user