Phase 1: API + Frontend Skelett
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
-- Migration: 001_initial_schema.sql
|
||||
-- Created: 2024
|
||||
|
||||
-- Enable UUID extension
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- ============================================
|
||||
-- PLATFORM ADMINS (internal team)
|
||||
-- ============================================
|
||||
CREATE TABLE platform_admins (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) NOT NULL DEFAULT 'support' CHECK (role IN ('super_admin', 'support', 'sales')),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_platform_admins_email ON platform_admins(email);
|
||||
|
||||
-- ============================================
|
||||
-- MODULES (global module definitions)
|
||||
-- ============================================
|
||||
CREATE TABLE modules (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
monthly_price INTEGER NOT NULL DEFAULT 0, -- cent
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert default modules
|
||||
INSERT INTO modules (key, name, description, monthly_price) VALUES
|
||||
('office', 'Office', 'Kundenverwaltung, Fahrzeugverwaltung, Berichte', 0),
|
||||
('calendar', 'Kalender', 'Terminplanung (Integration fahrschultermin.de)', 0),
|
||||
('messenger', 'Messenger', 'WhatsApp-ähnlicher Messenger', 0),
|
||||
('learning_blank', 'Lernsoftware (Blanko)', 'Eigene Inhalte erstellen', 0),
|
||||
('learning_premium', 'Lernsoftware (Premium)', 'Inkl. vorgefertigter Lerninhalte', 0);
|
||||
|
||||
-- ============================================
|
||||
-- PACKAGES (pricing bundles)
|
||||
-- ============================================
|
||||
CREATE TABLE packages (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
monthly_price INTEGER NOT NULL DEFAULT 0, -- cent
|
||||
modules_included JSONB DEFAULT '[]',
|
||||
max_users INTEGER DEFAULT 5,
|
||||
max_branches INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert default packages
|
||||
INSERT INTO packages (key, name, description, monthly_price, modules_included, max_users, max_branches) VALUES
|
||||
('starter', 'Starter', 'Basic-Paket für kleine Fahrschulen', 4900, '["office", "calendar", "messenger"]', 5, 1),
|
||||
('professional', 'Professional', 'Für mittlere Fahrschulen', 9900, '["office", "calendar", "messenger", "learning_blank"]', 20, 3),
|
||||
('premium', 'Premium', 'Alle Funktionen inkl. Premium-Lerninhalte', 14900, '["office", "calendar", "messenger", "learning_premium"]', 100, 10);
|
||||
|
||||
-- ============================================
|
||||
-- TENANTS (fahrschulen / customers)
|
||||
-- ============================================
|
||||
CREATE TABLE tenants (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
slug VARCHAR(255) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
phone VARCHAR(100),
|
||||
address TEXT,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending_approval' CHECK (status IN ('pending_approval', 'active', 'suspended', 'trial')),
|
||||
trial_ends_at TIMESTAMP WITH TIME ZONE,
|
||||
is_self_hosted BOOLEAN DEFAULT FALSE,
|
||||
self_hosted_license_key VARCHAR(255),
|
||||
-- Branding
|
||||
logo_url VARCHAR(500),
|
||||
primary_color VARCHAR(7) DEFAULT '#2563eb',
|
||||
secondary_color VARCHAR(7) DEFAULT '#64748b',
|
||||
custom_css TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tenants_slug ON tenants(slug);
|
||||
CREATE INDEX idx_tenants_status ON tenants(status);
|
||||
CREATE INDEX idx_tenants_email ON tenants(email);
|
||||
|
||||
-- ============================================
|
||||
-- BRANCHES (filialen)
|
||||
-- ============================================
|
||||
CREATE TABLE branches (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address TEXT,
|
||||
phone VARCHAR(100),
|
||||
email VARCHAR(255),
|
||||
opening_hours JSONB DEFAULT '{}',
|
||||
is_headquarters BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_branches_tenant ON branches(tenant_id);
|
||||
|
||||
-- ============================================
|
||||
-- USERS (tenant users)
|
||||
-- ============================================
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
branch_id UUID REFERENCES branches(id) ON DELETE SET NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
first_name VARCHAR(255) NOT NULL,
|
||||
last_name VARCHAR(255) NOT NULL,
|
||||
user_type VARCHAR(50) NOT NULL DEFAULT 'student' CHECK (user_type IN ('admin', 'office', 'instructor', 'lecturer', 'student')),
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'pending')),
|
||||
avatar_url VARCHAR(500),
|
||||
phone VARCHAR(100),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMP WITH TIME ZONE,
|
||||
UNIQUE(tenant_id, email)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_tenant ON users(tenant_id);
|
||||
CREATE INDEX idx_users_branch ON users(branch_id);
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
|
||||
-- ============================================
|
||||
-- TENANT MODULES (module activation per tenant)
|
||||
-- ============================================
|
||||
CREATE TABLE tenant_modules (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
module_id UUID NOT NULL REFERENCES modules(id) ON DELETE RESTRICT,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'enabled' CHECK (status IN ('enabled', 'disabled')),
|
||||
enabled_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
trial_ends_at TIMESTAMP WITH TIME ZONE,
|
||||
UNIQUE(tenant_id, module_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tenant_modules_tenant ON tenant_modules(tenant_id);
|
||||
|
||||
-- ============================================
|
||||
-- TENANT PACKAGES (active package per tenant)
|
||||
-- ============================================
|
||||
CREATE TABLE tenant_packages (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
package_id UUID NOT NULL REFERENCES packages(id) ON DELETE RESTRICT,
|
||||
started_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
UNIQUE(tenant_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tenant_packages_tenant ON tenant_packages(tenant_id);
|
||||
|
||||
-- ============================================
|
||||
-- LEARNING CONTENTS
|
||||
-- ============================================
|
||||
CREATE TABLE learning_contents (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, -- NULL = platform global
|
||||
category VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
content_type VARCHAR(50) NOT NULL CHECK (content_type IN ('video', 'document', 'quiz', 'exercise')),
|
||||
content_url VARCHAR(500),
|
||||
thumbnail_url VARCHAR(500),
|
||||
duration_minutes INTEGER,
|
||||
is_premium BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_learning_contents_tenant ON learning_contents(tenant_id);
|
||||
CREATE INDEX idx_learning_contents_category ON learning_contents(category);
|
||||
|
||||
-- ============================================
|
||||
-- STUDENT PROGRESS
|
||||
-- ============================================
|
||||
CREATE TABLE student_progress (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
content_id UUID NOT NULL REFERENCES learning_contents(id) ON DELETE CASCADE,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'not_started' CHECK (status IN ('not_started', 'in_progress', 'completed')),
|
||||
completed_at TIMESTAMP WITH TIME ZONE,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, content_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_student_progress_user ON student_progress(user_id);
|
||||
CREATE INDEX idx_student_progress_content ON student_progress(content_id);
|
||||
|
||||
-- ============================================
|
||||
-- AUDIT LOGS
|
||||
-- ============================================
|
||||
CREATE TABLE audit_logs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE, -- NULL = platform admin action
|
||||
user_id UUID, -- can be NULL if system action
|
||||
action VARCHAR(100) NOT NULL,
|
||||
entity_type VARCHAR(100) NOT NULL,
|
||||
entity_id UUID,
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_logs_tenant ON audit_logs(tenant_id);
|
||||
CREATE INDEX idx_audit_logs_created ON audit_logs(created_at);
|
||||
CREATE INDEX idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
|
||||
|
||||
-- ============================================
|
||||
-- INVOICES
|
||||
-- ============================================
|
||||
CREATE TABLE invoices (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
invoice_number VARCHAR(100) NOT NULL UNIQUE,
|
||||
amount INTEGER NOT NULL, -- cent
|
||||
vat_amount INTEGER NOT NULL DEFAULT 0,
|
||||
total_amount INTEGER NOT NULL,
|
||||
currency VARCHAR(3) DEFAULT 'EUR',
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('draft', 'pending', 'paid', 'overdue', 'cancelled')),
|
||||
billing_period_start DATE,
|
||||
billing_period_end DATE,
|
||||
due_date DATE,
|
||||
paid_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_invoices_tenant ON invoices(tenant_id);
|
||||
CREATE INDEX idx_invoices_status ON invoices(status);
|
||||
CREATE INDEX idx_invoices_due_date ON invoices(due_date);
|
||||
|
||||
CREATE TABLE invoice_items (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
invoice_id UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
|
||||
description TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL DEFAULT 1,
|
||||
unit_price INTEGER NOT NULL, -- cent
|
||||
total_price INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_invoice_items_invoice ON invoice_items(invoice_id);
|
||||
|
||||
-- ============================================
|
||||
-- NOTIFICATIONS
|
||||
-- ============================================
|
||||
CREATE TABLE notification_templates (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
subject VARCHAR(500) NOT NULL,
|
||||
body_html TEXT,
|
||||
body_text TEXT,
|
||||
variables JSONB DEFAULT '[]',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE notifications (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
template_key VARCHAR(100),
|
||||
subject VARCHAR(500) NOT NULL,
|
||||
body TEXT,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'sent', 'failed')),
|
||||
sent_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_notifications_user ON notifications(user_id);
|
||||
CREATE INDEX idx_notifications_tenant ON notifications(tenant_id);
|
||||
|
||||
-- ============================================
|
||||
-- FILES (DKS)
|
||||
-- ============================================
|
||||
CREATE TABLE files (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
entity_type VARCHAR(100) NOT NULL,
|
||||
entity_id UUID,
|
||||
file_type VARCHAR(100) NOT NULL,
|
||||
original_filename VARCHAR(500) NOT NULL,
|
||||
storage_path VARCHAR(1000) NOT NULL,
|
||||
mime_type VARCHAR(255),
|
||||
size_bytes BIGINT,
|
||||
expires_at TIMESTAMP WITH TIME ZONE, -- for temp files
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_files_tenant ON files(tenant_id);
|
||||
CREATE INDEX idx_files_entity ON files(entity_type, entity_id);
|
||||
CREATE INDEX idx_files_user ON files(user_id);
|
||||
|
||||
-- ============================================
|
||||
-- MESSENGER
|
||||
-- ============================================
|
||||
CREATE TABLE conversations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
type VARCHAR(50) NOT NULL DEFAULT 'direct' CHECK (type IN ('direct', 'group')),
|
||||
name VARCHAR(255), -- only for groups
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_conversations_tenant ON conversations(tenant_id);
|
||||
|
||||
CREATE TABLE conversation_participants (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
joined_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
last_read_at TIMESTAMP WITH TIME ZONE,
|
||||
UNIQUE(conversation_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_conversation_participants_conversation ON conversation_participants(conversation_id);
|
||||
CREATE INDEX idx_conversation_participants_user ON conversation_participants(user_id);
|
||||
|
||||
CREATE TABLE messages (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
body TEXT NOT NULL,
|
||||
is_edited BOOLEAN DEFAULT FALSE,
|
||||
is_deleted BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_messages_conversation ON messages(conversation_id);
|
||||
CREATE INDEX idx_messages_created ON messages(created_at);
|
||||
|
||||
-- ============================================
|
||||
-- UPDATE TIMESTAMP TRIGGER
|
||||
-- ============================================
|
||||
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER tr_platform_admins_updated_at BEFORE UPDATE ON platform_admins FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_tenants_updated_at BEFORE UPDATE ON tenants FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_branches_updated_at BEFORE UPDATE ON branches FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_learning_contents_updated_at BEFORE UPDATE ON learning_contents FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_student_progress_updated_at BEFORE UPDATE ON student_progress FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_invoices_updated_at BEFORE UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_conversations_updated_at BEFORE UPDATE ON conversations FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER tr_messages_updated_at BEFORE UPDATE ON messages FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
Reference in New Issue
Block a user