Add Multi-Tenant SaaS foundation for customer management
Phase 1 implementation includes: Database: - schema.sql with tables for tenants, domains, settings, branding, streams, users, subscriptions, plans, invoices, viewer_stats Core Classes (src/Core/): - Database.php: PDO wrapper with singleton pattern - TenantResolver.php: Domain-to-tenant resolution with fallback Tenant Classes (src/Tenant/): - TenantManager.php: CRUD operations for tenants - TenantSettingsManager.php: DB-based settings per tenant Configuration: - config.example.php: Template for database/stripe/mail config - bootstrap.php: Initializes multi-tenant environment - .gitignore: Excludes config.php and cache files Integration: - SettingsManager.php: Added saas_features toggles (all off by default) - index.php: Uses getSiteConfig() from bootstrap when multi-tenant enabled, falls back to legacy hardcoded domains when disabled All SaaS features are disabled by default (saas_features.multi_tenant_enabled = false), ensuring zero breaking changes to existing installations.
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
-- Aurora Livecam - Multi-Tenant SaaS Schema
|
||||
-- Version: 1.0.0
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Subscription Plans
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `plans` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`slug` VARCHAR(50) UNIQUE NOT NULL,
|
||||
`stripe_price_id` VARCHAR(100) NULL,
|
||||
`price_monthly` DECIMAL(10,2) DEFAULT 0.00,
|
||||
`price_yearly` DECIMAL(10,2) DEFAULT 0.00,
|
||||
`features` JSON NULL COMMENT '{"max_viewers": 100, "storage_gb": 5, "custom_domain": true}',
|
||||
`is_active` TINYINT(1) DEFAULT 1,
|
||||
`sort_order` INT DEFAULT 0,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Default Plans
|
||||
INSERT INTO `plans` (`name`, `slug`, `price_monthly`, `price_yearly`, `features`, `sort_order`) VALUES
|
||||
('Free', 'free', 0.00, 0.00, '{"max_viewers": 10, "storage_gb": 0.5, "custom_domain": false, "weather_widget": true, "timelapse": false, "analytics": false, "branding": false}', 1),
|
||||
('Basic', 'basic', 19.00, 190.00, '{"max_viewers": 50, "storage_gb": 5, "custom_domain": false, "weather_widget": true, "timelapse": true, "analytics": true, "branding": false}', 2),
|
||||
('Professional', 'professional', 49.00, 490.00, '{"max_viewers": 200, "storage_gb": 20, "custom_domain": true, "weather_widget": true, "timelapse": true, "analytics": true, "branding": true}', 3),
|
||||
('Enterprise', 'enterprise', 149.00, 1490.00, '{"max_viewers": -1, "storage_gb": 100, "custom_domain": true, "weather_widget": true, "timelapse": true, "analytics": true, "branding": true, "priority_support": true}', 4);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Tenants (Customers)
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `tenants` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`uuid` VARCHAR(36) UNIQUE NOT NULL,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`slug` VARCHAR(100) UNIQUE NOT NULL COMMENT 'URL-safe identifier, e.g. aurora, seecam',
|
||||
`email` VARCHAR(255) NOT NULL,
|
||||
`status` ENUM('trial', 'active', 'suspended', 'cancelled') DEFAULT 'trial',
|
||||
`plan_id` INT UNSIGNED NULL,
|
||||
`trial_ends_at` TIMESTAMP NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`plan_id`) REFERENCES `plans`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_slug` (`slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Tenant Domains
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `tenant_domains` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`tenant_id` INT UNSIGNED NOT NULL,
|
||||
`domain` VARCHAR(255) UNIQUE NOT NULL,
|
||||
`is_primary` TINYINT(1) DEFAULT 0,
|
||||
`ssl_status` ENUM('pending', 'active', 'failed') DEFAULT 'pending',
|
||||
`verified_at` TIMESTAMP NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE,
|
||||
INDEX `idx_domain` (`domain`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Tenant Settings (replaces settings.json per tenant)
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `tenant_settings` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`tenant_id` INT UNSIGNED NOT NULL,
|
||||
`setting_key` VARCHAR(255) NOT NULL,
|
||||
`setting_value` TEXT NULL,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `uk_tenant_key` (`tenant_id`, `setting_key`),
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Tenant Branding
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `tenant_branding` (
|
||||
`tenant_id` INT UNSIGNED PRIMARY KEY,
|
||||
`site_name` VARCHAR(255) NULL,
|
||||
`site_name_full` VARCHAR(255) NULL,
|
||||
`tagline` VARCHAR(255) NULL,
|
||||
`logo_path` VARCHAR(500) NULL,
|
||||
`favicon_path` VARCHAR(500) NULL,
|
||||
`primary_color` VARCHAR(7) DEFAULT '#667eea',
|
||||
`secondary_color` VARCHAR(7) DEFAULT '#764ba2',
|
||||
`accent_color` VARCHAR(7) DEFAULT '#f093fb',
|
||||
`welcome_text_de` TEXT NULL,
|
||||
`welcome_text_en` TEXT NULL,
|
||||
`footer_text` TEXT NULL,
|
||||
`custom_css` TEXT NULL,
|
||||
`custom_js` TEXT NULL,
|
||||
`social_facebook` VARCHAR(255) NULL,
|
||||
`social_instagram` VARCHAR(255) NULL,
|
||||
`social_youtube` VARCHAR(255) NULL,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Tenant Streams
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `tenant_streams` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`tenant_id` INT UNSIGNED NOT NULL,
|
||||
`name` VARCHAR(255) DEFAULT 'Main Stream',
|
||||
`stream_url` VARCHAR(500) NOT NULL,
|
||||
`stream_type` ENUM('hls', 'rtmp', 'webrtc', 'iframe') DEFAULT 'hls',
|
||||
`is_active` TINYINT(1) DEFAULT 1,
|
||||
`is_primary` TINYINT(1) DEFAULT 1,
|
||||
`last_check_at` TIMESTAMP NULL,
|
||||
`last_status` ENUM('online', 'offline', 'error') NULL,
|
||||
`error_message` VARCHAR(500) NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Users
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`tenant_id` INT UNSIGNED NULL COMMENT 'NULL = Super Admin',
|
||||
`email` VARCHAR(255) UNIQUE NOT NULL,
|
||||
`password_hash` VARCHAR(255) NOT NULL,
|
||||
`name` VARCHAR(255) NULL,
|
||||
`role` ENUM('super_admin', 'tenant_admin', 'tenant_user') NOT NULL DEFAULT 'tenant_user',
|
||||
`email_verified_at` TIMESTAMP NULL,
|
||||
`last_login_at` TIMESTAMP NULL,
|
||||
`remember_token` VARCHAR(100) NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE,
|
||||
INDEX `idx_email` (`email`),
|
||||
INDEX `idx_tenant` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Subscriptions
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `subscriptions` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`tenant_id` INT UNSIGNED NOT NULL,
|
||||
`plan_id` INT UNSIGNED NOT NULL,
|
||||
`stripe_subscription_id` VARCHAR(100) NULL,
|
||||
`stripe_customer_id` VARCHAR(100) NULL,
|
||||
`status` ENUM('trialing', 'active', 'past_due', 'canceled', 'unpaid', 'incomplete') DEFAULT 'trialing',
|
||||
`current_period_start` TIMESTAMP NULL,
|
||||
`current_period_end` TIMESTAMP NULL,
|
||||
`canceled_at` TIMESTAMP NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`plan_id`) REFERENCES `plans`(`id`),
|
||||
INDEX `idx_tenant` (`tenant_id`),
|
||||
INDEX `idx_stripe_sub` (`stripe_subscription_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Invoices (Stripe cache)
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `invoices` (
|
||||
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`tenant_id` INT UNSIGNED NOT NULL,
|
||||
`stripe_invoice_id` VARCHAR(100) UNIQUE NULL,
|
||||
`amount` DECIMAL(10,2) NOT NULL,
|
||||
`currency` VARCHAR(3) DEFAULT 'CHF',
|
||||
`status` VARCHAR(50) NULL,
|
||||
`paid_at` TIMESTAMP NULL,
|
||||
`invoice_pdf_url` VARCHAR(500) NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Viewer Statistics
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `viewer_stats` (
|
||||
`id` BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
`tenant_id` INT UNSIGNED NOT NULL,
|
||||
`recorded_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`viewer_count` INT DEFAULT 0,
|
||||
`unique_sessions` INT DEFAULT 0,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE,
|
||||
INDEX `idx_tenant_time` (`tenant_id`, `recorded_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
-- Onboarding Progress
|
||||
-- --------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `tenant_onboarding` (
|
||||
`tenant_id` INT UNSIGNED PRIMARY KEY,
|
||||
`current_step` INT DEFAULT 1,
|
||||
`stream_verified` TINYINT(1) DEFAULT 0,
|
||||
`branding_configured` TINYINT(1) DEFAULT 0,
|
||||
`payment_configured` TINYINT(1) DEFAULT 0,
|
||||
`completed_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
Reference in New Issue
Block a user