-- Auto-Approval Onboarding System Migration -- This migration adds the database schema for auto-approval rules and account status tracking -- Create auto-approval rules table CREATE TABLE IF NOT EXISTS auto_approval_rules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), rule_name VARCHAR(100) NOT NULL, email_domain VARCHAR(255), business_type VARCHAR(50), minimum_score INTEGER DEFAULT 0, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Add account status and approval tracking to organizations ALTER TABLE organizations ADD COLUMN IF NOT EXISTS account_status VARCHAR(50) DEFAULT 'pending_approval'; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS approval_score INTEGER DEFAULT 0; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS auto_approved BOOLEAN DEFAULT false; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS approved_at TIMESTAMP WITH TIME ZONE; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS approved_by UUID REFERENCES users(id); ALTER TABLE organizations ADD COLUMN IF NOT EXISTS approval_reason TEXT; -- Add Stripe onboarding tracking ALTER TABLE organizations ADD COLUMN IF NOT EXISTS stripe_onboarding_status VARCHAR(50) DEFAULT 'not_started'; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS stripe_onboarding_url TEXT; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS stripe_details_submitted BOOLEAN DEFAULT false; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS stripe_charges_enabled BOOLEAN DEFAULT false; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS stripe_payouts_enabled BOOLEAN DEFAULT false; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS onboarding_completed_at TIMESTAMP WITH TIME ZONE; -- Add business profile fields for better approval scoring ALTER TABLE organizations ADD COLUMN IF NOT EXISTS business_type VARCHAR(50); ALTER TABLE organizations ADD COLUMN IF NOT EXISTS business_description TEXT; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS website_url TEXT; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS phone_number VARCHAR(20); ALTER TABLE organizations ADD COLUMN IF NOT EXISTS address_line1 TEXT; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS address_line2 TEXT; ALTER TABLE organizations ADD COLUMN IF NOT EXISTS city VARCHAR(100); ALTER TABLE organizations ADD COLUMN IF NOT EXISTS state VARCHAR(50); ALTER TABLE organizations ADD COLUMN IF NOT EXISTS postal_code VARCHAR(20); ALTER TABLE organizations ADD COLUMN IF NOT EXISTS country VARCHAR(2) DEFAULT 'US'; -- Create organization onboarding progress table CREATE TABLE IF NOT EXISTS organization_onboarding_progress ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), organization_id UUID REFERENCES organizations(id) NOT NULL, step_key VARCHAR(100) NOT NULL, status VARCHAR(20) DEFAULT 'pending', completed_at TIMESTAMP WITH TIME ZONE, data JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(organization_id, step_key) ); -- Create audit log for approval actions CREATE TABLE IF NOT EXISTS approval_audit_log ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), organization_id UUID REFERENCES organizations(id) NOT NULL, action VARCHAR(50) NOT NULL, -- 'auto_approved', 'manually_approved', 'rejected' actor_id UUID REFERENCES users(id), -- NULL for auto-approval reason TEXT, previous_status VARCHAR(50), new_status VARCHAR(50), metadata JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Insert default auto-approval rules INSERT INTO auto_approval_rules (rule_name, email_domain, minimum_score, is_active) VALUES ('Trusted Educational Domains', 'edu', 80, true), ('Government Organizations', 'gov', 90, true), ('Known Venue Partners', 'eventbrite.com', 85, true), ('High Score Threshold', NULL, 95, true) ON CONFLICT DO NOTHING; -- Add indexes for performance CREATE INDEX IF NOT EXISTS idx_organizations_account_status ON organizations(account_status); CREATE INDEX IF NOT EXISTS idx_organizations_approval_score ON organizations(approval_score); CREATE INDEX IF NOT EXISTS idx_organizations_stripe_onboarding_status ON organizations(stripe_onboarding_status); CREATE INDEX IF NOT EXISTS idx_auto_approval_rules_active ON auto_approval_rules(is_active); CREATE INDEX IF NOT EXISTS idx_onboarding_progress_org_id ON organization_onboarding_progress(organization_id); CREATE INDEX IF NOT EXISTS idx_approval_audit_log_org_id ON approval_audit_log(organization_id); CREATE INDEX IF NOT EXISTS idx_approval_audit_log_created_at ON approval_audit_log(created_at); -- Create function to update updated_at timestamps CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; -- Add triggers for updated_at CREATE TRIGGER update_auto_approval_rules_updated_at BEFORE UPDATE ON auto_approval_rules FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_onboarding_progress_updated_at BEFORE UPDATE ON organization_onboarding_progress FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- RLS Policies for new tables ALTER TABLE auto_approval_rules ENABLE ROW LEVEL SECURITY; ALTER TABLE organization_onboarding_progress ENABLE ROW LEVEL SECURITY; ALTER TABLE approval_audit_log ENABLE ROW LEVEL SECURITY; -- Auto-approval rules: Only admins can access CREATE POLICY "Admins can manage auto-approval rules" ON auto_approval_rules FOR ALL USING (auth.uid() IN (SELECT id FROM users WHERE role = 'admin')); -- Onboarding progress: Users can access their own organization's progress CREATE POLICY "Users can access their organization's onboarding progress" ON organization_onboarding_progress FOR ALL USING ( organization_id IN ( SELECT organization_id FROM users WHERE id = auth.uid() ) ); -- Audit log: Users can read their own organization's audit log, admins can read all CREATE POLICY "Users can read their organization's audit log" ON approval_audit_log FOR SELECT USING ( organization_id IN ( SELECT organization_id FROM users WHERE id = auth.uid() ) OR auth.uid() IN (SELECT id FROM users WHERE role = 'admin') ); -- Only admins can write to audit log CREATE POLICY "Only admins can write to audit log" ON approval_audit_log FOR INSERT WITH CHECK (auth.uid() IN (SELECT id FROM users WHERE role = 'admin')); -- Create function to calculate approval score CREATE OR REPLACE FUNCTION calculate_approval_score(org_id UUID) RETURNS INTEGER AS $$ DECLARE score INTEGER DEFAULT 0; org_record RECORD; user_record RECORD; BEGIN -- Get organization and user data SELECT * INTO org_record FROM organizations WHERE id = org_id; SELECT * INTO user_record FROM users WHERE organization_id = org_id AND role = 'organizer' LIMIT 1; IF org_record IS NULL OR user_record IS NULL THEN RETURN 0; END IF; -- Base score for having an organization score := 10; -- Email domain scoring IF user_record.email LIKE '%.edu' THEN score := score + 40; ELSIF user_record.email LIKE '%.gov' THEN score := score + 50; ELSIF user_record.email LIKE '%.org' THEN score := score + 20; END IF; -- Business information completeness IF org_record.business_type IS NOT NULL THEN score := score + 10; END IF; IF org_record.business_description IS NOT NULL AND LENGTH(org_record.business_description) > 50 THEN score := score + 15; END IF; IF org_record.website_url IS NOT NULL THEN score := score + 10; END IF; IF org_record.phone_number IS NOT NULL THEN score := score + 5; END IF; IF org_record.address_line1 IS NOT NULL THEN score := score + 10; END IF; -- Update the organization's approval score UPDATE organizations SET approval_score = score WHERE id = org_id; RETURN score; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Create function to check if organization should be auto-approved CREATE OR REPLACE FUNCTION should_auto_approve(org_id UUID) RETURNS BOOLEAN AS $$ DECLARE score INTEGER; org_record RECORD; user_record RECORD; rule_record RECORD; BEGIN -- Calculate current score score := calculate_approval_score(org_id); -- Get organization and user data SELECT * INTO org_record FROM organizations WHERE id = org_id; SELECT * INTO user_record FROM users WHERE organization_id = org_id AND role = 'organizer' LIMIT 1; -- Check domain-specific rules FOR rule_record IN SELECT * FROM auto_approval_rules WHERE is_active = true AND email_domain IS NOT NULL LOOP IF user_record.email LIKE '%' || rule_record.email_domain THEN IF score >= rule_record.minimum_score THEN RETURN true; END IF; END IF; END LOOP; -- Check general score threshold rules FOR rule_record IN SELECT * FROM auto_approval_rules WHERE is_active = true AND email_domain IS NULL LOOP IF score >= rule_record.minimum_score THEN RETURN true; END IF; END LOOP; RETURN false; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Create function to process auto-approval CREATE OR REPLACE FUNCTION process_auto_approval(org_id UUID) RETURNS BOOLEAN AS $$ DECLARE should_approve BOOLEAN; current_status VARCHAR(50); BEGIN -- Get current status SELECT account_status INTO current_status FROM organizations WHERE id = org_id; -- Only process if currently pending approval IF current_status != 'pending_approval' THEN RETURN false; END IF; -- Check if should auto-approve should_approve := should_auto_approve(org_id); IF should_approve THEN -- Update organization status UPDATE organizations SET account_status = 'approved', auto_approved = true, approved_at = NOW(), approval_reason = 'Auto-approved based on scoring rules' WHERE id = org_id; -- Log the approval INSERT INTO approval_audit_log ( organization_id, action, actor_id, reason, previous_status, new_status, metadata ) VALUES ( org_id, 'auto_approved', NULL, 'Auto-approved based on scoring rules', 'pending_approval', 'approved', jsonb_build_object('approval_score', (SELECT approval_score FROM organizations WHERE id = org_id)) ); RETURN true; END IF; RETURN false; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Create trigger to process auto-approval when organization is created or updated CREATE OR REPLACE FUNCTION trigger_auto_approval_check() RETURNS TRIGGER AS $$ BEGIN -- Only process if account_status is pending_approval IF NEW.account_status = 'pending_approval' THEN PERFORM process_auto_approval(NEW.id); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -- Add trigger to organizations table CREATE TRIGGER check_auto_approval_on_update AFTER UPDATE ON organizations FOR EACH ROW EXECUTE FUNCTION trigger_auto_approval_check(); -- Create view for admin dashboard CREATE OR REPLACE VIEW admin_pending_approvals AS SELECT o.id, o.name, o.account_status, o.approval_score, o.business_type, o.business_description, o.created_at, u.email as owner_email, u.name as owner_name, calculate_approval_score(o.id) as current_score, should_auto_approve(o.id) as can_auto_approve FROM organizations o JOIN users u ON u.organization_id = o.id AND u.role = 'organizer' WHERE o.account_status = 'pending_approval' ORDER BY o.created_at DESC; -- Grant permissions to authenticated users GRANT SELECT ON admin_pending_approvals TO authenticated; GRANT EXECUTE ON FUNCTION calculate_approval_score TO authenticated; GRANT EXECUTE ON FUNCTION should_auto_approve TO authenticated; GRANT EXECUTE ON FUNCTION process_auto_approval TO authenticated;