Initial commit - Black Canyon Tickets whitelabel platform

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-08 12:31:31 -06:00
commit 997c129383
139 changed files with 60476 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
-- Create premium add-ons system for BCT platform
-- This allows monetizing features like seating maps, AI descriptions, etc.
-- Create add_on_types table for available premium features
CREATE TABLE add_on_types (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
slug TEXT UNIQUE NOT NULL, -- e.g., 'seating-maps', 'ai-description'
name TEXT NOT NULL,
description TEXT NOT NULL,
pricing_type VARCHAR(20) NOT NULL DEFAULT 'per_event', -- 'per_event', 'monthly', 'annual', 'per_ticket'
price_cents INTEGER NOT NULL, -- Price in cents
category VARCHAR(50) NOT NULL DEFAULT 'feature', -- 'feature', 'service', 'analytics', 'marketing'
is_active BOOLEAN DEFAULT true,
requires_setup BOOLEAN DEFAULT false, -- Whether add-on needs admin setup
auto_enable_conditions JSONB, -- Conditions for auto-enabling (e.g., event size)
feature_flags JSONB, -- What features this unlocks
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create event_add_ons table to track purchased add-ons per event
CREATE TABLE event_add_ons (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
event_id UUID REFERENCES events(id) ON DELETE CASCADE NOT NULL,
add_on_type_id UUID REFERENCES add_on_types(id) NOT NULL,
organization_id UUID REFERENCES organizations(id) NOT NULL,
purchase_price_cents INTEGER NOT NULL, -- Price paid (may differ from current price)
status VARCHAR(20) DEFAULT 'active', -- 'active', 'cancelled', 'expired'
purchased_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE, -- For time-limited add-ons
metadata JSONB, -- Add-on specific configuration
stripe_payment_intent_id TEXT, -- Link to Stripe payment
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create organization_subscriptions for monthly/annual add-ons
CREATE TABLE organization_subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE NOT NULL,
add_on_type_id UUID REFERENCES add_on_types(id) NOT NULL,
status VARCHAR(20) DEFAULT 'active', -- 'active', 'cancelled', 'expired', 'past_due'
current_period_start TIMESTAMP WITH TIME ZONE NOT NULL,
current_period_end TIMESTAMP WITH TIME ZONE NOT NULL,
stripe_subscription_id TEXT,
stripe_customer_id TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Insert default premium add-ons
INSERT INTO add_on_types (slug, name, description, pricing_type, price_cents, category, requires_setup, feature_flags, sort_order) VALUES
-- Event Setup & Management (Automated Features - Low Cost)
('ai-event-description', 'AI Event Description', 'Professional AI-generated event descriptions optimized for your venue and audience', 'per_event', 500, 'service', false, '{"ai_description": true}', 1),
('premium-setup-service', 'Premium Setup Service', 'Dedicated onboarding specialist helps create and optimize your event', 'per_event', 5000, 'service', true, '{"priority_support": true, "setup_assistance": true}', 2),
('custom-event-branding', 'Custom Event Branding', 'Custom colors, styling, and logo integration for your ticket pages', 'per_event', 1000, 'feature', false, '{"custom_branding": true}', 3),
-- Advanced Features (Automated - Low Cost)
('seating-maps', 'Visual Seating Management', 'Interactive venue maps with seat selection, table assignments, and VIP sections', 'per_event', 1500, 'feature', false, '{"seating_maps": true, "seat_selection": true}', 4),
('guest-list-pro', 'Guest List Pro', 'Advanced attendee management with check-in app, VIP flagging, and notes', 'per_event', 1000, 'feature', false, '{"advanced_guest_management": true, "checkin_app": true}', 5),
('premium-analytics', 'Premium Analytics', 'Advanced sales forecasting, customer insights, and marketing performance tracking', 'per_event', 1000, 'analytics', false, '{"advanced_analytics": true, "forecasting": true, "demographics": true}', 6),
('ticket-scanner', 'Professional Ticket Scanner', 'Advanced QR code scanning with offline support, guest check-in tracking, and real-time reports', 'per_event', 500, 'feature', false, '{"ticket_scanner": true, "offline_scanning": true, "checkin_reports": true}', 7),
-- Marketing & Promotion (Mostly Automated - Moderate Cost)
('email-marketing-suite', 'Email Marketing Suite', 'Professional email templates, automated sequences, and post-event follow-up', 'per_event', 2000, 'marketing', false, '{"email_marketing": true, "automated_sequences": true}', 8),
('social-media-package', 'Social Media Package', 'Auto-generated posts, Instagram templates, and Facebook event integration', 'per_event', 1500, 'marketing', false, '{"social_media_tools": true, "auto_posts": true}', 9),
-- White-Glove Services
('concierge-management', 'Concierge Event Management', 'Dedicated event manager with day-of coordination and real-time support', 'per_event', 50000, 'service', true, '{"dedicated_manager": true, "day_of_support": true}', 10),
('premium-support', 'Premium Customer Support', 'Priority phone/chat support with dedicated account manager', 'per_event', 20000, 'service', false, '{"priority_support": true, "dedicated_manager": true}', 11),
-- Subscriptions
('bct-pro-monthly', 'BCT Pro Monthly', 'All premium features included for unlimited events', 'monthly', 19900, 'subscription', false, '{"all_features": true, "unlimited_events": true}', 12),
('enterprise-package', 'Enterprise Package', 'Multi-venue management, white-label options, and custom development', 'monthly', 99900, 'subscription', true, '{"multi_venue": true, "white_label": true, "api_access": true}', 13);
-- Function to check if organization has specific add-on for event
CREATE OR REPLACE FUNCTION has_event_addon(p_event_id UUID, p_addon_slug TEXT)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM event_add_ons ea
JOIN add_on_types at ON ea.add_on_type_id = at.id
WHERE ea.event_id = p_event_id
AND at.slug = p_addon_slug
AND ea.status = 'active'
AND (ea.expires_at IS NULL OR ea.expires_at > NOW())
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to check if organization has subscription add-on
CREATE OR REPLACE FUNCTION has_subscription_addon(p_organization_id UUID, p_addon_slug TEXT)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM organization_subscriptions os
JOIN add_on_types at ON os.add_on_type_id = at.id
WHERE os.organization_id = p_organization_id
AND at.slug = p_addon_slug
AND os.status = 'active'
AND os.current_period_end > NOW()
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to check if organization/event has any add-on with specific feature flag
CREATE OR REPLACE FUNCTION has_feature_access(p_organization_id UUID, p_event_id UUID, p_feature_flag TEXT)
RETURNS BOOLEAN AS $$
BEGIN
-- Check subscription add-ons
IF EXISTS (
SELECT 1
FROM organization_subscriptions os
JOIN add_on_types at ON os.add_on_type_id = at.id
WHERE os.organization_id = p_organization_id
AND os.status = 'active'
AND os.current_period_end > NOW()
AND at.feature_flags ? p_feature_flag
) THEN
RETURN TRUE;
END IF;
-- Check event-specific add-ons
IF p_event_id IS NOT NULL AND EXISTS (
SELECT 1
FROM event_add_ons ea
JOIN add_on_types at ON ea.add_on_type_id = at.id
WHERE ea.event_id = p_event_id
AND ea.status = 'active'
AND (ea.expires_at IS NULL OR ea.expires_at > NOW())
AND at.feature_flags ? p_feature_flag
) THEN
RETURN TRUE;
END IF;
RETURN FALSE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to get available add-ons for organization/event
CREATE OR REPLACE FUNCTION get_available_addons(p_organization_id UUID, p_event_id UUID DEFAULT NULL)
RETURNS TABLE(
addon_id UUID,
slug TEXT,
name TEXT,
description TEXT,
pricing_type TEXT,
price_cents INTEGER,
category TEXT,
has_access BOOLEAN,
purchased_at TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
RETURN QUERY
SELECT
at.id as addon_id,
at.slug,
at.name,
at.description,
at.pricing_type,
at.price_cents,
at.category,
CASE
WHEN has_subscription_addon(p_organization_id, at.slug) THEN TRUE
WHEN p_event_id IS NOT NULL AND has_event_addon(p_event_id, at.slug) THEN TRUE
ELSE FALSE
END as has_access,
COALESCE(
(SELECT ea.purchased_at FROM event_add_ons ea WHERE ea.event_id = p_event_id AND ea.add_on_type_id = at.id AND ea.status = 'active' LIMIT 1),
(SELECT os.created_at FROM organization_subscriptions os WHERE os.organization_id = p_organization_id AND os.add_on_type_id = at.id AND os.status = 'active' LIMIT 1)
) as purchased_at
FROM add_on_types at
WHERE at.is_active = true
ORDER BY at.sort_order, at.name;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Enable RLS
ALTER TABLE add_on_types ENABLE ROW LEVEL SECURITY;
ALTER TABLE event_add_ons ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_subscriptions ENABLE ROW LEVEL SECURITY;
-- RLS Policies for add_on_types (everyone can read active add-ons)
CREATE POLICY "Anyone can view active add-on types" ON add_on_types
FOR SELECT USING (is_active = true);
-- Admins can manage add-on types
CREATE POLICY "Admins can manage add-on types" ON add_on_types
FOR ALL USING (auth.uid() IN (SELECT id FROM users WHERE role = 'admin'));
-- RLS Policies for event_add_ons
CREATE POLICY "Users can view their organization's event add-ons" ON event_add_ons
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')
);
CREATE POLICY "Users can purchase add-ons for their events" ON event_add_ons
FOR INSERT WITH CHECK (
event_id IN (
SELECT id FROM events WHERE created_by = auth.uid()
)
);
-- RLS Policies for organization_subscriptions
CREATE POLICY "Users can view their organization's subscriptions" ON organization_subscriptions
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')
);
-- Indexes for performance
CREATE INDEX idx_add_on_types_slug ON add_on_types(slug);
CREATE INDEX idx_add_on_types_active ON add_on_types(is_active);
CREATE INDEX idx_add_on_types_category ON add_on_types(category);
CREATE INDEX idx_event_add_ons_event_id ON event_add_ons(event_id);
CREATE INDEX idx_event_add_ons_organization_id ON event_add_ons(organization_id);
CREATE INDEX idx_event_add_ons_status ON event_add_ons(status);
CREATE INDEX idx_organization_subscriptions_org_id ON organization_subscriptions(organization_id);
CREATE INDEX idx_organization_subscriptions_status ON organization_subscriptions(status);
-- Audit logging for add-on purchases
CREATE OR REPLACE FUNCTION log_addon_purchase()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_logs (user_id, action, resource_type, resource_id, new_values)
VALUES (
auth.uid(),
'purchase',
'event_add_on',
NEW.id,
row_to_json(NEW)
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER event_add_ons_audit_trigger
AFTER INSERT ON event_add_ons
FOR EACH ROW EXECUTE FUNCTION log_addon_purchase();
-- Add add-on revenue tracking to platform_stats view
DROP VIEW IF EXISTS platform_stats;
CREATE VIEW platform_stats AS
SELECT
(SELECT COUNT(*) FROM users WHERE role = 'organizer' AND is_active = true) as active_organizers,
(SELECT COUNT(*) FROM users WHERE role = 'admin') as admin_users,
(SELECT COUNT(*) FROM organizations) as total_organizations,
(SELECT COUNT(*) FROM events) as total_events,
(SELECT COUNT(*) FROM events WHERE start_time >= NOW()) as upcoming_events,
(SELECT COUNT(*) FROM tickets) as total_tickets_sold,
(SELECT COALESCE(SUM(price), 0) FROM tickets) as total_revenue,
(SELECT COALESCE(SUM(platform_fee_charged), 0) FROM tickets) as total_platform_fees,
(SELECT COALESCE(SUM(purchase_price_cents), 0) FROM event_add_ons WHERE status = 'active') as total_addon_revenue,
(SELECT COUNT(*) FROM event_add_ons WHERE status = 'active') as active_addons,
(SELECT COUNT(*) FROM organization_subscriptions WHERE status = 'active') as active_subscriptions,
(SELECT COUNT(DISTINCT DATE(created_at)) FROM tickets WHERE created_at >= NOW() - INTERVAL '30 days') as active_days_last_30,
(SELECT COUNT(*) FROM users WHERE created_at >= NOW() - INTERVAL '7 days') as new_users_last_7_days;
-- Comments for documentation
COMMENT ON TABLE add_on_types IS 'Available premium add-ons and their pricing';
COMMENT ON TABLE event_add_ons IS 'Purchased add-ons for specific events';
COMMENT ON TABLE organization_subscriptions IS 'Monthly/annual subscriptions for organizations';
COMMENT ON FUNCTION has_event_addon IS 'Check if event has specific add-on purchased';
COMMENT ON FUNCTION has_subscription_addon IS 'Check if organization has subscription add-on';
COMMENT ON FUNCTION has_feature_access IS 'Check if organization/event has access to feature flag';