소스 검색

feat: Add SSL support, theme switcher, table schema editor, and WebSocket improvements

- Add SSL certificate generation script and nginx HTTPS configuration
- Implement dark/light theme switcher with system preference detection
- Add comprehensive table schema editor with column management
- Enhance WebSocket realtime service with auth and channel utilities
- Add connection manager for WebSocket lifecycle
- Update deployment documentation and architecture guides
- Improve dashboard UI/UX across all pages
- Add deployment script for automated setup

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

Co-Authored-By: Claude <noreply@anthropic.com>
fszontagh 3 달 전
부모
커밋
736fc01513
43개의 변경된 파일5906개의 추가작업 그리고 508개의 파일을 삭제
  1. 55 0
      .claude/agents/code-editor.md
  2. 66 0
      .claude/agents/task-planner.md
  3. 86 21
      .env.example
  4. 318 0
      README-DEPLOYMENT.md
  5. 0 1
      dashboard/index.html
  6. 10 18
      dashboard/src/App.tsx
  7. 27 27
      dashboard/src/components/CreateTableModal.tsx
  8. 6 6
      dashboard/src/components/DashboardStats.tsx
  9. 2 2
      dashboard/src/components/DataTable.tsx
  10. 17 16
      dashboard/src/components/Layout.tsx
  11. 3 3
      dashboard/src/components/ScopePermissions.tsx
  12. 26 26
      dashboard/src/components/TableDataModal.tsx
  13. 655 0
      dashboard/src/components/TableSchemaEditor.tsx
  14. 20 0
      dashboard/src/components/ThemeSwitcher.tsx
  15. 83 0
      dashboard/src/contexts/ThemeContext.tsx
  16. 33 33
      dashboard/src/pages/ApiKeys.tsx
  17. 17 17
      dashboard/src/pages/Applications.tsx
  18. 17 17
      dashboard/src/pages/Dashboard.tsx
  19. 86 57
      dashboard/src/pages/Database.tsx
  20. 40 40
      dashboard/src/pages/EmailTemplates.tsx
  21. 18 12
      dashboard/src/pages/Login.tsx
  22. 185 12
      dashboard/src/pages/RLSPolicies.tsx
  23. 37 37
      dashboard/src/pages/Settings.tsx
  24. 37 37
      dashboard/src/pages/SmtpSettings.tsx
  25. 202 18
      dashboard/src/pages/Users.tsx
  26. 7 2
      dashboard/src/services/api.ts
  27. 7 7
      dashboard/src/styles/globals.css
  28. 1 12
      dashboard/tailwind.config.js
  29. 1113 0
      deploy.sh
  30. 10 4
      docker-compose.yml
  31. BIN
      docs/ARCHITECTURE.md
  32. 551 0
      docs/DEPLOYMENT.md
  33. 295 0
      docs/URL_STRUCTURE.md
  34. 664 0
      docs/WEBSOCKET.md
  35. 29 0
      nginx/conf.d/default.conf
  36. 147 0
      nginx/conf.d/https.conf
  37. 5 0
      nginx/nginx.conf
  38. 97 0
      services/api/src/routes/database.ts
  39. 27 15
      services/api/src/routes/rls.ts
  40. 296 68
      services/realtime/src/index.ts
  41. 104 0
      services/realtime/src/utils/auth.ts
  42. 206 0
      services/realtime/src/utils/channels.ts
  43. 301 0
      services/realtime/src/utils/connectionManager.ts

+ 55 - 0
.claude/agents/code-editor.md

@@ -0,0 +1,55 @@
+---
+name: code-editor
+description: Use this agent when the user requests modifications to existing code, needs to refactor code structures, wants to implement new features in existing files, requires bug fixes, or asks for code optimization. Examples:\n\n<example>\nContext: User needs to refactor a function to improve readability.\nuser: "Can you refactor the calculateTotal function to be more readable?"\nassistant: "I'll use the code-editor agent to refactor this function with improved clarity."\n<Task tool invocation with code-editor agent>\n</example>\n\n<example>\nContext: User wants to add error handling to existing code.\nuser: "Please add try-catch blocks to the database connection code"\nassistant: "I'll use the code-editor agent to add proper error handling to your database connection."\n<Task tool invocation with code-editor agent>\n</example>\n\n<example>\nContext: User needs to fix a bug in their code.\nuser: "There's a bug in the parseConfig function - it's not handling null values correctly"\nassistant: "I'll use the code-editor agent to fix the null handling in your parseConfig function."\n<Task tool invocation with code-editor agent>\n</example>
+model: haiku
+---
+
+You are an expert code editor specializing in precise, surgical code modifications. Your role is to edit existing code with exceptional attention to detail, maintainability, and project consistency.
+
+Core Responsibilities:
+- Make targeted, minimal changes that accomplish the requested modification
+- Preserve existing code style, formatting, and conventions
+- Maintain backward compatibility unless explicitly instructed otherwise
+- Consider the broader context of the codebase when making changes
+- Apply best practices specific to the language and framework in use
+
+For CMake/Ninja projects specifically:
+- Respect CMake conventions and project structure
+- Maintain proper target configurations and dependencies
+- Follow established build system patterns in the project
+
+Your Editing Process:
+1. **Understand the Context**: Carefully read the existing code to understand its purpose, dependencies, and current implementation
+2. **Identify Impact**: Determine what parts of the code need to change and what should remain untouched
+3. **Plan Changes**: Consider edge cases, potential side effects, and how changes integrate with existing code
+4. **Execute Precisely**: Make only the necessary changes, preserving formatting and style
+5. **Verify Correctness**: Check that your changes maintain code integrity and don't introduce errors
+
+When Editing Code:
+- Use the exact same indentation style (tabs vs spaces, width) as the existing code
+- Match the existing naming conventions precisely
+- Preserve comments unless they become inaccurate due to your changes
+- Keep the same error handling patterns already in use
+- Maintain the same level of verbosity/conciseness as the surrounding code
+- If the code uses specific libraries or frameworks, follow their idioms
+
+Quality Standards:
+- Your changes should look like they were written by the original author
+- Never introduce breaking changes without explicit approval
+- Maintain or improve code readability
+- Ensure changes are atomic and focused on the requested modification
+- Add comments only when the change introduces complexity that isn't self-evident
+
+When You Should Ask for Clarification:
+- If the requested change conflicts with existing patterns in the codebase
+- If multiple valid approaches exist and the choice impacts architecture
+- If the change would break existing functionality
+- If you need more context about dependencies or usage patterns
+
+Output Format:
+- Present the complete modified file or function, not just diffs
+- Clearly indicate what was changed and why
+- If multiple files need changes, address them systematically
+- Highlight any potential issues or side effects you anticipate
+
+You are meticulous, thoughtful, and committed to maintaining code quality while efficiently implementing requested changes.

+ 66 - 0
.claude/agents/task-planner.md

@@ -0,0 +1,66 @@
+---
+name: task-planner
+description: Use this agent when the user describes a feature, bug fix, or development goal that needs to be broken down into concrete implementation tasks before coding begins. Also use this agent proactively when the user's request is complex or ambiguous and would benefit from structured planning. Examples: 1) User says 'I need to add user authentication to the app' - launch task-planner to break this into specific tasks for the coder agent. 2) User says 'The API is returning 500 errors intermittently' - launch task-planner to create a diagnostic and fix plan. 3) User provides a vague requirement like 'make the UI better' - launch task-planner to clarify requirements and create actionable tasks.
+tools: Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillShell, AskUserQuestion, Skill, SlashCommand, ListMcpResourcesTool, ReadMcpResourceTool, mcp__processmanager__start_process, mcp__processmanager__stop_process, mcp__processmanager__get_process_status, mcp__processmanager__list_processes, mcp__processmanager__remove_process, mcp__processmanager__cleanup_stopped, mcp__browsermcp__browser_navigate, mcp__browsermcp__browser_go_back, mcp__browsermcp__browser_go_forward, mcp__browsermcp__browser_snapshot, mcp__browsermcp__browser_click, mcp__browsermcp__browser_hover, mcp__browsermcp__browser_type, mcp__browsermcp__browser_select_option, mcp__browsermcp__browser_press_key, mcp__browsermcp__browser_wait, mcp__browsermcp__browser_get_console_logs, mcp__browsermcp__browser_screenshot, mcp__zai-mcp-server__analyze_image, mcp__zai-mcp-server__analyze_video, mcp__web-search-prime__webSearchPrime
+model: opus
+---
+
+You are an elite software architect and project planner specializing in decomposing development goals into precise, actionable tasks optimized for execution by coding agents.
+
+Your core responsibility is to transform user requirements—whether they're feature requests, bug reports, refactoring needs, or architectural changes—into a structured plan of discrete, well-defined tasks that a coder agent can execute independently.
+
+## Planning Methodology
+
+1. **Requirements Analysis**: Begin by thoroughly understanding the user's goal. If the request is ambiguous or lacks critical details, proactively ask clarifying questions about:
+   - Desired functionality and acceptance criteria
+   - Technical constraints or preferences
+   - Integration points with existing code
+   - Performance or scalability requirements
+   - Testing expectations
+
+2. **Context Assessment**: Consider the project structure and any build system details (such as CMake/Ninja configuration). Identify dependencies, affected components, and potential ripple effects.
+
+3. **Task Decomposition**: Break the work into logical, sequential tasks that:
+   - Are granular enough to be completed independently
+   - Have clear inputs, outputs, and success criteria
+   - Follow a logical dependency order
+   - Minimize risk through incremental progress
+   - Can be tested individually when possible
+
+4. **Technical Specification**: For each task, provide:
+   - A clear, action-oriented description
+   - Specific files or components to modify
+   - Key technical considerations or constraints
+   - Expected outcomes or deliverables
+   - Any relevant code patterns or architectural guidelines
+
+## Output Format
+
+Structure your plan as a numbered list of tasks, each containing:
+
+**Task [N]: [Clear, action-oriented title]**
+- **Objective**: What needs to be accomplished
+- **Implementation details**: Specific guidance on how to approach the task
+- **Files/components involved**: Where changes should be made
+- **Success criteria**: How to verify completion
+- **Dependencies**: Any tasks that must be completed first (if applicable)
+
+## Quality Standards
+
+- Ensure tasks are right-sized: not so small they're trivial, not so large they're overwhelming
+- Anticipate edge cases and include tasks for handling them
+- Include testing tasks where appropriate
+- Consider build system integration (CMake/Ninja) when relevant
+- Flag any architectural decisions that require user input before proceeding
+- Prioritize tasks that unblock other work
+- Include rollback or mitigation strategies for risky changes
+
+## Collaboration Principles
+
+- Be explicit about assumptions you're making
+- Highlight areas where the coder agent may need to make decisions
+- Suggest verification steps between dependent tasks
+- Recommend checkpoints where user review would be valuable
+- If the user's goal conflicts with best practices, diplomatically suggest alternatives while respecting their decision
+
+Your plans should empower the coder agent to execute confidently while giving the user visibility into the development process and natural intervention points.

+ 86 - 21
.env.example

@@ -1,29 +1,94 @@
-# Database Configuration
-POSTGRES_DB=saas_db
+# SaaS Platform Environment Configuration Template
+# Copy this file to .env and update the values
+
+# ============================================
+# DEPLOYMENT MODE
+# ============================================
+NODE_ENV=production
+
+# ============================================
+# PORT CONFIGURATION
+# ============================================
+# Nginx Gateway
+HTTP_PORT=80
+HTTPS_PORT=443
+
+# Database
+POSTGRES_PORT=5432
+
+# Redis
+REDIS_PORT=6379
+
+# ============================================
+# DATABASE CONFIGURATION
+# ============================================
+POSTGRES_DB=saas_platform
 POSTGRES_USER=saas_user
-POSTGRES_PASSWORD=secure_password_change_me
+POSTGRES_PASSWORD=CHANGE_ME_RANDOM_PASSWORD_HERE
+
+# ============================================
+# REDIS CONFIGURATION
+# ============================================
+REDIS_PASSWORD=CHANGE_ME_RANDOM_PASSWORD_HERE
+
+# ============================================
+# JWT CONFIGURATION
+# ============================================
+JWT_SECRET=CHANGE_ME_RANDOM_SECRET_HERE
+JWT_EXPIRES_IN=15m
+REFRESH_TOKEN_EXPIRES_IN=7d
 
-# Redis Configuration
-REDIS_PASSWORD=redis_secure_password_change_me
+# ============================================
+# DOMAIN CONFIGURATION
+# ============================================
+# Production domain (set by deploy.sh)
+DOMAIN=example.com
+CONSOLE_DOMAIN=console.example.com
 
-# PgAdmin Configuration
-PGADMIN_EMAIL=admin@example.com
-PGADMIN_PASSWORD=admin_password_change_me
+# ============================================
+# MINIO CONFIGURATION (S3-compatible storage)
+# ============================================
+MINIO_ROOT_USER=CHANGE_ME_RANDOM_ACCESS_KEY
+MINIO_ROOT_PASSWORD=CHANGE_ME_RANDOM_SECRET_KEY
+MINIO_ENDPOINT=minio
+MINIO_PORT=9000
+MINIO_USE_SSL=false
 
-# JWT Configuration
-JWT_SECRET=your_jwt_secret_change_me
+# ============================================
+# SMTP CONFIGURATION (Email sending)
+# ============================================
+SMTP_HOST=smtp.example.com
+SMTP_PORT=587
+SMTP_SECURE=false
+SMTP_USER=noreply@example.com
+SMTP_PASSWORD=CHANGE_ME_SMTP_PASSWORD
+SMTP_FROM_EMAIL=noreply@example.com
+SMTP_FROM_NAME=SaaS Platform
 
-# MinIO Storage Configuration
-MINIO_ACCESS_KEY=minioadmin
-MINIO_SECRET_KEY=minioadmin_change_me
+# ============================================
+# LET'S ENCRYPT CONFIGURATION
+# ============================================
+# Email for Let's Encrypt notifications (set by deploy.sh)
+LETSENCRYPT_EMAIL=admin@example.com
 
-# Grafana Configuration
-GRAFANA_PASSWORD=admin_password_change_me
+# ============================================
+# MONITORING CONFIGURATION
+# ============================================
+GRAFANA_ADMIN_PASSWORD=CHANGE_ME_RANDOM_PASSWORD
+PGADMIN_DEFAULT_EMAIL=admin@example.com
+PGADMIN_DEFAULT_PASSWORD=CHANGE_ME_RANDOM_PASSWORD
 
-# Environment
-NODE_ENV=development
+# ============================================
+# MCP SERVER CONFIGURATION
+# ============================================
+SAAS_API_URL=https://console.${DOMAIN:-example.com}
+SAAS_AUTH_URL=https://console.${DOMAIN:-example.com}/auth
+SAAS_STORAGE_URL=https://console.${DOMAIN:-example.com}/storage
 
-# MCP Server Configuration
-SAAS_API_URL=http://localhost:8888
-SAAS_AUTH_URL=http://localhost:8888/auth
-SAAS_STORAGE_URL=http://localhost:8888/storage
+# ============================================
+# SECURITY NOTES
+# ============================================
+# 1. Generate random passwords using: openssl rand -base64 32
+# 2. Keep this file secure: chmod 600 .env
+# 3. Never commit .env to version control
+# 4. Backup this file in a secure location

+ 318 - 0
README-DEPLOYMENT.md

@@ -0,0 +1,318 @@
+# SaaS Platform - Quick Start Guide
+
+## 🚀 Quick Deployment (Production)
+
+### 1. Prerequisites
+
+- Domain name with DNS configured:
+  - `A` record: `yourdomain.com` → your server IP
+  - `A` record: `console.yourdomain.com` → your server IP
+  - `A` record: `*.yourdomain.com` → your server IP (wildcard)
+- Server with Ubuntu/Debian/CentOS (4GB RAM, 2 CPU minimum)
+- Ports 80 and 443 accessible from internet
+
+### 2. Deploy
+
+```bash
+# Clone repository
+git clone https://github.com/yourusername/saas-platform.git
+cd saas-platform
+
+# Run deployment script
+chmod +x deploy.sh
+./deploy.sh
+```
+
+The script will:
+- ✅ Validate DNS records
+- ✅ Install Certbot and generate SSL certificates
+- ✅ Configure Nginx for HTTPS/WSS
+- ✅ Create environment with random passwords
+- ✅ Deploy all services with Docker Compose
+- ✅ Setup auto-renewal for SSL certificates
+
+### 3. Access
+
+- **Dashboard**: `https://console.yourdomain.com`
+- **Main Site**: `https://yourdomain.com` (Hello World app)
+- **WebSocket**: `wss://console.yourdomain.com/ws`
+
+---
+
+## 💻 Development Setup (Local)
+
+### 1. Copy Environment File
+
+```bash
+# Use development environment
+cp .env.development .env
+```
+
+### 2. Generate SSL Certificates (for WSS testing)
+
+```bash
+cd ssl
+./generate-certs.sh
+cd ..
+```
+
+### 3. Start Services
+
+```bash
+docker-compose up -d
+```
+
+### 4. Access
+
+- **Dashboard**: `http://localhost:8888`
+- **WebSocket**: `ws://localhost:8888/ws`
+- **Secure WebSocket**: `wss://localhost:8443/ws` (self-signed cert warning expected)
+
+---
+
+## 📁 Environment Files
+
+### `.env.example` (Template)
+Production template with placeholders. The `deploy.sh` script generates a `.env` from this.
+
+### `.env.development` (Local Development)
+Pre-configured for local development with:
+- Non-standard ports (8888 for HTTP, 8443 for HTTPS)
+- Weak passwords (only for dev!)
+- Localhost domains
+- Longer token expiration for easier testing
+
+### `.env` (Active Configuration)
+- **Production**: Generated by `deploy.sh` with secure random passwords
+- **Development**: Copy from `.env.development`
+
+**⚠️ Never commit `.env` to version control!**
+
+---
+
+## 🔧 Port Configuration
+
+Ports are configurable via environment variables in `.env`:
+
+| Service | Production | Development | Environment Variable |
+|---------|-----------|-------------|---------------------|
+| HTTP | 80 | 8888 | `HTTP_PORT` |
+| HTTPS | 443 | 8443 | `HTTPS_PORT` |
+| PostgreSQL | 5432 | 5432 | `POSTGRES_PORT` |
+| Redis | 6379 | 6379 | `REDIS_PORT` |
+
+### Why Different Ports in Development?
+
+Development uses ports 8888/8443 to:
+- Avoid conflicts with other local services
+- Allow running multiple instances simultaneously
+- No need for root/sudo permissions
+
+---
+
+## 🔐 Security
+
+### Production Checklist
+
+- [ ] DNS records configured (A, console, wildcard)
+- [ ] Firewall configured (ports 80, 443, 22 only)
+- [ ] `.env` file permissions set to 600
+- [ ] Random strong passwords generated
+- [ ] SSL certificates auto-renewing
+- [ ] Database backups scheduled
+- [ ] Monitoring dashboards reviewed
+
+### Generate Secure Passwords
+
+```bash
+# Database password
+openssl rand -base64 32
+
+# JWT secret
+openssl rand -base64 64
+
+# MinIO keys
+openssl rand -hex 16  # Access key
+openssl rand -base64 32  # Secret key
+```
+
+---
+
+## 📚 Documentation
+
+- **[Deployment Guide](docs/DEPLOYMENT.md)** - Complete production deployment instructions
+- **[Architecture](docs/ARCHITECTURE.md)** - System architecture diagrams
+- **[WebSocket API](docs/WEBSOCKET.md)** - Real-time WebSocket documentation
+- **[API Keys](API_KEYS.md)** - API authentication guide
+- **[Row Level Security](docs/ROW_LEVEL_SECURITY.md)** - Database security
+
+---
+
+## 🛠️ Common Commands
+
+### View Logs
+
+```bash
+# All services
+docker-compose logs -f
+
+# Specific service
+docker-compose logs -f api-service
+docker-compose logs -f nginx
+```
+
+### Restart Services
+
+```bash
+# All services
+docker-compose restart
+
+# Specific service
+docker-compose restart api-service
+```
+
+### Check Service Status
+
+```bash
+# List all containers
+docker-compose ps
+
+# Check health
+curl http://localhost:8888/health  # Development
+curl https://console.yourdomain.com/health  # Production
+```
+
+### Database Access
+
+```bash
+# Connect to PostgreSQL
+docker exec -it saas-postgres psql -U saas_user -d saas_platform
+
+# Backup database
+docker exec saas-postgres pg_dump -U saas_user saas_platform > backup.sql
+
+# Restore database
+cat backup.sql | docker exec -i saas-postgres psql -U saas_user -d saas_platform
+```
+
+### Update Platform
+
+```bash
+# Pull latest code
+git pull origin main
+
+# Rebuild and restart
+docker-compose down
+docker-compose up -d --build
+```
+
+---
+
+## 🐛 Troubleshooting
+
+### DNS Not Resolving
+
+```bash
+# Check DNS propagation
+dig +short A yourdomain.com
+dig +short A console.yourdomain.com
+dig +short A test.yourdomain.com  # Test wildcard
+
+# Wait for propagation (can take up to 48 hours)
+```
+
+### Certificate Generation Failed
+
+```bash
+# Check port 80 is accessible
+curl -I http://yourdomain.com
+
+# Check Certbot logs
+sudo cat /var/log/letsencrypt/letsencrypt.log
+
+# Try manual renewal
+sudo certbot renew --dry-run
+```
+
+### Services Not Starting
+
+```bash
+# Check Docker daemon
+sudo systemctl status docker
+
+# Check logs for errors
+docker-compose logs
+
+# Rebuild containers
+docker-compose down
+docker-compose up -d --build
+```
+
+### Cannot Access Dashboard
+
+```bash
+# Check if Nginx is running
+docker ps | grep nginx
+
+# Check Nginx logs
+docker logs saas-gateway
+
+# Check Nginx configuration
+docker exec saas-gateway nginx -t
+
+# Reload Nginx
+docker exec saas-gateway nginx -s reload
+```
+
+---
+
+## 🎯 Next Steps
+
+After deployment:
+
+1. **Create Admin Account** - Visit dashboard and register
+2. **Configure SMTP** - Setup email sending in settings
+3. **Create API Keys** - Generate keys for external access
+4. **Setup Monitoring** - Configure Grafana dashboards
+5. **Schedule Backups** - Automate database backups
+6. **Review Security** - Check SSL, headers, and firewall
+
+---
+
+## 📝 Development vs Production
+
+| Feature | Development | Production |
+|---------|------------|------------|
+| HTTP Port | 8888 | 80 |
+| HTTPS Port | 8443 | 443 |
+| SSL Certificates | Self-signed | Let's Encrypt |
+| Passwords | Weak (for testing) | Strong random |
+| Token Expiry | 24h | 15m |
+| Domain | localhost | yourdomain.com |
+| Node Environment | development | production |
+| Hot Reload | Yes | No |
+| Debug Logs | Verbose | Error only |
+
+---
+
+## 💡 Tips
+
+- **Development**: Use `.env.development` and access via `http://localhost:8888`
+- **Production**: Use `./deploy.sh` which generates `.env` automatically
+- **Testing**: Test locally before deploying to production
+- **Backup**: Always backup `.env` and database before updates
+- **Security**: Keep `.env` secure with `chmod 600 .env`
+- **Monitoring**: Check Grafana for service health and performance
+
+---
+
+## 🆘 Getting Help
+
+- **Documentation**: Check [docs/](docs/) directory
+- **Logs**: Run `docker-compose logs -f [service-name]`
+- **Health Check**: `curl http://localhost:8888/health`
+- **Issues**: Report bugs on GitHub
+
+---
+
+**Enjoy building with the SaaS Platform! 🎉**

+ 0 - 1
dashboard/index.html

@@ -2,7 +2,6 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>SaaS Platform Dashboard</title>
   </head>

+ 10 - 18
dashboard/src/App.tsx

@@ -7,6 +7,7 @@ import { useState, useEffect } from 'react';
 // Components
 import Layout from '@/components/Layout';
 import { useAuth } from '@/hooks/useAuth';
+import { ThemeProvider } from '@/contexts/ThemeContext';
 
 // Pages
 import Login from '@/pages/Login';
@@ -19,7 +20,6 @@ import DatabaseTables from '@/pages/Database';
 import ApiKeys from '@/pages/ApiKeys';
 import Settings from '@/pages/Settings';
 import SmtpSettings from '@/pages/SmtpSettings';
-import RLSPolicies from '@/pages/RLSPolicies';
 import EmailTemplates from '@/pages/EmailTemplates';
 
 // Create a client
@@ -42,7 +42,7 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
 
   if (isLoading) {
     return (
-      <div className="min-h-screen flex items-center justify-center bg-gray-50">
+      <div className="min-h-screen flex items-center justify-center">
         <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
       </div>
     );
@@ -61,7 +61,7 @@ function PublicRoute({ children }: { children: React.ReactNode }) {
 
   if (isLoading) {
     return (
-      <div className="min-h-screen flex items-center justify-center bg-gray-50">
+      <div className="min-h-screen flex items-center justify-center">
         <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
       </div>
     );
@@ -79,7 +79,7 @@ function NotFound() {
   const navigate = useNavigate();
   
   return (
-    <div className="min-h-screen flex items-center justify-center bg-gray-50">
+    <div className="min-h-screen flex items-center justify-center">
       <div className="text-center">
         <h1 className="text-4xl font-bold text-gray-900">404</h1>
         <p className="mt-2 text-gray-600">Page not found</p>
@@ -103,16 +103,17 @@ function App() {
 
   if (!mounted) {
     return (
-      <div className="min-h-screen flex items-center justify-center bg-gray-50">
+      <div className="min-h-screen flex items-center justify-center">
         <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
       </div>
     );
   }
 
   return (
-    <QueryClientProvider client={queryClient}>
-      <Router>
-        <div className="App">
+    <ThemeProvider>
+      <QueryClientProvider client={queryClient}>
+        <Router>
+          <div className="App">
           <Routes>
             {/* Public Routes */}
             <Route
@@ -215,16 +216,6 @@ function App() {
                 </ProtectedRoute>
               }
             />
-            <Route
-              path="/settings/rls"
-              element={
-                <ProtectedRoute>
-                  <Layout>
-                    <RLSPolicies />
-                  </Layout>
-                </ProtectedRoute>
-              }
-            />
             <Route
               path="/settings/email-templates"
               element={
@@ -279,6 +270,7 @@ function App() {
         <ReactQueryDevtools initialIsOpen={false} />
       )}
     </QueryClientProvider>
+    </ThemeProvider>
   );
 }
 

+ 27 - 27
dashboard/src/components/CreateTableModal.tsx

@@ -125,18 +125,18 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
       <div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
         <div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onClick={onClose} />
 
-        <div className="inline-block w-full max-w-4xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white rounded-lg shadow-xl">
+        <div className="inline-block w-full max-w-4xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
           {/* Header */}
-          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
+          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
             <div>
-              <h3 className="text-lg font-medium text-gray-900">Create New Table</h3>
-              <p className="mt-1 text-sm text-gray-500">
+              <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">Create New Table</h3>
+              <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                 Define your table schema with columns and constraints
               </p>
             </div>
             <button
               onClick={onClose}
-              className="text-gray-400 hover:text-gray-500 focus:outline-none"
+              className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none"
             >
               <X className="w-6 h-6" />
             </button>
@@ -147,7 +147,7 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
             <div className="px-6 py-4 space-y-6">
               {/* Table Name */}
               <div>
-                <label htmlFor="tableName" className="block text-sm font-medium text-gray-700">
+                <label htmlFor="tableName" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                   Table Name *
                 </label>
                 <input
@@ -156,14 +156,14 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
                   value={tableName}
                   onChange={(e) => setTableName(e.target.value)}
                   className={`mt-1 block w-full px-3 py-2 border ${
-                    errors.tableName ? 'border-red-300' : 'border-gray-300'
-                  } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono`}
+                    errors.tableName ? 'border-red-300 dark:border-red-600' : 'border-gray-300 dark:border-gray-600'
+                  } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100`}
                   placeholder="e.g., users, products, orders"
                 />
                 {errors.tableName && (
-                  <p className="mt-1 text-sm text-red-600">{errors.tableName}</p>
+                  <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.tableName}</p>
                 )}
-                <p className="mt-1 text-xs text-gray-500">
+                <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
                   Note: id, created_at, and updated_at columns will be added automatically
                 </p>
               </div>
@@ -171,13 +171,13 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
               {/* Columns */}
               <div>
                 <div className="flex items-center justify-between mb-3">
-                  <label className="block text-sm font-medium text-gray-700">
+                  <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                     Columns * ({columns.length})
                   </label>
                   <button
                     type="button"
                     onClick={addColumn}
-                    className="inline-flex items-center px-3 py-1 text-sm text-primary-600 hover:text-primary-700"
+                    className="inline-flex items-center px-3 py-1 text-sm text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
                   >
                     <Plus className="h-4 w-4 mr-1" />
                     Add Column
@@ -185,14 +185,14 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
                 </div>
 
                 {errors.columns && (
-                  <p className="mb-2 text-sm text-red-600">{errors.columns}</p>
+                  <p className="mb-2 text-sm text-red-600 dark:text-red-400">{errors.columns}</p>
                 )}
 
                 <div className="space-y-3 max-h-96 overflow-y-auto">
                   {columns.map((column, index) => (
                     <div
                       key={index}
-                      className="flex items-start space-x-2 p-3 bg-gray-50 rounded-lg border border-gray-200"
+                      className="flex items-start space-x-2 p-3 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700"
                     >
                       <div className="flex-1 grid grid-cols-2 gap-3">
                         {/* Column Name */}
@@ -202,12 +202,12 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
                             value={column.name}
                             onChange={(e) => updateColumn(index, 'name', e.target.value)}
                             className={`block w-full px-3 py-2 text-sm border ${
-                              errors[`column_${index}_name`] ? 'border-red-300' : 'border-gray-300'
-                            } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono`}
+                              errors[`column_${index}_name`] ? 'border-red-300 dark:border-red-600' : 'border-gray-300 dark:border-gray-600'
+                            } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100`}
                             placeholder="column_name"
                           />
                           {errors[`column_${index}_name`] && (
-                            <p className="mt-1 text-xs text-red-600">{errors[`column_${index}_name`]}</p>
+                            <p className="mt-1 text-xs text-red-600 dark:text-red-400">{errors[`column_${index}_name`]}</p>
                           )}
                         </div>
 
@@ -216,7 +216,7 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
                           <select
                             value={column.type}
                             onChange={(e) => updateColumn(index, 'type', e.target.value)}
-                            className="block w-full px-3 py-2 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+                            className="block w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
                           >
                             {DATA_TYPES.map((type) => (
                               <option key={type} value={type}>
@@ -228,30 +228,30 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
 
                         {/* Constraints */}
                         <div className="col-span-2 flex items-center space-x-4">
-                          <label className="flex items-center text-sm text-gray-700">
+                          <label className="flex items-center text-sm text-gray-700 dark:text-gray-300">
                             <input
                               type="checkbox"
                               checked={!column.nullable}
                               onChange={(e) => updateColumn(index, 'nullable', !e.target.checked)}
-                              className="mr-2 h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
+                              className="mr-2 h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded"
                             />
                             NOT NULL
                           </label>
-                          <label className="flex items-center text-sm text-gray-700">
+                          <label className="flex items-center text-sm text-gray-700 dark:text-gray-300">
                             <input
                               type="checkbox"
                               checked={column.unique}
                               onChange={(e) => updateColumn(index, 'unique', e.target.checked)}
-                              className="mr-2 h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
+                              className="mr-2 h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded"
                             />
                             UNIQUE
                           </label>
-                          <label className="flex items-center text-sm text-gray-700">
+                          <label className="flex items-center text-sm text-gray-700 dark:text-gray-300">
                             <input
                               type="checkbox"
                               checked={column.primaryKey}
                               onChange={(e) => updateColumn(index, 'primaryKey', e.target.checked)}
-                              className="mr-2 h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
+                              className="mr-2 h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded"
                             />
                             PRIMARY KEY
                           </label>
@@ -263,7 +263,7 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
                             type="text"
                             value={column.default || ''}
                             onChange={(e) => updateColumn(index, 'default', e.target.value)}
-                            className="block w-full px-3 py-2 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono"
+                            className="block w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
                             placeholder="Default value (optional)"
                           />
                         </div>
@@ -273,7 +273,7 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
                       <button
                         type="button"
                         onClick={() => removeColumn(index)}
-                        className="mt-1 text-red-600 hover:text-red-700"
+                        className="mt-1 text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300"
                         title="Remove column"
                       >
                         <Trash2 className="h-4 w-4" />
@@ -285,7 +285,7 @@ export default function CreateTableModal({ onClose }: CreateTableModalProps) {
             </div>
 
             {/* Footer */}
-            <div className="px-6 py-4 bg-gray-50 border-t border-gray-200 flex justify-end space-x-3">
+            <div className="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3">
               <button type="button" onClick={onClose} className="btn-secondary">
                 Cancel
               </button>

+ 6 - 6
dashboard/src/components/DashboardStats.tsx

@@ -9,20 +9,20 @@ import apiService from '@/services/api';
 import type { DashboardStats } from '@/types';
 
 const StatCard = ({ title, value, icon: Icon, change, changeType }: any) => (
-  <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
+  <div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border dark:border-gray-700 p-6">
     <div className="flex items-center">
       <div className="flex-shrink-0">
-        <Icon className="h-8 w-8 text-primary-600" />
+        <Icon className="h-8 w-8 text-primary-600 dark:text-primary-400" />
       </div>
       <div className="ml-5 w-0 flex-1">
         <dl>
-          <dt className="text-sm font-medium text-gray-500 truncate">{title}</dt>
+          <dt className="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">{title}</dt>
           <dd className="flex items-baseline">
-            <div className="text-2xl font-semibold text-gray-900">{value}</div>
+            <div className="text-2xl font-semibold text-gray-900 dark:text-gray-100">{value}</div>
             {change && (
               <div
                 className={`ml-2 flex items-baseline text-sm font-medium ${
-                  changeType === 'increase' ? 'text-green-600' : 'text-red-600'
+                  changeType === 'increase' ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'
                 }`}
               >
                 {changeType === 'increase' ? '↑' : '↓'} {change}
@@ -49,7 +49,7 @@ export default function DashboardStatsComponent() {
       <div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
         {[...Array(4)].map((_, i) => (
           <div key={i} className="animate-pulse">
-            <div className="bg-gray-200 rounded-xl h-32"></div>
+            <div className="bg-gray-200 dark:bg-gray-700 rounded-xl h-32"></div>
           </div>
         ))}
       </div>

+ 2 - 2
dashboard/src/components/DataTable.tsx

@@ -76,7 +76,7 @@ function DataTable<T>({
   };
 
   return (
-    <div className="bg-white rounded-xl shadow-sm border border-gray-200">
+    <div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200">
       {/* Header */}
       <div className="px-6 py-4 border-b border-gray-200">
         <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
@@ -149,7 +149,7 @@ function DataTable<T>({
                 {actions && <th className="px-6 py-3 w-12"></th>}
               </tr>
             </thead>
-            <tbody className="bg-white divide-y divide-gray-200">
+            <tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200">
               {data.map((item, index) => (
                 <tr key={index} className="hover:bg-gray-50">
                   {columns.map((column) => (

+ 17 - 16
dashboard/src/components/Layout.tsx

@@ -11,10 +11,10 @@ import {
   Menu,
   X,
   Mail,
-  Shield,
 } from 'lucide-react';
 import { useAuth } from '@/hooks/useAuth';
 import { clsx } from 'clsx';
+import ThemeSwitcher from '@/components/ThemeSwitcher';
 
 interface LayoutProps {
   children: ReactNode;
@@ -28,7 +28,6 @@ const navigation = [
   { name: 'API Keys', href: '/api-keys', icon: Key },
   { name: 'Email (SMTP)', href: '/settings/smtp', icon: Mail },
   { name: 'Email Templates', href: '/settings/email-templates', icon: Mail },
-  { name: 'RLS Policies', href: '/settings/rls', icon: Shield },
   { name: 'Settings', href: '/settings', icon: Settings },
 ];
 
@@ -47,7 +46,7 @@ export default function Layout({ children }: LayoutProps) {
   }
 
   return (
-    <div className="min-h-screen bg-gray-50 flex">
+    <div className="min-h-screen flex">
       {/* Mobile sidebar backdrop */}
       {sidebarOpen && (
         <div
@@ -59,15 +58,15 @@ export default function Layout({ children }: LayoutProps) {
       {/* Sidebar */}
       <div
         className={clsx(
-          'fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out lg:translate-x-0 lg:static lg:inset-0',
+          'fixed inset-y-0 left-0 z-50 w-64 bg-white dark:bg-gray-800 shadow-lg transform transition-transform duration-300 ease-in-out lg:translate-x-0 lg:static lg:inset-0',
           sidebarOpen ? 'translate-x-0' : '-translate-x-full'
         )}
       >
-        <div className="flex items-center justify-between h-16 px-6 border-b border-gray-200">
-          <h1 className="text-xl font-bold text-gray-900">SaaS Platform</h1>
+        <div className="flex items-center justify-between h-16 px-6 border-b dark:border-gray-700">
+          <h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">SaaS Platform</h1>
           <button
             onClick={() => setSidebarOpen(false)}
-            className="lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100"
+            className="lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-gray-300"
           >
             <X className="h-5 w-5" />
           </button>
@@ -84,8 +83,8 @@ export default function Layout({ children }: LayoutProps) {
                     className={clsx(
                       'flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors',
                       isActive
-                        ? 'bg-primary-100 text-primary-700 border-r-2 border-primary-700'
-                        : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
+                        ? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 border-r-2 border-primary-700 dark:border-primary-500'
+                        : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700/50'
                     )}
                   >
                     <item.icon className="mr-3 h-5 w-5" />
@@ -97,15 +96,16 @@ export default function Layout({ children }: LayoutProps) {
           </ul>
         </nav>
 
-        <div className="absolute bottom-0 left-0 right-0 p-4 border-t border-gray-200">
+        <div className="absolute bottom-0 left-0 right-0 p-4 border-t dark:border-gray-700">
           <div className="flex items-center">
             <div className="flex-1">
-              <p className="text-sm font-medium text-gray-900">{user?.email}</p>
-              <p className="text-xs text-gray-500">Administrator</p>
+              <p className="text-sm font-medium text-gray-900 dark:text-gray-100">{user?.email}</p>
+              <p className="text-xs text-gray-500 dark:text-gray-400">Administrator</p>
             </div>
+            <ThemeSwitcher />
             <button
               onClick={handleLogout}
-              className="ml-3 p-2 text-gray-400 hover:text-gray-500 hover:bg-gray-100 rounded-md"
+              className="ml-3 p-2 text-gray-400 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md"
               title="Logout"
             >
               <LogOut className="h-5 w-5" />
@@ -117,15 +117,16 @@ export default function Layout({ children }: LayoutProps) {
       {/* Main content */}
       <div className="flex-1 flex flex-col lg:ml-0">
         {/* Top bar */}
-        <header className="bg-white shadow-sm border-b border-gray-200 lg:hidden">
+        <header className="bg-white dark:bg-gray-800 shadow-sm border-b dark:border-gray-700 lg:hidden">
           <div className="flex items-center justify-between h-16 px-4">
             <button
               onClick={() => setSidebarOpen(true)}
-              className="p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100"
+              className="p-2 rounded-md text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
             >
               <Menu className="h-6 w-6" />
             </button>
-            <h1 className="text-lg font-semibold text-gray-900">Dashboard</h1>
+            <h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Dashboard</h1>
+            <ThemeSwitcher />
           </div>
         </header>
 

+ 3 - 3
dashboard/src/components/ScopePermissions.tsx

@@ -142,7 +142,7 @@ export default function ScopePermissions({ value, onChange }: ScopePermissionsPr
             className={`border rounded-lg overflow-hidden ${
               isResourceEnabled(resource.key as keyof Scopes)
                 ? 'border-primary-300 bg-primary-50'
-                : 'border-gray-200 bg-white'
+                : 'border-gray-200 bg-white dark:bg-gray-800'
             }`}
           >
             {/* Resource Header */}
@@ -196,7 +196,7 @@ export default function ScopePermissions({ value, onChange }: ScopePermissionsPr
                       className={`flex items-center justify-between px-3 py-2 text-sm rounded-lg border-2 transition-all ${
                         isEnabled
                           ? 'border-primary-500 bg-primary-100 text-primary-900'
-                          : 'border-gray-200 bg-white text-gray-700 hover:border-gray-300'
+                          : 'border-gray-200 bg-white dark:bg-gray-800 text-gray-700 hover:border-gray-300'
                       }`}
                     >
                       <span className="font-medium">{action.label}</span>
@@ -229,7 +229,7 @@ export default function ScopePermissions({ value, onChange }: ScopePermissionsPr
             return (
               <div
                 key={resource.key}
-                className="inline-flex items-center px-2 py-1 rounded-md text-xs bg-white border border-gray-200"
+                className="inline-flex items-center px-2 py-1 rounded-md text-xs bg-white dark:bg-gray-800 border border-gray-200"
               >
                 <span className="font-medium text-gray-900">{resource.label}:</span>
                 <span className="ml-1 text-gray-600">

+ 26 - 26
dashboard/src/components/TableDataModal.tsx

@@ -94,7 +94,7 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
     if (editingRow === row.id && !isSystemTable) {
       // Don't allow editing id, created_at, updated_at
       if (['id', 'created_at', 'updated_at'].includes(column.name)) {
-        return <span className="text-sm text-gray-500 font-mono">{formatValue(value)}</span>;
+        return <span className="text-sm text-gray-500 dark:text-gray-400 font-mono">{formatValue(value)}</span>;
       }
 
       return (
@@ -102,16 +102,16 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
           type="text"
           value={editValues[column.name] || ''}
           onChange={(e) => handleInputChange(column.name, e.target.value)}
-          className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500"
+          className="w-full px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-800 dark:text-gray-100"
         />
       );
     }
 
-    return <span className="text-sm text-gray-900 font-mono">{formatValue(value)}</span>;
+    return <span className="text-sm text-gray-900 dark:text-gray-100 font-mono">{formatValue(value)}</span>;
   };
 
   const formatValue = (value: any) => {
-    if (value === null) return <span className="text-gray-400">NULL</span>;
+    if (value === null) return <span className="text-gray-400 dark:text-gray-500">NULL</span>;
     if (value === true) return 'true';
     if (value === false) return 'false';
     if (typeof value === 'object') return JSON.stringify(value);
@@ -124,25 +124,25 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
       <div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
         <div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onClick={onClose} />
 
-        <div className="inline-block w-full max-w-6xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white rounded-lg shadow-xl">
+        <div className="inline-block w-full max-w-6xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
           {/* Header */}
-          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
+          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
             <div>
-              <h3 className="text-lg font-medium text-gray-900 flex items-center">
-                Table: <span className="ml-2 font-mono text-primary-600">{tableName}</span>
+              <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 flex items-center">
+                Table: <span className="ml-2 font-mono text-primary-600 dark:text-primary-400">{tableName}</span>
                 {isSystemTable && (
-                  <span className="ml-3 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
+                  <span className="ml-3 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-300">
                     Protected
                   </span>
                 )}
               </h3>
-              <p className="mt-1 text-sm text-gray-500">
+              <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                 {schema?.rowCount || 0} rows • {schema?.columns?.length || 0} columns
               </p>
             </div>
             <button
               onClick={onClose}
-              className="text-gray-400 hover:text-gray-500 focus:outline-none"
+              className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none"
             >
               <X className="w-6 h-6" />
             </button>
@@ -156,30 +156,30 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
                   <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
                 </div>
               ) : tableData?.data?.length > 0 ? (
-                <table className="min-w-full divide-y divide-gray-200">
-                  <thead className="bg-gray-50">
+                <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
+                  <thead className="bg-gray-50 dark:bg-gray-900">
                     <tr>
                       {schema?.columns?.map((column: any) => (
                         <th
                           key={column.name}
-                          className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
+                          className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
                         >
                           {column.name}
-                          <div className="text-xs font-normal text-gray-400 normal-case mt-1">
+                          <div className="text-xs font-normal text-gray-400 dark:text-gray-500 normal-case mt-1">
                             {column.type}
                           </div>
                         </th>
                       ))}
                       {!isSystemTable && (
-                        <th className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
+                        <th className="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                           Actions
                         </th>
                       )}
                     </tr>
                   </thead>
-                  <tbody className="bg-white divide-y divide-gray-200">
+                  <tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
                     {tableData.data.map((row: any) => (
-                      <tr key={row.id} className={editingRow === row.id ? 'bg-blue-50' : ''}>
+                      <tr key={row.id} className={editingRow === row.id ? 'bg-blue-50 dark:bg-blue-900/20' : ''}>
                         {schema?.columns?.map((column: any) => (
                           <td key={column.name} className="px-4 py-3 whitespace-nowrap">
                             {renderCellValue(column, row)}
@@ -191,14 +191,14 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
                               <div className="flex justify-end space-x-2">
                                 <button
                                   onClick={handleSave}
-                                  className="text-green-600 hover:text-green-900"
+                                  className="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300"
                                   title="Save"
                                 >
                                   <Save className="h-4 w-4" />
                                 </button>
                                 <button
                                   onClick={handleCancel}
-                                  className="text-gray-600 hover:text-gray-900"
+                                  className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
                                   title="Cancel"
                                 >
                                   <XCircle className="h-4 w-4" />
@@ -208,14 +208,14 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
                               <div className="flex justify-end space-x-2">
                                 <button
                                   onClick={() => handleEdit(row)}
-                                  className="text-primary-600 hover:text-primary-900"
+                                  className="text-primary-600 dark:text-primary-400 hover:text-primary-900 dark:hover:text-primary-300"
                                   title="Edit"
                                 >
                                   <Edit2 className="h-4 w-4" />
                                 </button>
                                 <button
                                   onClick={() => handleDelete(row.id)}
-                                  className="text-red-600 hover:text-red-900"
+                                  className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
                                   title="Delete"
                                 >
                                   <Trash2 className="h-4 w-4" />
@@ -229,7 +229,7 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
                   </tbody>
                 </table>
               ) : (
-                <div className="text-center py-8 text-gray-500">
+                <div className="text-center py-8 text-gray-500 dark:text-gray-400">
                   No data in this table
                 </div>
               )}
@@ -237,8 +237,8 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
 
             {/* Pagination */}
             {tableData?.pagination && tableData.pagination.totalPages > 1 && (
-              <div className="flex items-center justify-between mt-4 pt-4 border-t border-gray-200">
-                <div className="text-sm text-gray-700">
+              <div className="flex items-center justify-between mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
+                <div className="text-sm text-gray-700 dark:text-gray-300">
                   Page {tableData.pagination.page} of {tableData.pagination.totalPages}
                 </div>
                 <div className="flex space-x-2">
@@ -262,7 +262,7 @@ export default function TableDataModal({ tableName, isSystemTable, onClose }: Ta
           </div>
 
           {/* Footer */}
-          <div className="px-6 py-4 bg-gray-50 border-t border-gray-200 flex justify-end">
+          <div className="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-end">
             <button onClick={onClose} className="btn-secondary">
               Close
             </button>

+ 655 - 0
dashboard/src/components/TableSchemaEditor.tsx

@@ -0,0 +1,655 @@
+import { useState } from 'react';
+import { useQuery, useMutation, useQueryClient } from 'react-query';
+import { X, Plus, Edit2, Trash2, Save, XCircle, Check, AlertCircle } from 'lucide-react';
+import apiService from '@/services/api';
+import toast from 'react-hot-toast';
+
+interface TableSchemaEditorProps {
+  tableName: string;
+  isSystemTable: boolean;
+  onClose: () => void;
+}
+
+interface ColumnInfo {
+  name: string;
+  type: string;
+  nullable: boolean;
+  default: string | null;
+  isPrimaryKey: boolean;
+  isUnique: boolean;
+  maxLength?: number;
+}
+
+interface EditingColumn {
+  originalName: string;
+  name: string;
+  type: string;
+  nullable: boolean;
+  default: string;
+  dropDefault: boolean;
+}
+
+const DATA_TYPES = [
+  'TEXT',
+  'VARCHAR(255)',
+  'INTEGER',
+  'BIGINT',
+  'BOOLEAN',
+  'TIMESTAMP',
+  'TIMESTAMP WITH TIME ZONE',
+  'DATE',
+  'JSONB',
+  'UUID',
+  'DECIMAL',
+  'NUMERIC',
+  'REAL',
+  'DOUBLE PRECISION',
+  'SMALLINT',
+  'SERIAL',
+  'BIGSERIAL',
+];
+
+// Get default value suggestions based on column type
+const getDefaultValueSuggestions = (dataType: string): string[] => {
+  const type = dataType.toUpperCase();
+
+  if (type.includes('BOOLEAN')) {
+    return ['true', 'false'];
+  }
+
+  if (type.includes('INTEGER') || type.includes('BIGINT') || type.includes('SMALLINT') || type.includes('NUMERIC') || type.includes('DECIMAL') || type.includes('REAL') || type.includes('DOUBLE')) {
+    return ['0', '1', '-1', '100', '1000'];
+  }
+
+  if (type.includes('TIMESTAMP') || type.includes('DATE')) {
+    return ['CURRENT_TIMESTAMP', 'NOW()', 'CURRENT_DATE'];
+  }
+
+  if (type.includes('TEXT') || type.includes('VARCHAR')) {
+    return ["''", "'default'", "'N/A'", "'Unknown'"];
+  }
+
+  if (type.includes('UUID')) {
+    return ['uuid_generate_v4()', 'gen_random_uuid()'];
+  }
+
+  if (type.includes('JSONB') || type.includes('JSON')) {
+    return ["'{}'", "'[]'", "'{\"key\": \"value\"}'"];
+  }
+
+  if (type.includes('SERIAL') || type.includes('BIGSERIAL')) {
+    return []; // SERIAL types auto-generate, no defaults needed
+  }
+
+  return [];
+};
+
+export default function TableSchemaEditor({ tableName, isSystemTable, onClose }: TableSchemaEditorProps) {
+  const [editingColumn, setEditingColumn] = useState<EditingColumn | null>(null);
+  const [showAddColumn, setShowAddColumn] = useState(false);
+  const [newColumn, setNewColumn] = useState({
+    columnName: '',
+    dataType: 'TEXT',
+    nullable: true,
+    defaultValue: '',
+    unique: false,
+  });
+  const queryClient = useQueryClient();
+
+  // Fetch table schema
+  const { data: schema, isLoading } = useQuery(
+    ['table-schema', tableName],
+    () => apiService.getTableSchema(tableName)
+  );
+
+  // Add column mutation
+  const addColumnMutation = useMutation(
+    (data: any) => apiService.addTableColumn(tableName, data),
+    {
+      onSuccess: () => {
+        toast.success('Column added successfully');
+        queryClient.invalidateQueries(['table-schema', tableName]);
+        queryClient.invalidateQueries('database-tables');
+        setShowAddColumn(false);
+        setNewColumn({
+          columnName: '',
+          dataType: 'TEXT',
+          nullable: true,
+          defaultValue: '',
+          unique: false,
+        });
+      },
+      onError: (error: any) => {
+        toast.error(error.response?.data?.error || 'Failed to add column');
+      },
+    }
+  );
+
+  // Modify column mutation
+  const modifyColumnMutation = useMutation(
+    (data: { columnName: string; modifications: any }) =>
+      apiService.modifyTableColumn(tableName, data.columnName, data.modifications),
+    {
+      onSuccess: () => {
+        toast.success('Column modified successfully');
+        queryClient.invalidateQueries(['table-schema', tableName]);
+        queryClient.invalidateQueries('database-tables');
+        setEditingColumn(null);
+      },
+      onError: (error: any) => {
+        toast.error(error.response?.data?.error || 'Failed to modify column');
+      },
+    }
+  );
+
+  // Delete column mutation
+  const deleteColumnMutation = useMutation(
+    (columnName: string) => apiService.dropTableColumn(tableName, columnName),
+    {
+      onSuccess: () => {
+        toast.success('Column deleted successfully');
+        queryClient.invalidateQueries(['table-schema', tableName]);
+        queryClient.invalidateQueries('database-tables');
+      },
+      onError: (error: any) => {
+        toast.error(error.response?.data?.error || 'Failed to delete column');
+      },
+    }
+  );
+
+  const handleAddColumn = () => {
+    if (!newColumn.columnName || !newColumn.dataType) {
+      toast.error('Column name and data type are required');
+      return;
+    }
+    addColumnMutation.mutate(newColumn);
+  };
+
+  const handleEditColumn = (column: ColumnInfo) => {
+    setEditingColumn({
+      originalName: column.name,
+      name: column.name,
+      type: column.type,
+      nullable: column.nullable,
+      default: column.default || '',
+      dropDefault: false,
+    });
+  };
+
+  const handleSaveColumnEdit = () => {
+    if (!editingColumn) return;
+
+    const modifications: any = {};
+
+    if (editingColumn.name !== editingColumn.originalName) {
+      modifications.newColumnName = editingColumn.name;
+    }
+
+    const originalColumn = schema.columns.find((c: ColumnInfo) => c.name === editingColumn.originalName);
+    if (originalColumn) {
+      if (editingColumn.type !== originalColumn.type) {
+        modifications.dataType = editingColumn.type;
+      }
+      if (editingColumn.nullable !== originalColumn.nullable) {
+        modifications.nullable = editingColumn.nullable;
+      }
+      if (editingColumn.dropDefault) {
+        modifications.dropDefault = true;
+      } else if (editingColumn.default !== (originalColumn.default || '')) {
+        modifications.defaultValue = editingColumn.default;
+      }
+    }
+
+    if (Object.keys(modifications).length === 0) {
+      toast.error('No changes to save');
+      return;
+    }
+
+    modifyColumnMutation.mutate({
+      columnName: editingColumn.originalName,
+      modifications,
+    });
+  };
+
+  const handleDeleteColumn = (columnName: string) => {
+    if (window.confirm(`Are you sure you want to delete column "${columnName}"? This action cannot be undone.`)) {
+      deleteColumnMutation.mutate(columnName);
+    }
+  };
+
+  const formatColumnType = (column: ColumnInfo) => {
+    if (column.maxLength) {
+      return `${column.type}(${column.maxLength})`;
+    }
+    return column.type;
+  };
+
+  const isProtectedColumn = (columnName: string) => {
+    return ['id', 'created_at', 'updated_at'].includes(columnName.toLowerCase());
+  };
+
+  return (
+    <div className="fixed inset-0 z-50 overflow-y-auto">
+      <div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
+        <div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onClick={onClose} />
+
+        <div className="inline-block w-full max-w-6xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
+          {/* Header */}
+          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-gradient-to-r from-primary-50 dark:from-primary-900/20 to-white dark:to-gray-800">
+            <div>
+              <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center">
+                <Edit2 className="h-5 w-5 mr-2 text-primary-600 dark:text-primary-400" />
+                Edit Table Schema: <span className="ml-2 font-mono text-primary-600 dark:text-primary-400">{tableName}</span>
+                {isSystemTable && (
+                  <span className="ml-3 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-300">
+                    Protected
+                  </span>
+                )}
+              </h3>
+              <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
+                Add, modify, or remove columns from this table
+              </p>
+            </div>
+            <button
+              onClick={onClose}
+              className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none"
+            >
+              <X className="w-6 h-6" />
+            </button>
+          </div>
+
+          {/* Content */}
+          <div className="px-6 py-4 max-h-[70vh] overflow-y-auto">
+            {isLoading ? (
+              <div className="text-center py-8">
+                <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
+              </div>
+            ) : (
+              <>
+                {/* Add Column Form */}
+                {showAddColumn && !isSystemTable && (
+                  <div className="mb-6 p-4 bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-lg">
+                    <h4 className="text-md font-medium text-gray-900 dark:text-gray-100 mb-3 flex items-center">
+                      <Plus className="h-4 w-4 mr-2" />
+                      Add New Column
+                    </h4>
+                    <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+                      <div>
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Column Name *</label>
+                        <input
+                          type="text"
+                          value={newColumn.columnName}
+                          onChange={(e) => setNewColumn({ ...newColumn, columnName: e.target.value })}
+                          placeholder="e.g., email, age, description"
+                          className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-800 dark:text-gray-100"
+                        />
+                      </div>
+                      <div>
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Data Type *</label>
+                        <select
+                          value={newColumn.dataType}
+                          onChange={(e) => setNewColumn({ ...newColumn, dataType: e.target.value })}
+                          className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-800 dark:text-gray-100"
+                        >
+                          {DATA_TYPES.map((type) => (
+                            <option key={type} value={type}>
+                              {type}
+                            </option>
+                          ))}
+                        </select>
+                      </div>
+                      <div className="md:col-span-2">
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Default Value</label>
+                        <div className="flex space-x-2">
+                          {getDefaultValueSuggestions(newColumn.dataType).length > 0 ? (
+                            <>
+                              <select
+                                value={newColumn.defaultValue}
+                                onChange={(e) => setNewColumn({ ...newColumn, defaultValue: e.target.value })}
+                                className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
+                              >
+                                <option value="">-- No default --</option>
+                                {getDefaultValueSuggestions(newColumn.dataType).map((suggestion, idx) => (
+                                  <option key={idx} value={suggestion}>
+                                    {suggestion}
+                                  </option>
+                                ))}
+                                <option value="__custom__">Custom value...</option>
+                              </select>
+                              {newColumn.defaultValue === '__custom__' && (
+                                <input
+                                  type="text"
+                                  onChange={(e) => setNewColumn({ ...newColumn, defaultValue: e.target.value })}
+                                  placeholder="Enter custom value"
+                                  className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
+                                  autoFocus
+                                />
+                              )}
+                            </>
+                          ) : (
+                            <input
+                              type="text"
+                              value={newColumn.defaultValue}
+                              onChange={(e) => setNewColumn({ ...newColumn, defaultValue: e.target.value })}
+                              placeholder="Optional default value"
+                              className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
+                            />
+                          )}
+                        </div>
+                        <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
+                          {getDefaultValueSuggestions(newColumn.dataType).length > 0
+                            ? 'Select a common default value or choose custom'
+                            : 'Leave empty for NULL'}
+                        </p>
+                      </div>
+                      <div className="flex items-center space-x-4">
+                        <label className="flex items-center text-sm">
+                          <input
+                            type="checkbox"
+                            checked={!newColumn.nullable}
+                            onChange={(e) => setNewColumn({ ...newColumn, nullable: !e.target.checked })}
+                            className="rounded border-gray-300 dark:border-gray-600 text-primary-600 shadow-sm focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50"
+                          />
+                          <span className="ml-2 text-gray-700 dark:text-gray-300">NOT NULL</span>
+                        </label>
+                        <label className="flex items-center text-sm">
+                          <input
+                            type="checkbox"
+                            checked={newColumn.unique}
+                            onChange={(e) => setNewColumn({ ...newColumn, unique: e.target.checked })}
+                            className="rounded border-gray-300 dark:border-gray-600 text-primary-600 shadow-sm focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50"
+                          />
+                          <span className="ml-2 text-gray-700 dark:text-gray-300">UNIQUE</span>
+                        </label>
+                      </div>
+                    </div>
+                    <div className="mt-4 flex space-x-2">
+                      <button
+                        onClick={handleAddColumn}
+                        disabled={addColumnMutation.isLoading}
+                        className="btn-primary inline-flex items-center disabled:opacity-50"
+                      >
+                        <Plus className="h-4 w-4 mr-1" />
+                        {addColumnMutation.isLoading ? 'Adding...' : 'Add Column'}
+                      </button>
+                      <button
+                        onClick={() => setShowAddColumn(false)}
+                        className="btn-secondary"
+                      >
+                        Cancel
+                      </button>
+                    </div>
+                  </div>
+                )}
+
+                {/* Columns Table */}
+                <div className="overflow-x-auto">
+                  <div className="flex items-center justify-between mb-4">
+                    <h4 className="text-md font-medium text-gray-900 dark:text-gray-100">
+                      Columns ({schema?.columns?.length || 0})
+                    </h4>
+                    {!showAddColumn && !isSystemTable && (
+                      <button
+                        onClick={() => setShowAddColumn(true)}
+                        className="btn-primary inline-flex items-center text-sm"
+                      >
+                        <Plus className="h-4 w-4 mr-1" />
+                        Add Column
+                      </button>
+                    )}
+                  </div>
+
+                  <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg">
+                    <thead className="bg-gray-50 dark:bg-gray-900">
+                      <tr>
+                        <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                          Name
+                        </th>
+                        <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                          Type
+                        </th>
+                        <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                          Default
+                        </th>
+                        <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                          Constraints
+                        </th>
+                        {!isSystemTable && (
+                          <th className="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
+                            Actions
+                          </th>
+                        )}
+                      </tr>
+                    </thead>
+                    <tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
+                      {schema?.columns?.map((column: ColumnInfo) => (
+                        <tr
+                          key={column.name}
+                          className={editingColumn?.originalName === column.name ? 'bg-blue-50 dark:bg-blue-900/20' : ''}
+                        >
+                          {/* Name */}
+                          <td className="px-4 py-3">
+                            {editingColumn?.originalName === column.name ? (
+                              <input
+                                type="text"
+                                value={editingColumn.name}
+                                onChange={(e) =>
+                                  setEditingColumn({ ...editingColumn, name: e.target.value })
+                                }
+                                disabled={isProtectedColumn(column.name)}
+                                className="w-full px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
+                              />
+                            ) : (
+                              <div className="flex items-center">
+                                <span className="text-sm font-mono font-medium text-gray-900 dark:text-gray-100">
+                                  {column.name}
+                                </span>
+                                {isProtectedColumn(column.name) && (
+                                  <span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300">
+                                    Protected
+                                  </span>
+                                )}
+                              </div>
+                            )}
+                          </td>
+
+                          {/* Type */}
+                          <td className="px-4 py-3">
+                            {editingColumn?.originalName === column.name && !isProtectedColumn(column.name) ? (
+                              <select
+                                value={editingColumn.type}
+                                onChange={(e) =>
+                                  setEditingColumn({ ...editingColumn, type: e.target.value })
+                                }
+                                className="w-full px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
+                              >
+                                {DATA_TYPES.map((type) => (
+                                  <option key={type} value={type}>
+                                    {type}
+                                  </option>
+                                ))}
+                              </select>
+                            ) : (
+                              <span className="text-sm font-mono text-gray-700 dark:text-gray-300">
+                                {formatColumnType(column)}
+                              </span>
+                            )}
+                          </td>
+
+                          {/* Default */}
+                          <td className="px-4 py-3">
+                            {editingColumn?.originalName === column.name && !isProtectedColumn(column.name) ? (
+                              <div className="flex items-center space-x-2">
+                                {getDefaultValueSuggestions(editingColumn.type).length > 0 ? (
+                                  <select
+                                    value={editingColumn.default === '' && editingColumn.dropDefault ? '__none__' : editingColumn.default}
+                                    onChange={(e) => {
+                                      if (e.target.value === '__none__') {
+                                        setEditingColumn({ ...editingColumn, default: '', dropDefault: true });
+                                      } else if (e.target.value === '__custom__') {
+                                        setEditingColumn({ ...editingColumn, default: '', dropDefault: false });
+                                      } else {
+                                        setEditingColumn({ ...editingColumn, default: e.target.value, dropDefault: false });
+                                      }
+                                    }}
+                                    className="flex-1 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
+                                  >
+                                    <option value="__none__">-- No default --</option>
+                                    {getDefaultValueSuggestions(editingColumn.type).map((suggestion, idx) => (
+                                      <option key={idx} value={suggestion}>
+                                        {suggestion}
+                                      </option>
+                                    ))}
+                                    <option value="__custom__">Custom value...</option>
+                                  </select>
+                                ) : (
+                                  <input
+                                    type="text"
+                                    value={editingColumn.default}
+                                    onChange={(e) =>
+                                      setEditingColumn({ ...editingColumn, default: e.target.value, dropDefault: false })
+                                    }
+                                    placeholder="NULL"
+                                    className="flex-1 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
+                                  />
+                                )}
+                                <button
+                                  onClick={() =>
+                                    setEditingColumn({ ...editingColumn, default: '', dropDefault: true })
+                                  }
+                                  className="text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300"
+                                  title="Remove default"
+                                >
+                                  <XCircle className="h-4 w-4" />
+                                </button>
+                              </div>
+                            ) : (
+                              <span className="text-sm font-mono text-gray-500 dark:text-gray-400">
+                                {column.default || <span className="text-gray-400 dark:text-gray-500">NULL</span>}
+                              </span>
+                            )}
+                          </td>
+
+                          {/* Constraints */}
+                          <td className="px-4 py-3">
+                            {editingColumn?.originalName === column.name && !isProtectedColumn(column.name) ? (
+                              <label className="flex items-center text-sm">
+                                <input
+                                  type="checkbox"
+                                  checked={!editingColumn.nullable}
+                                  onChange={(e) =>
+                                    setEditingColumn({ ...editingColumn, nullable: !e.target.checked })
+                                  }
+                                  className="rounded border-gray-300 dark:border-gray-600 text-primary-600"
+                                />
+                                <span className="ml-2 text-gray-700 dark:text-gray-300">NOT NULL</span>
+                              </label>
+                            ) : (
+                              <div className="flex flex-wrap gap-1">
+                                {column.isPrimaryKey && (
+                                  <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-300">
+                                    PRIMARY KEY
+                                  </span>
+                                )}
+                                {column.isUnique && !column.isPrimaryKey && (
+                                  <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-300">
+                                    UNIQUE
+                                  </span>
+                                )}
+                                {!column.nullable && (
+                                  <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-300">
+                                    NOT NULL
+                                  </span>
+                                )}
+                              </div>
+                            )}
+                          </td>
+
+                          {/* Actions */}
+                          {!isSystemTable && (
+                            <td className="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
+                              {editingColumn?.originalName === column.name ? (
+                                <div className="flex justify-end space-x-2">
+                                  <button
+                                    onClick={handleSaveColumnEdit}
+                                    className="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300"
+                                    title="Save changes"
+                                    disabled={modifyColumnMutation.isLoading}
+                                  >
+                                    <Save className="h-4 w-4" />
+                                  </button>
+                                  <button
+                                    onClick={() => setEditingColumn(null)}
+                                    className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
+                                    title="Cancel"
+                                  >
+                                    <XCircle className="h-4 w-4" />
+                                  </button>
+                                </div>
+                              ) : (
+                                <div className="flex justify-end space-x-2">
+                                  <button
+                                    onClick={() => handleEditColumn(column)}
+                                    className="text-primary-600 dark:text-primary-400 hover:text-primary-900 dark:hover:text-primary-300"
+                                    title="Edit column"
+                                    disabled={isProtectedColumn(column.name)}
+                                  >
+                                    <Edit2 className="h-4 w-4" />
+                                  </button>
+                                  <button
+                                    onClick={() => handleDeleteColumn(column.name)}
+                                    className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
+                                    title="Delete column"
+                                    disabled={isProtectedColumn(column.name)}
+                                  >
+                                    <Trash2 className="h-4 w-4" />
+                                  </button>
+                                </div>
+                              )}
+                            </td>
+                          )}
+                        </tr>
+                      ))}
+                    </tbody>
+                  </table>
+
+                  {schema?.columns?.length === 0 && (
+                    <div className="text-center py-8 text-sm text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-900 rounded-lg mt-2">
+                      No columns found in this table
+                    </div>
+                  )}
+                </div>
+
+                {/* Information Box */}
+                <div className="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
+                  <div className="flex">
+                    <AlertCircle className="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5" />
+                    <div className="ml-3">
+                      <h4 className="text-sm font-medium text-blue-900 dark:text-blue-300">Important Notes</h4>
+                      <ul className="mt-2 text-sm text-blue-800 dark:text-blue-300 list-disc list-inside space-y-1">
+                        <li>Protected columns (id, created_at, updated_at) cannot be modified or deleted</li>
+                        <li>Changing column types may result in data loss if the conversion is not compatible</li>
+                        <li>Deleting a column will permanently remove all data in that column</li>
+                        <li>Adding a NOT NULL constraint to existing columns requires a default value</li>
+                      </ul>
+                    </div>
+                  </div>
+                </div>
+              </>
+            )}
+          </div>
+
+          {/* Footer */}
+          <div className="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center">
+            <div className="text-sm text-gray-600 dark:text-gray-400">
+              <span className="font-medium">{schema?.rowCount || 0}</span> rows in this table
+            </div>
+            <button onClick={onClose} className="btn-secondary">
+              Close
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 20 - 0
dashboard/src/components/ThemeSwitcher.tsx

@@ -0,0 +1,20 @@
+import { Moon, Sun } from 'lucide-react';
+import { useTheme } from '@/contexts/ThemeContext';
+
+export default function ThemeSwitcher() {
+  const { theme, toggleTheme } = useTheme();
+
+  return (
+    <button
+      onClick={toggleTheme}
+      className="p-2 rounded-md transition-colors hover:bg-gray-100"
+      title={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
+    >
+      {theme === 'light' ? (
+        <Moon className="h-5 w-5" />
+      ) : (
+        <Sun className="h-5 w-5" />
+      )}
+    </button>
+  );
+}

+ 83 - 0
dashboard/src/contexts/ThemeContext.tsx

@@ -0,0 +1,83 @@
+import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
+
+type Theme = 'light' | 'dark';
+
+interface ThemeContextType {
+  theme: Theme;
+  toggleTheme: () => void;
+  setTheme: (theme: Theme) => void;
+}
+
+const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
+
+export function useTheme() {
+  const context = useContext(ThemeContext);
+  if (!context) {
+    throw new Error('useTheme must be used within a ThemeProvider');
+  }
+  return context;
+}
+
+interface ThemeProviderProps {
+  children: ReactNode;
+}
+
+export function ThemeProvider({ children }: ThemeProviderProps) {
+  const [theme, setThemeState] = useState<Theme>(() => {
+    // Check localStorage first, then system preference
+    if (typeof window !== 'undefined') {
+      const stored = localStorage.getItem('theme') as Theme | null;
+      if (stored) {
+        return stored;
+      }
+      // Check system preference
+      if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+        return 'dark';
+      }
+    }
+    return 'light';
+  });
+
+  useEffect(() => {
+    const root = window.document.documentElement;
+    
+    // Remove previous theme class
+    root.classList.remove('light', 'dark');
+    
+    // Add new theme class
+    root.classList.add(theme);
+    
+    // Store in localStorage
+    localStorage.setItem('theme', theme);
+  }, [theme]);
+
+  // Listen for system theme changes
+  useEffect(() => {
+    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+    
+    const handleChange = () => {
+      // Only auto-change if user hasn't manually set a preference
+      const stored = localStorage.getItem('theme');
+      if (!stored) {
+        setThemeState(mediaQuery.matches ? 'dark' : 'light');
+      }
+    };
+
+    mediaQuery.addEventListener('change', handleChange);
+    return () => mediaQuery.removeEventListener('change', handleChange);
+  }, []);
+
+  const toggleTheme = () => {
+    setThemeState(prev => prev === 'light' ? 'dark' : 'light');
+  };
+
+  const setTheme = (newTheme: Theme) => {
+    setThemeState(newTheme);
+  };
+
+  return (
+    <ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
+      {children}
+    </ThemeContext.Provider>
+  );
+}

+ 33 - 33
dashboard/src/pages/ApiKeys.tsx

@@ -23,12 +23,12 @@ const columns = [
     title: 'API Key',
     render: (value: string, apiKey: ApiKey) => (
       <div className="flex items-center">
-        <div className="flex-shrink-0 h-8 w-8 bg-primary-100 rounded-lg flex items-center justify-center">
-          <Key className="h-4 w-4 text-primary-600" />
+        <div className="flex-shrink-0 h-8 w-8 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center">
+          <Key className="h-4 w-4 text-primary-600 dark:text-primary-400" />
         </div>
         <div className="ml-3">
-          <p className="text-sm font-medium text-gray-900">{value}</p>
-          <p className="text-xs text-gray-500 font-mono">{apiKey.prefix}***</p>
+          <p className="text-sm font-medium text-gray-900 dark:text-gray-100">{value}</p>
+          <p className="text-xs text-gray-500 dark:text-gray-400 font-mono">{apiKey.prefix}***</p>
         </div>
       </div>
     ),
@@ -39,7 +39,7 @@ const columns = [
     render: (value: any) => {
       // Handle invalid or missing permissions
       if (!value) {
-        return <span className="text-sm text-gray-500">No scopes</span>;
+        return <span className="text-sm text-gray-500 dark:text-gray-400">No scopes</span>;
       }
 
       let enabledScopes: string[] = [];
@@ -68,13 +68,13 @@ const columns = [
             enabledScopes.map((scope, index) => (
               <span
                 key={index}
-                className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
+                className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-300"
               >
                 {scope}
               </span>
             ))
           ) : (
-            <span className="text-sm text-gray-500">No scopes</span>
+            <span className="text-sm text-gray-500 dark:text-gray-400">No scopes</span>
           )}
         </div>
       );
@@ -84,8 +84,8 @@ const columns = [
     key: 'expires_at',
     title: 'Expires',
     render: (value: string) => (
-      <div className="flex items-center text-sm text-gray-900">
-        <Calendar className="h-4 w-4 text-gray-400 mr-1" />
+      <div className="flex items-center text-sm text-gray-900 dark:text-gray-100">
+        <Calendar className="h-4 w-4 text-gray-400 dark:text-gray-500 mr-1" />
         {value ? format(new Date(value), 'MMM d, yyyy') : 'Never'}
       </div>
     ),
@@ -95,9 +95,9 @@ const columns = [
     title: 'Status',
     render: (value: string) => {
       const colors = {
-        active: 'bg-green-100 text-green-800',
-        expired: 'bg-red-100 text-red-800',
-        revoked: 'bg-gray-100 text-gray-800',
+        active: 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-300',
+        expired: 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-300',
+        revoked: 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300',
       };
       return (
         <span
@@ -114,7 +114,7 @@ const columns = [
     key: 'created_at',
     title: 'Created',
     render: (value: string) => (
-      <div className="text-sm text-gray-900">
+      <div className="text-sm text-gray-900 dark:text-gray-100">
         {format(new Date(value), 'MMM d, yyyy')}
       </div>
     ),
@@ -200,8 +200,8 @@ export default function ApiKeys() {
     <div>
       <div className="mb-8 flex items-center justify-between">
         <div>
-          <h1 className="text-3xl font-bold text-gray-900">API Keys</h1>
-          <p className="mt-2 text-gray-600">
+          <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">API Keys</h1>
+          <p className="mt-2 text-gray-600 dark:text-gray-400">
             Manage API keys for external service integration
           </p>
         </div>
@@ -251,14 +251,14 @@ export default function ApiKeys() {
               }}
             />
 
-            <div className="inline-block w-full max-w-4xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white rounded-lg shadow-xl">
+            <div className="inline-block w-full max-w-4xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
               {/* Header */}
-              <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
+              <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
                 <div>
-                  <h3 className="text-lg font-medium text-gray-900">
+                  <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
                     {createdKey ? 'API Key Created' : 'Create New API Key'}
                   </h3>
-                  <p className="mt-1 text-sm text-gray-500">
+                  <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                     {createdKey
                       ? 'Save this key securely - it will not be shown again'
                       : 'Define the name, scopes, and expiration for your API key'}
@@ -270,7 +270,7 @@ export default function ApiKeys() {
                     setCreatedKey(null);
                     setNewKeyData({ name: '', permissions: {}, expiresInDays: 0 });
                   }}
-                  className="text-gray-400 hover:text-gray-500 focus:outline-none"
+                  className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none"
                 >
                   <X className="w-6 h-6" />
                 </button>
@@ -282,7 +282,7 @@ export default function ApiKeys() {
                   <div className="space-y-6">
                     {/* Name */}
                     <div>
-                      <label className="block text-sm font-medium text-gray-700 mb-2">
+                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                         API Key Name *
                       </label>
                       <input
@@ -291,14 +291,14 @@ export default function ApiKeys() {
                         onChange={(e) =>
                           setNewKeyData({ ...newKeyData, name: e.target.value })
                         }
-                        className="w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
+                        className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent dark:bg-gray-800 dark:text-gray-100"
                         placeholder="e.g., Production API Key, Mobile App Key"
                       />
                     </div>
 
                     {/* Expiration */}
                     <div>
-                      <label className="block text-sm font-medium text-gray-700 mb-2">
+                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                         Expiration
                       </label>
                       <select
@@ -309,7 +309,7 @@ export default function ApiKeys() {
                             expiresInDays: parseInt(e.target.value),
                           })
                         }
-                        className="w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
+                        className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent dark:bg-gray-800 dark:text-gray-100"
                       >
                         <option value={0}>Never expires</option>
                         <option value={7}>7 days</option>
@@ -321,7 +321,7 @@ export default function ApiKeys() {
 
                     {/* Scope Permissions */}
                     <div>
-                      <label className="block text-sm font-medium text-gray-700 mb-3">
+                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
                         Resource Scopes *
                       </label>
                       <ScopePermissions
@@ -334,11 +334,11 @@ export default function ApiKeys() {
                   </div>
                 ) : (
                   <div className="space-y-4">
-                    <div className="p-4 bg-green-50 border border-green-200 rounded-lg">
-                      <p className="text-sm text-green-800 mb-3 font-medium">
+                    <div className="p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
+                      <p className="text-sm text-green-800 dark:text-green-300 mb-3 font-medium">
                         ✓ API key created successfully!
                       </p>
-                      <label className="block text-sm font-medium text-gray-700 mb-2">
+                      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                         Your API Key
                       </label>
                       <div className="flex items-center">
@@ -346,19 +346,19 @@ export default function ApiKeys() {
                           type="text"
                           value={createdKey}
                           readOnly
-                          className="flex-1 px-3 py-2 border border-gray-300 rounded-lg font-mono text-sm bg-white"
+                          className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg font-mono text-sm bg-white dark:bg-gray-800 dark:text-gray-100"
                         />
                         <button
                           onClick={() => copyToClipboard(createdKey)}
-                          className="ml-2 p-2 text-gray-400 hover:text-gray-600 bg-white border border-gray-300 rounded-lg"
+                          className="ml-2 p-2 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg"
                           title="Copy to clipboard"
                         >
                           <Copy className="h-5 w-5" />
                         </button>
                       </div>
                     </div>
-                    <div className="p-4 bg-red-50 border border-red-200 rounded-lg">
-                      <p className="text-sm text-red-800">
+                    <div className="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
+                      <p className="text-sm text-red-800 dark:text-red-300">
                         <strong>Important:</strong> Save this API key now. For security reasons, it will not be
                         displayed again. Store it in a secure location like a password manager or
                         environment variables.
@@ -369,7 +369,7 @@ export default function ApiKeys() {
               </div>
 
               {/* Footer */}
-              <div className="px-6 py-4 bg-gray-50 border-t border-gray-200 flex justify-end space-x-3">
+              <div className="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3">
                 {!createdKey ? (
                   <>
                     <button

+ 17 - 17
dashboard/src/pages/Applications.tsx

@@ -23,12 +23,12 @@ const columns = [
     title: 'Application',
     render: (value: string, app: Application) => (
       <div className="flex items-center">
-        <div className="flex-shrink-0 h-10 w-10 bg-primary-100 rounded-lg flex items-center justify-center">
-          <Code className="h-6 w-6 text-primary-600" />
+        <div className="flex-shrink-0 h-10 w-10 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center">
+          <Code className="h-6 w-6 text-primary-600 dark:text-primary-400" />
         </div>
         <div className="ml-4">
-          <p className="text-sm font-medium text-gray-900">{value}</p>
-          <p className="text-sm text-gray-500">/{app.slug}</p>
+          <p className="text-sm font-medium text-gray-900 dark:text-gray-100">{value}</p>
+          <p className="text-sm text-gray-500 dark:text-gray-400">/{app.slug}</p>
         </div>
       </div>
     ),
@@ -38,10 +38,10 @@ const columns = [
     title: 'Status',
     render: (value: string) => {
       const colors = {
-        active: 'bg-green-100 text-green-800',
-        inactive: 'bg-gray-100 text-gray-800',
-        building: 'bg-yellow-100 text-yellow-800',
-        failed: 'bg-red-100 text-red-800',
+        active: 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-300',
+        inactive: 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300',
+        building: 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-300',
+        failed: 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-300',
       };
       return (
         <span
@@ -58,7 +58,7 @@ const columns = [
     key: 'created_at',
     title: 'Created',
     render: (value: string) => (
-      <div className="text-sm text-gray-900">
+      <div className="text-sm text-gray-900 dark:text-gray-100">
         {format(new Date(value), 'MMM d, yyyy')}
       </div>
     ),
@@ -67,7 +67,7 @@ const columns = [
     key: 'last_deployed',
     title: 'Last Deployed',
     render: (value: string) => (
-      <div className="text-sm text-gray-900">
+      <div className="text-sm text-gray-900 dark:text-gray-100">
         {value ? format(new Date(value), 'MMM d, HH:mm') : 'Never'}
       </div>
     ),
@@ -124,21 +124,21 @@ export default function Applications() {
     <div className="flex items-center space-x-2">
       <button
         onClick={() => navigate(`/applications/${app.id}/edit`)}
-        className="text-primary-600 hover:text-primary-900"
+        className="text-primary-600 dark:text-primary-400 hover:text-primary-900 dark:hover:text-primary-300"
         title="Edit"
       >
         <Edit className="h-4 w-4" />
       </button>
       <button
         onClick={() => navigate(`/applications/${app.id}/deployments`)}
-        className="text-gray-600 hover:text-gray-900"
+        className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
         title="View Deployments"
       >
         <GitBranch className="h-4 w-4" />
       </button>
       <button
         onClick={() => navigate(`/applications/${app.id}/settings`)}
-        className="text-gray-600 hover:text-gray-900"
+        className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
         title="Settings"
       >
         <Settings className="h-4 w-4" />
@@ -146,14 +146,14 @@ export default function Applications() {
       <button
         onClick={() => handleDeploy(app)}
         disabled={app.status === 'building' || deployMutation.isLoading}
-        className="text-green-600 hover:text-green-900 disabled:opacity-50"
+        className="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 disabled:opacity-50"
         title="Deploy"
       >
         <Play className="h-4 w-4" />
       </button>
       <button
         onClick={() => handleDelete(app)}
-        className="text-red-600 hover:text-red-900"
+        className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
         title="Delete"
       >
         <Trash2 className="h-4 w-4" />
@@ -165,8 +165,8 @@ export default function Applications() {
     <div>
       <div className="mb-8 flex items-center justify-between">
         <div>
-          <h1 className="text-3xl font-bold text-gray-900">Applications</h1>
-          <p className="mt-2 text-gray-600">
+          <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Applications</h1>
+          <p className="mt-2 text-gray-600 dark:text-gray-400">
             Deploy and manage TypeScript applications
           </p>
         </div>

+ 17 - 17
dashboard/src/pages/Dashboard.tsx

@@ -22,14 +22,14 @@ interface ChartData {
 }
 
 const QuickStat = ({ title, value, icon: Icon, color }: any) => (
-  <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
+  <div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border dark:border-gray-700 p-6">
     <div className="flex items-center">
       <div className={`flex-shrink-0 p-3 rounded-lg ${color}`}>
         <Icon className="h-6 w-6 text-white" />
       </div>
       <div className="ml-5">
-        <p className="text-sm font-medium text-gray-500">{title}</p>
-        <p className="text-2xl font-bold text-gray-900">{value}</p>
+        <p className="text-sm font-medium text-gray-500 dark:text-gray-400">{title}</p>
+        <p className="text-2xl font-bold text-gray-900 dark:text-gray-100">{value}</p>
       </div>
     </div>
   </div>
@@ -63,8 +63,8 @@ export default function Dashboard() {
   return (
     <div>
       <div className="mb-8">
-        <h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
-        <p className="mt-2 text-gray-600">
+        <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Dashboard</h1>
+        <p className="mt-2 text-gray-600 dark:text-gray-400">
           Welcome to your SaaS platform management dashboard
         </p>
       </div>
@@ -77,10 +77,10 @@ export default function Dashboard() {
         {/* Growth Chart */}
         <div className="card">
           <div className="flex items-center justify-between mb-4">
-            <h3 className="text-lg font-medium text-gray-900">User Growth</h3>
+            <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">User Growth</h3>
             <div className="flex items-center">
               <div className="h-3 w-3 bg-green-500 rounded-full"></div>
-              <span className="ml-2 text-sm text-gray-600">Active Users</span>
+              <span className="ml-2 text-sm text-gray-600 dark:text-gray-400">Active Users</span>
             </div>
           </div>
           <ResponsiveContainer width="100%" height={300}>
@@ -103,15 +103,15 @@ export default function Dashboard() {
         {/* Applications & Deployments */}
         <div className="card">
           <div className="flex items-center justify-between mb-4">
-            <h3 className="text-lg font-medium text-gray-900">Activity</h3>
+            <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">Activity</h3>
             <div className="flex items-center gap-4">
               <div className="flex items-center">
                 <div className="h-3 w-3 bg-blue-500 rounded-full"></div>
-                <span className="ml-2 text-sm text-gray-600">Apps</span>
+                <span className="ml-2 text-sm text-gray-600 dark:text-gray-400">Apps</span>
               </div>
               <div className="flex items-center">
                 <div className="h-3 w-3 bg-purple-500 rounded-full"></div>
-                <span className="ml-2 text-sm text-gray-600">Deploys</span>
+                <span className="ml-2 text-sm text-gray-600 dark:text-gray-400">Deploys</span>
               </div>
             </div>
           </div>
@@ -131,11 +131,11 @@ export default function Dashboard() {
       {/* Recent Activity */}
       <div className="mt-8">
         <div className="card">
-          <h3 className="text-lg font-medium text-gray-900 mb-4">
+          <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
             Recent Activity
           </h3>
           {activitiesLoading ? (
-            <div className="text-center py-4 text-gray-500">Loading...</div>
+            <div className="text-center py-4 text-gray-500 dark:text-gray-400">Loading...</div>
           ) : activities && activities.length > 0 ? (
             <div className="space-y-4">
               {activities.map((activity: any, index: number) => {
@@ -163,7 +163,7 @@ export default function Dashboard() {
                 return (
                   <div
                     key={index}
-                    className="flex items-center justify-between py-3 border-b border-gray-100 last:border-0"
+                    className="flex items-center justify-between py-3 border-b border-gray-100 dark:border-gray-700 last:border-0"
                   >
                     <div className="flex items-center">
                       <div
@@ -172,16 +172,16 @@ export default function Dashboard() {
                         )}`}
                       ></div>
                       <div>
-                        <p className="text-sm font-medium text-gray-900">
+                        <p className="text-sm font-medium text-gray-900 dark:text-gray-100">
                           {activity.message}
                           {activity.details?.name && (
-                            <span className="text-gray-600">
+                            <span className="text-gray-600 dark:text-gray-400">
                               {' '}"
                               {activity.details.name}"
                             </span>
                           )}
                         </p>
-                        <p className="text-xs text-gray-500">
+                        <p className="text-xs text-gray-500 dark:text-gray-400">
                           {formatTimeAgo(activity.time)}
                           {activity.userEmail && ` • ${activity.userEmail}`}
                         </p>
@@ -192,7 +192,7 @@ export default function Dashboard() {
               })}
             </div>
           ) : (
-            <div className="text-center py-8 text-gray-500">
+            <div className="text-center py-8 text-gray-500 dark:text-gray-400">
               No recent activity
             </div>
           )}

+ 86 - 57
dashboard/src/pages/Database.tsx

@@ -19,6 +19,7 @@ import apiService from '@/services/api';
 import DataTable from '@/components/DataTable';
 import TableDataModal from '@/components/TableDataModal';
 import CreateTableModal from '@/components/CreateTableModal';
+import TableSchemaEditor from '@/components/TableSchemaEditor';
 import { DatabaseTable } from '@/types';
 import toast from 'react-hot-toast';
 
@@ -28,7 +29,7 @@ const columns = [
     title: 'Table Name',
     render: (value: string) => (
       <div className="flex items-center">
-        <Table className="h-4 w-4 text-gray-400 mr-2" />
+        <Table className="h-4 w-4 text-gray-400 dark:text-gray-500 mr-2" />
         <span className="font-mono text-sm">{value}</span>
       </div>
     ),
@@ -37,7 +38,7 @@ const columns = [
     key: 'columnCount',
     title: 'Columns',
     render: (value: number) => (
-      <span className="text-sm text-gray-900">
+      <span className="text-sm text-gray-900 dark:text-gray-100">
         {value?.toLocaleString() || 0}
       </span>
     ),
@@ -46,7 +47,7 @@ const columns = [
     key: 'rowCount',
     title: 'Rows',
     render: (value: number) => (
-      <span className="text-sm text-gray-900">
+      <span className="text-sm text-gray-900 dark:text-gray-100">
         {value?.toLocaleString() || 0}
       </span>
     ),
@@ -75,6 +76,9 @@ export default function DatabaseTables() {
   const [systemTablesExpanded, setSystemTablesExpanded] = useState(false);
   const [showRLSModal, setShowRLSModal] = useState(false);
   const [rlsTableName, setRlsTableName] = useState<string>('');
+  const [showSchemaEditor, setShowSchemaEditor] = useState(false);
+  const [schemaTableName, setSchemaTableName] = useState<string>('');
+  const [schemaIsSystemTable, setSchemaIsSystemTable] = useState(false);
   const queryClient = useQueryClient();
 
   const { data, isLoading } = useQuery<DatabaseTablesResponse>(
@@ -170,6 +174,12 @@ export default function DatabaseTables() {
     setShowRLSModal(true);
   };
 
+  const handleEditSchema = (table: SimpleTable) => {
+    setSchemaTableName(table.name);
+    setSchemaIsSystemTable(table.isSystemTable || false);
+    setShowSchemaEditor(true);
+  };
+
   const renderActions = (table: SimpleTable) => (
     <div className="flex items-center space-x-2">
       <button
@@ -179,6 +189,13 @@ export default function DatabaseTables() {
       >
         <Eye className="h-4 w-4" />
       </button>
+      <button
+        onClick={() => handleEditSchema(table)}
+        className="text-indigo-600 hover:text-indigo-900"
+        title="Edit Schema"
+      >
+        <Edit className="h-4 w-4" />
+      </button>
       {!table.isSystemTable && (
         <>
           <button
@@ -217,8 +234,8 @@ export default function DatabaseTables() {
   return (
     <div>
       <div className="mb-8">
-        <h1 className="text-3xl font-bold text-gray-900">Database</h1>
-        <p className="mt-2 text-gray-600">
+        <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Database</h1>
+        <p className="mt-2 text-gray-600 dark:text-gray-400">
           Manage and explore database tables and schemas
         </p>
       </div>
@@ -229,8 +246,8 @@ export default function DatabaseTables() {
           <div className="flex items-center">
             <DatabaseIcon className="h-8 w-8 text-blue-600" />
             <div className="ml-4">
-              <p className="text-sm font-medium text-gray-500">Total Tables</p>
-              <p className="text-2xl font-semibold text-gray-900">
+              <p className="text-sm font-medium text-gray-500 dark:text-gray-400">Total Tables</p>
+              <p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
                 {data?.totalTables || 0}
               </p>
             </div>
@@ -241,8 +258,8 @@ export default function DatabaseTables() {
           <div className="flex items-center">
             <Table className="h-8 w-8 text-green-600" />
             <div className="ml-4">
-              <p className="text-sm font-medium text-gray-500">System Tables</p>
-              <p className="text-2xl font-semibold text-gray-900">
+              <p className="text-sm font-medium text-gray-500 dark:text-gray-400">System Tables</p>
+              <p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
                 {systemTables.length}
               </p>
             </div>
@@ -253,8 +270,8 @@ export default function DatabaseTables() {
           <div className="flex items-center">
             <Table className="h-8 w-8 text-purple-600" />
             <div className="ml-4">
-              <p className="text-sm font-medium text-gray-500">User Tables</p>
-              <p className="text-2xl font-semibold text-gray-900">
+              <p className="text-sm font-medium text-gray-500 dark:text-gray-400">User Tables</p>
+              <p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
                 {userTables.length}
               </p>
             </div>
@@ -265,8 +282,8 @@ export default function DatabaseTables() {
           <div className="flex items-center">
             <Eye className="h-8 w-8 text-orange-600" />
             <div className="ml-4">
-              <p className="text-sm font-medium text-gray-500">Total Rows</p>
-              <p className="text-2xl font-semibold text-gray-900">
+              <p className="text-sm font-medium text-gray-500 dark:text-gray-400">Total Rows</p>
+              <p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
                 {allTables.reduce((acc, t) => acc + (t.rowCount || 0), 0).toLocaleString() || 0}
               </p>
             </div>
@@ -277,7 +294,7 @@ export default function DatabaseTables() {
       {/* User Tables */}
       <div className="mb-6">
         <div className="flex items-center justify-between mb-4">
-          <h2 className="text-xl font-semibold text-gray-900">Tables</h2>
+          <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Tables</h2>
           <button
             onClick={() => setShowCreateModal(true)}
             className="btn-primary inline-flex items-center"
@@ -302,37 +319,37 @@ export default function DatabaseTables() {
         <div className="mt-6">
           <button
             onClick={() => setSystemTablesExpanded(!systemTablesExpanded)}
-            className="w-full flex items-center justify-between p-4 bg-amber-50 border border-amber-200 rounded-lg hover:bg-amber-100 transition-colors"
+            className="w-full flex items-center justify-between p-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg hover:bg-amber-100 dark:hover:bg-amber-900/30 transition-colors"
           >
             <div className="flex items-center space-x-3">
-              <Shield className="h-5 w-5 text-amber-600" />
+              <Shield className="h-5 w-5 text-amber-600 dark:text-amber-400" />
               <div className="text-left">
-                <h3 className="text-sm font-semibold text-amber-900">
+                <h3 className="text-sm font-semibold text-amber-900 dark:text-amber-300">
                   System Tables (Protected)
                 </h3>
-                <p className="text-xs text-amber-700">
+                <p className="text-xs text-amber-700 dark:text-amber-400">
                   {systemTables.length} tables • Read-only access
                 </p>
               </div>
             </div>
             <div className="flex items-center space-x-2">
               {systemTablesExpanded ? (
-                <ChevronDown className="h-5 w-5 text-amber-600" />
+                <ChevronDown className="h-5 w-5 text-amber-600 dark:text-amber-400" />
               ) : (
-                <ChevronRight className="h-5 w-5 text-amber-600" />
+                <ChevronRight className="h-5 w-5 text-amber-600 dark:text-amber-400" />
               )}
             </div>
           </button>
 
           {systemTablesExpanded && (
-            <div className="mt-4 border border-amber-200 rounded-lg overflow-hidden animate-fadeIn">
-              <div className="bg-amber-50 px-4 py-2 border-b border-amber-200">
-                <p className="text-xs text-amber-800">
+            <div className="mt-4 border border-amber-200 dark:border-amber-800 rounded-lg overflow-hidden animate-fadeIn">
+              <div className="bg-amber-50 dark:bg-amber-900/20 px-4 py-2 border-b border-amber-200 dark:border-amber-800">
+                <p className="text-xs text-amber-800 dark:text-amber-300">
                   ⚠️ System tables contain platform configuration and cannot be modified directly.
                   Use the admin interface to manage these resources.
                 </p>
               </div>
-              <div className="bg-white">
+              <div className="bg-white dark:bg-gray-800">
                 <DataTable
                   data={systemTables}
                   columns={columns}
@@ -353,7 +370,7 @@ export default function DatabaseTables() {
       {/* Query Interface */}
       <div className="mt-8">
         <div className="card">
-          <h3 className="text-lg font-medium text-gray-900 mb-4">
+          <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
             SQL Query Interface
           </h3>
           <div className="space-y-4">
@@ -362,7 +379,7 @@ export default function DatabaseTables() {
               value={sqlQuery}
               onChange={(e) => setSqlQuery(e.target.value)}
               placeholder="Enter your SQL query here... (SELECT, INSERT, UPDATE allowed)"
-              className="w-full px-3 py-2 border border-gray-300 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
+              className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent dark:bg-gray-800 dark:text-gray-100"
             />
             <div className="flex justify-end space-x-3">
               <button
@@ -386,21 +403,21 @@ export default function DatabaseTables() {
 
             {/* Query Results */}
             {queryResult && (
-              <div className="mt-4 border border-gray-200 rounded-lg overflow-hidden">
-                <div className="bg-gray-50 px-4 py-2 border-b border-gray-200">
-                  <h4 className="text-sm font-medium text-gray-900">Query Result</h4>
+              <div className="mt-4 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
+                <div className="bg-gray-50 dark:bg-gray-800 px-4 py-2 border-b border-gray-200 dark:border-gray-700">
+                  <h4 className="text-sm font-medium text-gray-900 dark:text-gray-100">Query Result</h4>
                 </div>
                 <div className="p-4">
                   {queryResult.error ? (
-                    <div className="text-sm text-red-600 font-mono">{queryResult.error}</div>
+                    <div className="text-sm text-red-600 dark:text-red-400 font-mono">{queryResult.error}</div>
                   ) : (
                     <div>
-                      <div className="mb-2 text-sm text-gray-600">
+                      <div className="mb-2 text-sm text-gray-600 dark:text-gray-400">
                         {queryResult.rowCount} row(s) affected • Command: {queryResult.command}
                       </div>
                       {queryResult.rows && queryResult.rows.length > 0 && (
                         <div className="overflow-x-auto">
-                          <pre className="text-xs font-mono bg-gray-50 p-3 rounded border border-gray-200 max-h-96 overflow-y-auto">
+                          <pre className="text-xs font-mono bg-gray-50 dark:bg-gray-900 p-3 rounded border border-gray-200 dark:border-gray-700 max-h-96 overflow-y-auto">
                             {JSON.stringify(queryResult.rows, null, 2)}
                           </pre>
                         </div>
@@ -436,6 +453,18 @@ export default function DatabaseTables() {
           }}
         />
       )}
+
+      {showSchemaEditor && (
+        <TableSchemaEditor
+          tableName={schemaTableName}
+          isSystemTable={schemaIsSystemTable}
+          onClose={() => {
+            setShowSchemaEditor(false);
+            setSchemaTableName('');
+            setSchemaIsSystemTable(false);
+          }}
+        />
+      )}
     </div>
   );
 }
@@ -517,12 +546,12 @@ function RLSManagementModal({
       <div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
         <div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onClick={onClose} />
 
-        <div className="inline-block w-full max-w-3xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white rounded-lg shadow-xl">
-          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
-            <h3 className="text-lg font-medium text-gray-900">
+        <div className="inline-block w-full max-w-3xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
+          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
+            <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
               Manage RLS Policies - {tableName}
             </h3>
-            <button onClick={onClose} className="text-gray-400 hover:text-gray-500">
+            <button onClick={onClose} className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400">
               ×
             </button>
           </div>
@@ -535,11 +564,11 @@ function RLSManagementModal({
             ) : (
               <>
                 {/* RLS Status */}
-                <div className="mb-4 p-4 bg-gray-50 rounded-lg">
+                <div className="mb-4 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
                   <div className="flex items-center justify-between">
                     <div>
-                      <h4 className="font-medium text-gray-900">Row Level Security</h4>
-                      <p className="text-sm text-gray-600">
+                      <h4 className="font-medium text-gray-900 dark:text-gray-100">Row Level Security</h4>
+                      <p className="text-sm text-gray-600 dark:text-gray-400">
                         {data?.rlsEnabled ? 'Enabled' : 'Disabled'}
                       </p>
                     </div>
@@ -558,7 +587,7 @@ function RLSManagementModal({
                 {/* Policies List */}
                 <div className="mb-4">
                   <div className="flex items-center justify-between mb-3">
-                    <h4 className="font-medium text-gray-900">Policies ({data?.policies?.length || 0})</h4>
+                    <h4 className="font-medium text-gray-900 dark:text-gray-100">Policies ({data?.policies?.length || 0})</h4>
                     <button
                       onClick={() => setShowCreatePolicy(!showCreatePolicy)}
                       className="btn-secondary inline-flex items-center text-sm"
@@ -569,24 +598,24 @@ function RLSManagementModal({
                   </div>
 
                   {showCreatePolicy && (
-                    <form onSubmit={handleCreatePolicy} className="mb-4 p-4 bg-blue-50 rounded-lg space-y-3">
+                    <form onSubmit={handleCreatePolicy} className="mb-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg space-y-3">
                       <div>
-                        <label className="block text-sm font-medium text-gray-700">Policy Name</label>
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Policy Name</label>
                         <input
                           type="text"
                           value={policyData.policyName}
                           onChange={(e) => setPolicyData({ ...policyData, policyName: e.target.value })}
-                          className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
+                          className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm dark:bg-gray-800 dark:text-gray-100"
                           placeholder="e.g., users_read_own_data"
                           required
                         />
                       </div>
                       <div>
-                        <label className="block text-sm font-medium text-gray-700">Command</label>
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Command</label>
                         <select
                           value={policyData.command}
                           onChange={(e) => setPolicyData({ ...policyData, command: e.target.value })}
-                          className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
+                          className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm dark:bg-gray-800 dark:text-gray-100"
                         >
                           <option value="ALL">ALL</option>
                           <option value="SELECT">SELECT</option>
@@ -596,22 +625,22 @@ function RLSManagementModal({
                         </select>
                       </div>
                       <div>
-                        <label className="block text-sm font-medium text-gray-700">USING Expression</label>
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">USING Expression</label>
                         <textarea
                           rows={2}
                           value={policyData.using}
                           onChange={(e) => setPolicyData({ ...policyData, using: e.target.value })}
-                          className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md font-mono text-sm"
+                          className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md font-mono text-sm dark:bg-gray-800 dark:text-gray-100"
                           placeholder="e.g., user_id = current_user_id()"
                         />
                       </div>
                       <div>
-                        <label className="block text-sm font-medium text-gray-700">WITH CHECK Expression</label>
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">WITH CHECK Expression</label>
                         <textarea
                           rows={2}
                           value={policyData.withCheck}
                           onChange={(e) => setPolicyData({ ...policyData, withCheck: e.target.value })}
-                          className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md font-mono text-sm"
+                          className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md font-mono text-sm dark:bg-gray-800 dark:text-gray-100"
                           placeholder="e.g., user_id = current_user_id()"
                         />
                       </div>
@@ -629,19 +658,19 @@ function RLSManagementModal({
                   {data?.policies?.length > 0 ? (
                     <div className="space-y-2">
                       {data.policies.map((policy: any) => (
-                        <div key={policy.name} className="p-3 bg-white border border-gray-200 rounded-lg">
+                        <div key={policy.name} className="p-3 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg">
                           <div className="flex items-start justify-between">
                             <div className="flex-1">
-                              <h5 className="font-medium text-gray-900 font-mono text-sm">{policy.name}</h5>
+                              <h5 className="font-medium text-gray-900 dark:text-gray-100 font-mono text-sm">{policy.name}</h5>
                               <div className="mt-1 flex items-center space-x-2">
-                                <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
+                                <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-300">
                                   {policy.command}
                                 </span>
                               </div>
                               {policy.using && (
                                 <div className="mt-2">
-                                  <span className="text-xs font-medium text-gray-700">USING:</span>
-                                  <pre className="mt-1 text-xs bg-gray-50 p-2 rounded border border-gray-200 overflow-x-auto">
+                                  <span className="text-xs font-medium text-gray-700 dark:text-gray-300">USING:</span>
+                                  <pre className="mt-1 text-xs bg-gray-50 dark:bg-gray-800 p-2 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">
                                     {policy.using}
                                   </pre>
                                 </div>
@@ -653,7 +682,7 @@ function RLSManagementModal({
                                   deletePolicyMutation.mutate(policy.name);
                                 }
                               }}
-                              className="ml-3 text-red-600 hover:text-red-900"
+                              className="ml-3 text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
                               title="Delete Policy"
                             >
                               <Trash2 className="h-4 w-4" />
@@ -663,7 +692,7 @@ function RLSManagementModal({
                       ))}
                     </div>
                   ) : (
-                    <div className="text-center py-8 text-sm text-gray-500 bg-gray-50 rounded-lg">
+                    <div className="text-center py-8 text-sm text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-900 rounded-lg">
                       No policies defined. Create one to get started.
                     </div>
                   )}
@@ -672,7 +701,7 @@ function RLSManagementModal({
             )}
           </div>
 
-          <div className="px-6 py-4 bg-gray-50 border-t border-gray-200 flex justify-end">
+          <div className="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-end">
             <button onClick={onClose} className="btn-secondary">
               Close
             </button>

+ 40 - 40
dashboard/src/pages/EmailTemplates.tsx

@@ -68,8 +68,8 @@ export default function EmailTemplates() {
     <div>
       <div className="mb-8 flex items-center justify-between">
         <div>
-          <h1 className="text-3xl font-bold text-gray-900">Email Templates</h1>
-          <p className="mt-2 text-gray-600">
+          <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Email Templates</h1>
+          <p className="mt-2 text-gray-600 dark:text-gray-400">
             Manage email templates for automated notifications
           </p>
         </div>
@@ -85,8 +85,8 @@ export default function EmailTemplates() {
           <div className="flex items-center">
             <Mail className="h-8 w-8 text-blue-600" />
             <div className="ml-4">
-              <p className="text-sm font-medium text-gray-500">Total Templates</p>
-              <p className="text-2xl font-semibold text-gray-900">{templates.length}</p>
+              <p className="text-sm font-medium text-gray-500 dark:text-gray-400">Total Templates</p>
+              <p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">{templates.length}</p>
             </div>
           </div>
         </div>
@@ -95,8 +95,8 @@ export default function EmailTemplates() {
           <div className="flex items-center">
             <Code className="h-8 w-8 text-purple-600" />
             <div className="ml-4">
-              <p className="text-sm font-medium text-gray-500">System Templates</p>
-              <p className="text-2xl font-semibold text-gray-900">
+              <p className="text-sm font-medium text-gray-500 dark:text-gray-400">System Templates</p>
+              <p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
                 {templates.filter(t => t.is_system).length}
               </p>
             </div>
@@ -107,8 +107,8 @@ export default function EmailTemplates() {
           <div className="flex items-center">
             <Edit className="h-8 w-8 text-green-600" />
             <div className="ml-4">
-              <p className="text-sm font-medium text-gray-500">Custom Templates</p>
-              <p className="text-2xl font-semibold text-gray-900">
+              <p className="text-sm font-medium text-gray-500 dark:text-gray-400">Custom Templates</p>
+              <p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
                 {templates.filter(t => !t.is_system).length}
               </p>
             </div>
@@ -128,7 +128,7 @@ export default function EmailTemplates() {
               <div className="flex items-start justify-between">
                 <div className="flex-1">
                   <div className="flex items-center space-x-3">
-                    <h3 className="text-lg font-semibold text-gray-900 font-mono">
+                    <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 font-mono">
                       {template.name}
                     </h3>
                     {template.is_system && (
@@ -144,24 +144,24 @@ export default function EmailTemplates() {
                   </div>
 
                   {template.description && (
-                    <p className="mt-1 text-sm text-gray-600">{template.description}</p>
+                    <p className="mt-1 text-sm text-gray-600 dark:text-gray-400">{template.description}</p>
                   )}
 
                   <div className="mt-2 flex items-center space-x-4 text-sm">
                     <div>
-                      <span className="font-medium text-gray-700">Subject:</span>
-                      <span className="ml-2 text-gray-600">{template.subject}</span>
+                      <span className="font-medium text-gray-700 dark:text-gray-300">Subject:</span>
+                      <span className="ml-2 text-gray-600 dark:text-gray-400">{template.subject}</span>
                     </div>
                   </div>
 
                   {template.variables && template.variables.length > 0 && (
                     <div className="mt-2">
-                      <span className="text-xs font-medium text-gray-700">Variables: </span>
+                      <span className="text-xs font-medium text-gray-700 dark:text-gray-300">Variables: </span>
                       <div className="inline-flex flex-wrap gap-1 mt-1">
                         {template.variables.map((variable, idx) => (
                           <span
                             key={idx}
-                            className="inline-flex items-center px-2 py-0.5 rounded text-xs font-mono bg-gray-100 text-gray-700"
+                            className="inline-flex items-center px-2 py-0.5 rounded text-xs font-mono bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300"
                           >
                             {'{'}
                             {variable}
@@ -176,7 +176,7 @@ export default function EmailTemplates() {
                 <div className="flex items-center space-x-2 ml-4">
                   <button
                     onClick={() => handleEdit(template)}
-                    className="p-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-md"
+                    className="p-2 text-gray-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md"
                     title="Edit template"
                   >
                     <Edit className="h-4 w-4" />
@@ -184,7 +184,7 @@ export default function EmailTemplates() {
                   {!template.is_system && (
                     <button
                       onClick={() => handleDelete(template)}
-                      className="p-2 text-gray-600 hover:text-red-600 hover:bg-red-50 rounded-md"
+                      className="p-2 text-gray-600 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md"
                       title="Delete template"
                     >
                       <Trash2 className="h-4 w-4" />
@@ -197,9 +197,9 @@ export default function EmailTemplates() {
 
           {templates.length === 0 && (
             <div className="text-center py-12">
-              <Mail className="mx-auto h-12 w-12 text-gray-400" />
-              <h3 className="mt-2 text-sm font-medium text-gray-900">No templates</h3>
-              <p className="mt-1 text-sm text-gray-500">
+              <Mail className="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
+              <h3 className="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">No templates</h3>
+              <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                 Get started by creating your first email template.
               </p>
               <div className="mt-6">
@@ -289,12 +289,12 @@ function TemplateModal({
       <div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
         <div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onClick={onClose} />
 
-        <div className="inline-block w-full max-w-4xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white rounded-lg shadow-xl">
-          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
-            <h3 className="text-lg font-medium text-gray-900">
+        <div className="inline-block w-full max-w-4xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
+          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
+            <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
               {template ? 'Edit Template' : 'Create Template'}
             </h3>
-            <button onClick={onClose} className="text-gray-400 hover:text-gray-500">
+            <button onClick={onClose} className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400">
               ×
             </button>
           </div>
@@ -303,18 +303,18 @@ function TemplateModal({
             {/* Template Name */}
             {!template && (
               <div>
-                <label className="block text-sm font-medium text-gray-700">
+                <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                   Template Name *
                 </label>
                 <input
                   type="text"
                   value={formData.name}
                   onChange={(e) => setFormData({ ...formData, name: e.target.value })}
-                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono"
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
                   placeholder="e.g., welcome_email, password_reset"
                   required
                 />
-                <p className="mt-1 text-xs text-gray-500">
+                <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
                   Lowercase letters, numbers, and underscores only
                 </p>
               </div>
@@ -322,28 +322,28 @@ function TemplateModal({
 
             {/* Subject */}
             <div>
-              <label className="block text-sm font-medium text-gray-700">
+              <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 Email Subject *
               </label>
               <input
                 type="text"
                 value={formData.subject}
                 onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
-                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
                 required
               />
             </div>
 
             {/* Description */}
             <div>
-              <label className="block text-sm font-medium text-gray-700">
+              <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 Description
               </label>
               <input
                 type="text"
                 value={formData.description}
                 onChange={(e) => setFormData({ ...formData, description: e.target.value })}
-                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
                 placeholder="Brief description of this template"
               />
             </div>
@@ -351,17 +351,17 @@ function TemplateModal({
             {/* Variables */}
             {!template && (
               <div>
-                <label className="block text-sm font-medium text-gray-700">
+                <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                   Variables (comma-separated)
                 </label>
                 <input
                   type="text"
                   value={formData.variables}
                   onChange={(e) => setFormData({ ...formData, variables: e.target.value })}
-                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono"
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono dark:bg-gray-800 dark:text-gray-100"
                   placeholder="e.g., user_name, user_email, app_name"
                 />
-                <p className="mt-1 text-xs text-gray-500">
+                <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
                   Use these in your template as {'{variable_name}'}
                 </p>
               </div>
@@ -369,28 +369,28 @@ function TemplateModal({
 
             {/* Plain Text Body */}
             <div>
-              <label className="block text-sm font-medium text-gray-700">
+              <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 Plain Text Body *
               </label>
               <textarea
                 rows={6}
                 value={formData.body}
                 onChange={(e) => setFormData({ ...formData, body: e.target.value })}
-                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono text-sm"
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono text-sm dark:bg-gray-800 dark:text-gray-100"
                 required
               />
             </div>
 
             {/* HTML Body */}
             <div>
-              <label className="block text-sm font-medium text-gray-700">
+              <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 HTML Body (optional)
               </label>
               <textarea
                 rows={8}
                 value={formData.html_body}
                 onChange={(e) => setFormData({ ...formData, html_body: e.target.value })}
-                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono text-sm"
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 font-mono text-sm dark:bg-gray-800 dark:text-gray-100"
               />
             </div>
 
@@ -400,15 +400,15 @@ function TemplateModal({
                 type="checkbox"
                 checked={formData.enabled}
                 onChange={(e) => setFormData({ ...formData, enabled: e.target.checked })}
-                className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
+                className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded"
               />
-              <label className="ml-2 text-sm text-gray-700">
+              <label className="ml-2 text-sm text-gray-700 dark:text-gray-300">
                 Template enabled
               </label>
             </div>
 
             {/* Actions */}
-            <div className="flex justify-end space-x-3 pt-4 border-t border-gray-200">
+            <div className="flex justify-end space-x-3 pt-4 border-t border-gray-200 dark:border-gray-700">
               <button type="button" onClick={onClose} className="btn-secondary">
                 Cancel
               </button>

+ 18 - 12
dashboard/src/pages/Login.tsx

@@ -2,6 +2,7 @@ import { useState } from 'react';
 import { Link, useNavigate } from 'react-router-dom';
 import { useAuth } from '@/hooks/useAuth';
 import { Eye, EyeOff, Shield } from 'lucide-react';
+import ThemeSwitcher from '@/components/ThemeSwitcher';
 
 export default function Login() {
   const [email, setEmail] = useState('');
@@ -20,27 +21,32 @@ export default function Login() {
   };
 
   return (
-    <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
+    <div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8 relative">
+      {/* Theme Switcher */}
+      <div className="absolute top-4 right-4">
+        <ThemeSwitcher />
+      </div>
+      
       <div className="max-w-md w-full space-y-8">
         {/* Header */}
         <div className="text-center">
-          <div className="mx-auto h-16 w-16 rounded-full bg-primary-100 flex items-center justify-center">
-            <Shield className="h-8 w-8 text-primary-600" />
+          <div className="mx-auto h-16 w-16 rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center">
+            <Shield className="h-8 w-8 text-primary-600 dark:text-primary-400" />
           </div>
-          <h2 className="mt-6 text-3xl font-extrabold text-gray-900">
+          <h2 className="mt-6 text-3xl font-extrabold text-gray-900 dark:text-gray-100">
             SaaS Platform
           </h2>
-          <p className="mt-2 text-sm text-gray-600">
+          <p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
             Sign in to manage your platform
           </p>
         </div>
 
         {/* Login Form */}
-        <div className="bg-white py-8 px-6 shadow-xl rounded-xl">
+        <div className="bg-white dark:bg-gray-800 py-8 px-6 shadow-xl rounded-xl">
           <form className="space-y-6" onSubmit={handleSubmit}>
             {/* Email */}
             <div>
-              <label htmlFor="email" className="block text-sm font-medium text-gray-700">
+              <label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 Email address
               </label>
               <input
@@ -58,7 +64,7 @@ export default function Login() {
 
             {/* Password */}
             <div>
-              <label htmlFor="password" className="block text-sm font-medium text-gray-700">
+              <label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 Password
               </label>
               <div className="mt-1 relative">
@@ -104,11 +110,11 @@ export default function Login() {
           </form>
 
           {/* Demo Credentials */}
-          <div className="mt-6 border-t border-gray-200 pt-6">
-            <p className="text-center text-sm text-gray-600">
+          <div className="mt-6 border-t dark:border-gray-700 pt-6">
+            <p className="text-center text-sm text-gray-600 dark:text-gray-400">
               Demo Credentials:
             </p>
-            <div className="mt-2 text-xs text-gray-500 text-center">
+            <div className="mt-2 text-xs text-gray-500 dark:text-gray-400 text-center">
               <p>Email: admin@example.com</p>
               <p>Password: demo_password</p>
             </div>
@@ -117,7 +123,7 @@ export default function Login() {
 
         {/* Footer */}
         <div className="text-center">
-          <p className="text-sm text-gray-500">
+          <p className="text-sm text-gray-500 dark:text-gray-400">
             Don't have an account?{' '}
             <Link
               to="/register"

+ 185 - 12
dashboard/src/pages/RLSPolicies.tsx

@@ -215,7 +215,7 @@ export default function RLSPolicies() {
                         {table.policies.map((policy) => (
                           <div
                             key={policy.name}
-                            className="bg-white border border-gray-200 rounded-lg p-4"
+                            className="bg-white dark:bg-gray-800 border border-gray-200 rounded-lg p-4"
                           >
                             <div className="flex items-start justify-between mb-3">
                               <div>
@@ -314,6 +314,77 @@ export default function RLSPolicies() {
   );
 }
 
+// RLS Policy Templates
+const RLS_TEMPLATES = [
+  {
+    name: 'User Owns Record',
+    description: 'User can only access their own records',
+    command: 'ALL',
+    using: 'user_id = current_user_id()',
+    withCheck: 'user_id = current_user_id()',
+  },
+  {
+    name: 'Admin Full Access',
+    description: 'Admins have full access to all records',
+    command: 'ALL',
+    using: 'is_admin()',
+    withCheck: 'is_admin()',
+  },
+  {
+    name: 'Public Read Only',
+    description: 'Everyone can read, no one can modify',
+    command: 'SELECT',
+    using: 'true',
+    withCheck: '',
+  },
+  {
+    name: 'Authenticated Users Read',
+    description: 'Any authenticated user can read',
+    command: 'SELECT',
+    using: 'auth.user_id() IS NOT NULL',
+    withCheck: '',
+  },
+  {
+    name: 'Organization Members',
+    description: 'Members of the same organization',
+    command: 'ALL',
+    using: 'organization_id = current_user_organization_id()',
+    withCheck: 'organization_id = current_user_organization_id()',
+  },
+  {
+    name: 'Public Data',
+    description: 'Records marked as public are visible to everyone',
+    command: 'SELECT',
+    using: 'is_public = true',
+    withCheck: '',
+  },
+  {
+    name: 'Tenant Isolation',
+    description: 'Users can only access data from their tenant',
+    command: 'ALL',
+    using: 'tenant_id = current_tenant_id()',
+    withCheck: 'tenant_id = current_tenant_id()',
+  },
+];
+
+// Common expression snippets for autocomplete
+const EXPRESSION_SNIPPETS = [
+  'current_user_id()',
+  'current_user_email()',
+  'is_admin()',
+  'auth.user_id()',
+  'auth.email()',
+  'user_id = current_user_id()',
+  'organization_id = current_user_organization_id()',
+  'tenant_id = current_tenant_id()',
+  'is_public = true',
+  'created_by = current_user_id()',
+  'owner_id = current_user_id()',
+  'auth.user_id() IS NOT NULL',
+  'role IN (\'admin\', \'moderator\')',
+  'true',
+];
+
 // Create Policy Modal Component
 function CreatePolicyModal({
   tableName,
@@ -331,6 +402,10 @@ function CreatePolicyModal({
     using: '',
     withCheck: '',
   });
+  const [showUsingSuggestions, setShowUsingSuggestions] = useState(false);
+  const [showCheckSuggestions, setShowCheckSuggestions] = useState(false);
+  const [usingFilteredSnippets, setUsingFilteredSnippets] = useState(EXPRESSION_SNIPPETS);
+  const [checkFilteredSnippets, setCheckFilteredSnippets] = useState(EXPRESSION_SNIPPETS);
   const queryClient = useQueryClient();
 
   const createMutation = useMutation(
@@ -347,6 +422,49 @@ function CreatePolicyModal({
     }
   );
 
+  const handleApplyTemplate = (template: typeof RLS_TEMPLATES[0]) => {
+    setFormData({
+      ...formData,
+      command: template.command,
+      using: template.using,
+      withCheck: template.withCheck,
+      policyName: formData.policyName || template.name.toLowerCase().replace(/\s+/g, '_'),
+    });
+  };
+
+  const handleUsingChange = (value: string) => {
+    setFormData({ ...formData, using: value });
+    // Filter snippets based on input
+    const filtered = EXPRESSION_SNIPPETS.filter((snippet) =>
+      snippet.toLowerCase().includes(value.toLowerCase())
+    );
+    setUsingFilteredSnippets(filtered);
+    setShowUsingSuggestions(value.length > 0 && filtered.length > 0);
+  };
+
+  const handleCheckChange = (value: string) => {
+    setFormData({ ...formData, withCheck: value });
+    // Filter snippets based on input
+    const filtered = EXPRESSION_SNIPPETS.filter((snippet) =>
+      snippet.toLowerCase().includes(value.toLowerCase())
+    );
+    setCheckFilteredSnippets(filtered);
+    setShowCheckSuggestions(value.length > 0 && filtered.length > 0);
+  };
+
+  const insertSnippet = (field: 'using' | 'withCheck', snippet: string) => {
+    const currentValue = formData[field];
+    const newValue = currentValue ? `${currentValue} ${snippet}` : snippet;
+
+    if (field === 'using') {
+      setFormData({ ...formData, using: newValue });
+      setShowUsingSuggestions(false);
+    } else {
+      setFormData({ ...formData, withCheck: newValue });
+      setShowCheckSuggestions(false);
+    }
+  };
+
   const handleSubmit = (e: React.FormEvent) => {
     e.preventDefault();
 
@@ -366,7 +484,7 @@ function CreatePolicyModal({
           onClick={onClose}
         />
 
-        <div className="inline-block w-full max-w-2xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white rounded-lg shadow-xl">
+        <div className="inline-block w-full max-w-2xl my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
           <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
             <h3 className="text-lg font-medium text-gray-900">
               Create RLS Policy
@@ -380,6 +498,33 @@ function CreatePolicyModal({
           </div>
 
           <form onSubmit={handleSubmit} className="px-6 py-4 space-y-4">
+            {/* Policy Templates */}
+            <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
+              <label className="block text-sm font-medium text-blue-900 mb-2">
+                Quick Start Templates
+              </label>
+              <p className="text-xs text-blue-700 mb-3">
+                Select a template to quickly set up common RLS patterns
+              </p>
+              <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
+                {RLS_TEMPLATES.map((template) => (
+                  <button
+                    key={template.name}
+                    type="button"
+                    onClick={() => handleApplyTemplate(template)}
+                    className="text-left p-3 bg-white dark:bg-gray-800 border border-blue-200 rounded-lg hover:border-blue-400 hover:bg-blue-50 transition-colors"
+                  >
+                    <div className="text-sm font-medium text-gray-900">
+                      {template.name}
+                    </div>
+                    <div className="text-xs text-gray-600 mt-1">
+                      {template.description}
+                    </div>
+                  </button>
+                ))}
+              </div>
+            </div>
+
             {/* Table Name */}
             <div>
               <label className="block text-sm font-medium text-gray-700 mb-2">
@@ -440,40 +585,68 @@ function CreatePolicyModal({
             </div>
 
             {/* USING Expression */}
-            <div>
+            <div className="relative">
               <label className="block text-sm font-medium text-gray-700 mb-2">
                 USING Expression
               </label>
               <textarea
                 rows={3}
                 value={formData.using}
-                onChange={(e) =>
-                  setFormData({ ...formData, using: e.target.value })
-                }
+                onChange={(e) => handleUsingChange(e.target.value)}
+                onFocus={() => formData.using && setShowUsingSuggestions(true)}
+                onBlur={() => setTimeout(() => setShowUsingSuggestions(false), 200)}
                 className="w-full px-3 py-2 border border-gray-300 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
                 placeholder="e.g., user_id = current_user_id() OR is_admin()"
               />
+              {showUsingSuggestions && usingFilteredSnippets.length > 0 && (
+                <div className="absolute z-10 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-300 rounded-lg shadow-lg max-h-48 overflow-y-auto">
+                  {usingFilteredSnippets.map((snippet, idx) => (
+                    <button
+                      key={idx}
+                      type="button"
+                      onClick={() => insertSnippet('using', snippet)}
+                      className="w-full text-left px-3 py-2 hover:bg-gray-100 font-mono text-sm text-gray-700 border-b border-gray-100 last:border-b-0"
+                    >
+                      {snippet}
+                    </button>
+                  ))}
+                </div>
+              )}
               <p className="mt-1 text-xs text-gray-500">
-                Boolean expression to filter rows visible to the current user
+                Boolean expression to filter rows visible to the current user. Start typing for suggestions.
               </p>
             </div>
 
             {/* WITH CHECK Expression */}
-            <div>
+            <div className="relative">
               <label className="block text-sm font-medium text-gray-700 mb-2">
                 WITH CHECK Expression
               </label>
               <textarea
                 rows={3}
                 value={formData.withCheck}
-                onChange={(e) =>
-                  setFormData({ ...formData, withCheck: e.target.value })
-                }
+                onChange={(e) => handleCheckChange(e.target.value)}
+                onFocus={() => formData.withCheck && setShowCheckSuggestions(true)}
+                onBlur={() => setTimeout(() => setShowCheckSuggestions(false), 200)}
                 className="w-full px-3 py-2 border border-gray-300 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
                 placeholder="e.g., user_id = current_user_id()"
               />
+              {showCheckSuggestions && checkFilteredSnippets.length > 0 && (
+                <div className="absolute z-10 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-300 rounded-lg shadow-lg max-h-48 overflow-y-auto">
+                  {checkFilteredSnippets.map((snippet, idx) => (
+                    <button
+                      key={idx}
+                      type="button"
+                      onClick={() => insertSnippet('withCheck', snippet)}
+                      className="w-full text-left px-3 py-2 hover:bg-gray-100 font-mono text-sm text-gray-700 border-b border-gray-100 last:border-b-0"
+                    >
+                      {snippet}
+                    </button>
+                  ))}
+                </div>
+              )}
               <p className="mt-1 text-xs text-gray-500">
-                Boolean expression to verify new/updated rows (for INSERT/UPDATE)
+                Boolean expression to verify new/updated rows (for INSERT/UPDATE). Start typing for suggestions.
               </p>
             </div>
 

+ 37 - 37
dashboard/src/pages/Settings.tsx

@@ -108,7 +108,7 @@ export default function Settings() {
   const renderGeneralTab = () => (
     <div className="space-y-6">
       <div>
-        <label className="block text-sm font-medium text-gray-700">
+        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
           Platform Name
         </label>
         <input
@@ -119,7 +119,7 @@ export default function Settings() {
       </div>
 
       <div>
-        <label className="block text-sm font-medium text-gray-700">
+        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
           Platform Domain
         </label>
         <input
@@ -130,7 +130,7 @@ export default function Settings() {
       </div>
 
       <div>
-        <label className="block text-sm font-medium text-gray-700">
+        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
           Administrator Email
         </label>
         <input
@@ -141,7 +141,7 @@ export default function Settings() {
       </div>
 
       <div>
-        <label className="block text-sm font-medium text-gray-700">
+        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
           Session Timeout (seconds)
         </label>
         <input
@@ -160,9 +160,9 @@ export default function Settings() {
           <input
             type="checkbox"
             {...register('enable_registration')}
-            className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
+            className="rounded border-gray-300 dark:border-gray-600 text-primary-600 focus:ring-primary-500"
           />
-          <span className="ml-2 text-sm text-gray-700">
+          <span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
             Enable User Registration
           </span>
         </label>
@@ -171,16 +171,16 @@ export default function Settings() {
           <input
             type="checkbox"
             {...register('require_email_verification')}
-            className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
+            className="rounded border-gray-300 dark:border-gray-600 text-primary-600 focus:ring-primary-500"
           />
-          <span className="ml-2 text-sm text-gray-700">
+          <span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
             Require Email Verification
           </span>
         </label>
       </div>
 
       <div>
-        <label className="block text-sm font-medium text-gray-700">
+        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
           Default User Role
         </label>
         <select
@@ -192,13 +192,13 @@ export default function Settings() {
         </select>
       </div>
 
-      <div className="pt-4 border-t border-gray-200">
-        <h4 className="text-sm font-medium text-gray-900 mb-4">
+      <div className="pt-4 border-t border-gray-200 dark:border-gray-700">
+        <h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-4">
           Password Policy
         </h4>
         <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
           <div>
-            <label className="block text-sm font-medium text-gray-700">
+            <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
               Minimum Length
             </label>
             <input
@@ -208,7 +208,7 @@ export default function Settings() {
             />
           </div>
           <div>
-            <label className="block text-sm font-medium text-gray-700">
+            <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
               Password Expiry (days)
             </label>
             <input
@@ -225,7 +225,7 @@ export default function Settings() {
   const renderDatabaseTab = () => (
     <div className="space-y-6">
       <div>
-        <label className="block text-sm font-medium text-gray-700">
+        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
           Max Upload Size (bytes)
         </label>
         <input
@@ -236,7 +236,7 @@ export default function Settings() {
       </div>
 
       <div>
-        <label className="block text-sm font-medium text-gray-700">
+        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
           Backup Retention (days)
         </label>
         <input
@@ -246,8 +246,8 @@ export default function Settings() {
         />
       </div>
 
-      <div className="pt-4 border-t border-gray-200">
-        <h4 className="text-sm font-medium text-gray-900 mb-4">
+      <div className="pt-4 border-t border-gray-200 dark:border-gray-700">
+        <h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-4">
           Database Maintenance
         </h4>
         <div className="space-y-4">
@@ -264,7 +264,7 @@ export default function Settings() {
             {backupMutation.isLoading ? 'Creating Backup...' : 'Create Backup'}
           </button>
 
-          <div className="text-sm text-gray-500">
+          <div className="text-sm text-gray-500 dark:text-gray-400">
             Last backup: Never
           </div>
         </div>
@@ -279,9 +279,9 @@ export default function Settings() {
           <input
             type="checkbox"
             {...register('enable_metrics')}
-            className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
+            className="rounded border-gray-300 dark:border-gray-600 text-primary-600 focus:ring-primary-500"
           />
-          <span className="ml-2 text-sm text-gray-700">
+          <span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
             Enable Metrics Collection
           </span>
         </label>
@@ -290,16 +290,16 @@ export default function Settings() {
           <input
             type="checkbox"
             {...register('enable_alerts')}
-            className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
+            className="rounded border-gray-300 dark:border-gray-600 text-primary-600 focus:ring-primary-500"
           />
-          <span className="ml-2 text-sm text-gray-700">
+          <span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
             Enable System Alerts
           </span>
         </label>
       </div>
 
-      <div className="pt-4 border-t border-gray-200">
-        <h4 className="text-sm font-medium text-gray-900 mb-4">
+      <div className="pt-4 border-t border-gray-200 dark:border-gray-700">
+        <h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-4">
           Email Notifications
         </h4>
         <div className="space-y-4">
@@ -307,9 +307,9 @@ export default function Settings() {
             <input
               type="checkbox"
               defaultChecked
-              className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
+              className="rounded border-gray-300 dark:border-gray-600 text-primary-600 focus:ring-primary-500"
             />
-            <span className="ml-2 text-sm text-gray-700">
+            <span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
               New user registrations
             </span>
           </label>
@@ -318,9 +318,9 @@ export default function Settings() {
             <input
               type="checkbox"
               defaultChecked
-              className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
+              className="rounded border-gray-300 dark:border-gray-600 text-primary-600 focus:ring-primary-500"
             />
-            <span className="ml-2 text-sm text-gray-700">
+            <span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
               Failed deployments
             </span>
           </label>
@@ -329,9 +329,9 @@ export default function Settings() {
             <input
               type="checkbox"
               defaultChecked
-              className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
+              className="rounded border-gray-300 dark:border-gray-600 text-primary-600 focus:ring-primary-500"
             />
-            <span className="ml-2 text-sm text-gray-700">
+            <span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
               System alerts
             </span>
           </label>
@@ -358,15 +358,15 @@ export default function Settings() {
   return (
     <div>
       <div className="mb-8">
-        <h1 className="text-3xl font-bold text-gray-900">Settings</h1>
-        <p className="mt-2 text-gray-600">
+        <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Settings</h1>
+        <p className="mt-2 text-gray-600 dark:text-gray-400">
           Configure platform settings and preferences
         </p>
       </div>
 
-      <div className="bg-white rounded-xl shadow-sm border border-gray-200">
+      <div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
         {/* Tabs */}
-        <div className="border-b border-gray-200">
+        <div className="border-b border-gray-200 dark:border-gray-700">
           <nav className="-mb-px flex space-x-8 px-6" aria-label="Tabs">
             {tabs.map((tab) => {
               const Icon = tab.icon;
@@ -376,8 +376,8 @@ export default function Settings() {
                   onClick={() => setActiveTab(tab.id)}
                   className={`group inline-flex items-center py-4 px-1 border-b-2 font-medium text-sm ${
                     activeTab === tab.id
-                      ? 'border-primary-500 text-primary-600'
-                      : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
+                      ? 'border-primary-500 text-primary-600 dark:text-primary-400'
+                      : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600'
                   }`}
                 >
                   <Icon className="mr-2 h-5 w-5" />
@@ -394,7 +394,7 @@ export default function Settings() {
             {renderTabContent()}
 
             {/* Save Button */}
-            <div className="pt-6 border-t border-gray-200">
+            <div className="pt-6 border-t border-gray-200 dark:border-gray-700">
               <div className="flex justify-end">
                 <button
                   type="submit"

+ 37 - 37
dashboard/src/pages/SmtpSettings.tsx

@@ -161,8 +161,8 @@ export default function SmtpSettings() {
   return (
     <div>
       <div className="mb-8">
-        <h1 className="text-3xl font-bold text-gray-900">SMTP Settings</h1>
-        <p className="mt-2 text-gray-600">
+        <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">SMTP Settings</h1>
+        <p className="mt-2 text-gray-600 dark:text-gray-400">
           Configure email delivery settings for system notifications
         </p>
       </div>
@@ -170,14 +170,14 @@ export default function SmtpSettings() {
       <div className="max-w-3xl space-y-6">
         {/* Configuration Form */}
         <div className="card">
-          <h2 className="text-lg font-medium text-gray-900 mb-6">Email Server Configuration</h2>
+          <h2 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-6">Email Server Configuration</h2>
 
           <form onSubmit={handleSubmit} className="space-y-4">
             {/* Enable/Disable */}
-            <div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
+            <div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
               <div>
-                <h3 className="text-sm font-medium text-gray-900">Enable Email Delivery</h3>
-                <p className="text-sm text-gray-500">
+                <h3 className="text-sm font-medium text-gray-900 dark:text-gray-100">Enable Email Delivery</h3>
+                <p className="text-sm text-gray-500 dark:text-gray-400">
                   Turn on to start sending emails through SMTP
                 </p>
               </div>
@@ -190,13 +190,13 @@ export default function SmtpSettings() {
                   }
                   className="sr-only peer"
                 />
-                <div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div>
+                <div className="w-11 h-6 bg-gray-200 dark:bg-gray-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 dark:after:border-gray-600 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div>
               </label>
             </div>
 
             {/* SMTP Host */}
             <div>
-              <label htmlFor="host" className="block text-sm font-medium text-gray-700">
+              <label htmlFor="host" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 SMTP Host *
               </label>
               <input
@@ -205,30 +205,30 @@ export default function SmtpSettings() {
                 value={formData.host}
                 onChange={(e) => setFormData({ ...formData, host: e.target.value })}
                 className={`mt-1 block w-full px-3 py-2 border ${
-                  errors.host ? 'border-red-300' : 'border-gray-300'
-                } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500`}
+                  errors.host ? 'border-red-300 dark:border-red-600' : 'border-gray-300 dark:border-gray-600'
+                } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100`}
                 placeholder="smtp.gmail.com"
               />
-              {errors.host && <p className="mt-1 text-sm text-red-600">{errors.host}</p>}
+              {errors.host && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.host}</p>}
             </div>
 
             {/* Security Mode and Port */}
             <div className="grid grid-cols-2 gap-4">
               <div>
-                <label htmlFor="securityMode" className="block text-sm font-medium text-gray-700">
+                <label htmlFor="securityMode" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                   Security Mode *
                 </label>
                 <select
                   id="securityMode"
                   value={securityMode}
                   onChange={(e) => handleSecurityModeChange(e.target.value as 'none' | 'starttls' | 'ssl')}
-                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
                 >
                   <option value="none">None (Port 25) - Unencrypted</option>
                   <option value="starttls">STARTTLS (Port 587) - Recommended</option>
                   <option value="ssl">SSL/TLS (Port 465) - Direct encryption</option>
                 </select>
-                <p className="mt-1 text-xs text-gray-500">
+                <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
                   {securityMode === 'none' && 'No encryption - not recommended for production'}
                   {securityMode === 'starttls' && 'Start plain, upgrade to TLS - most common for port 587'}
                   {securityMode === 'ssl' && 'Direct TLS connection - used for port 465'}
@@ -236,7 +236,7 @@ export default function SmtpSettings() {
               </div>
 
               <div>
-                <label htmlFor="port" className="block text-sm font-medium text-gray-700">
+                <label htmlFor="port" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                   Port *
                 </label>
                 <input
@@ -247,11 +247,11 @@ export default function SmtpSettings() {
                     setFormData({ ...formData, port: parseInt(e.target.value) })
                   }
                   className={`mt-1 block w-full px-3 py-2 border ${
-                    errors.port ? 'border-red-300' : 'border-gray-300'
-                  } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500`}
+                    errors.port ? 'border-red-300 dark:border-red-600' : 'border-gray-300 dark:border-gray-600'
+                  } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100`}
                 />
-                {errors.port && <p className="mt-1 text-sm text-red-600">{errors.port}</p>}
-                <p className="mt-1 text-xs text-gray-500">
+                {errors.port && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.port}</p>}
+                <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
                   Auto-set based on security mode (can be manually adjusted)
                 </p>
               </div>
@@ -260,7 +260,7 @@ export default function SmtpSettings() {
             {/* Authentication */}
             <div className="grid grid-cols-2 gap-4">
               <div>
-                <label htmlFor="username" className="block text-sm font-medium text-gray-700">
+                <label htmlFor="username" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                   Username (optional)
                 </label>
                 <input
@@ -268,14 +268,14 @@ export default function SmtpSettings() {
                   id="username"
                   value={formData.username}
                   onChange={(e) => setFormData({ ...formData, username: e.target.value })}
-                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
                   placeholder="user@example.com"
                   autoComplete="off"
                 />
               </div>
 
               <div>
-                <label htmlFor="password" className="block text-sm font-medium text-gray-700">
+                <label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                   Password (optional)
                 </label>
                 <input
@@ -283,7 +283,7 @@ export default function SmtpSettings() {
                   id="password"
                   value={formData.password}
                   onChange={(e) => setFormData({ ...formData, password: e.target.value })}
-                  className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+                  className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
                   placeholder="Leave empty to keep current"
                   autoComplete="new-password"
                 />
@@ -292,7 +292,7 @@ export default function SmtpSettings() {
 
             {/* From Email */}
             <div>
-              <label htmlFor="from_email" className="block text-sm font-medium text-gray-700">
+              <label htmlFor="from_email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 From Email Address *
               </label>
               <input
@@ -303,18 +303,18 @@ export default function SmtpSettings() {
                   setFormData({ ...formData, from_email: e.target.value })
                 }
                 className={`mt-1 block w-full px-3 py-2 border ${
-                  errors.from_email ? 'border-red-300' : 'border-gray-300'
-                } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500`}
+                  errors.from_email ? 'border-red-300 dark:border-red-600' : 'border-gray-300 dark:border-gray-600'
+                } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100`}
                 placeholder="noreply@example.com"
               />
               {errors.from_email && (
-                <p className="mt-1 text-sm text-red-600">{errors.from_email}</p>
+                <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.from_email}</p>
               )}
             </div>
 
             {/* From Name */}
             <div>
-              <label htmlFor="from_name" className="block text-sm font-medium text-gray-700">
+              <label htmlFor="from_name" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
                 From Name
               </label>
               <input
@@ -322,7 +322,7 @@ export default function SmtpSettings() {
                 id="from_name"
                 value={formData.from_name}
                 onChange={(e) => setFormData({ ...formData, from_name: e.target.value })}
-                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
                 placeholder="SaaS Platform"
               />
             </div>
@@ -343,8 +343,8 @@ export default function SmtpSettings() {
 
         {/* Test Email */}
         <div className="card">
-          <h2 className="text-lg font-medium text-gray-900 mb-4">Test Email Configuration</h2>
-          <p className="text-sm text-gray-600 mb-4">
+          <h2 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Test Email Configuration</h2>
+          <p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
             Send a test email to verify your SMTP configuration is working correctly.
           </p>
 
@@ -354,7 +354,7 @@ export default function SmtpSettings() {
               value={testEmail}
               onChange={(e) => setTestEmail(e.target.value)}
               placeholder="Enter email to receive test"
-              className="flex-1 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
+              className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
             />
             <button
               onClick={handleTestEmail}
@@ -376,18 +376,18 @@ export default function SmtpSettings() {
           </div>
 
           {!formData.enabled && (
-            <p className="mt-2 text-sm text-amber-600">
+            <p className="mt-2 text-sm text-amber-600 dark:text-amber-400">
               ⚠️ Email delivery is currently disabled. Enable it above to send test emails.
             </p>
           )}
         </div>
 
         {/* Common SMTP Providers */}
-        <div className="card bg-blue-50 border-blue-200">
-          <h3 className="text-sm font-medium text-blue-900 mb-3">
+        <div className="card bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800">
+          <h3 className="text-sm font-medium text-blue-900 dark:text-blue-300 mb-3">
             Common SMTP Provider Settings
           </h3>
-          <div className="space-y-2 text-sm text-blue-800">
+          <div className="space-y-2 text-sm text-blue-800 dark:text-blue-300">
             <div>
               <strong>Gmail:</strong> smtp.gmail.com, Port 587 (STARTTLS) or 465 (SSL/TLS)
             </div>
@@ -401,7 +401,7 @@ export default function SmtpSettings() {
               <strong>AWS SES:</strong> email-smtp.[region].amazonaws.com, Port 587 (STARTTLS)
             </div>
           </div>
-          <div className="mt-3 pt-3 border-t border-blue-200 text-xs text-blue-700">
+          <div className="mt-3 pt-3 border-t border-blue-200 dark:border-blue-800 text-xs text-blue-700 dark:text-blue-300">
             <strong>Note:</strong> Most modern SMTP servers use port 587 with STARTTLS. Port 465 with SSL/TLS is legacy but still supported by some providers.
           </div>
         </div>

+ 202 - 18
dashboard/src/pages/Users.tsx

@@ -6,9 +6,9 @@ import {
   Trash2,
   Eye,
   EyeOff,
+  X,
 } from 'lucide-react';
 import { format } from 'date-fns';
-import { useNavigate } from 'react-router-dom';
 import apiService from '@/services/api';
 import DataTable from '@/components/DataTable';
 import { User } from '@/types';
@@ -20,14 +20,14 @@ const columns = [
     title: 'Email',
     render: (value: string, user: User) => (
       <div className="flex items-center">
-        <div className="h-8 w-8 rounded-full bg-primary-100 flex items-center justify-center">
-          <span className="text-sm font-medium text-primary-600">
+        <div className="h-8 w-8 rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center">
+          <span className="text-sm font-medium text-primary-600 dark:text-primary-400">
             {value.charAt(0).toUpperCase()}
           </span>
         </div>
         <div className="ml-3">
-          <p className="text-sm font-medium text-gray-900">{value}</p>
-          <p className="text-xs text-gray-500">
+          <p className="text-sm font-medium text-gray-900 dark:text-gray-100">{value}</p>
+          <p className="text-xs text-gray-500 dark:text-gray-400">
             ID: {user.id.substring(0, 8)}...
           </p>
         </div>
@@ -39,9 +39,9 @@ const columns = [
     title: 'Status',
     render: (value: string) => {
       const colors = {
-        active: 'bg-green-100 text-green-800',
-        inactive: 'bg-gray-100 text-gray-800',
-        suspended: 'bg-red-100 text-red-800',
+        active: 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-300',
+        inactive: 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300',
+        suspended: 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-300',
       };
       return (
         <span
@@ -58,7 +58,7 @@ const columns = [
     key: 'created_at',
     title: 'Created',
     render: (value: string) => (
-      <div className="text-sm text-gray-900">
+      <div className="text-sm text-gray-900 dark:text-gray-100">
         {format(new Date(value), 'MMM d, yyyy')}
       </div>
     ),
@@ -67,7 +67,7 @@ const columns = [
     key: 'last_login',
     title: 'Last Login',
     render: (value: string) => (
-      <div className="text-sm text-gray-900">
+      <div className="text-sm text-gray-900 dark:text-gray-100">
         {value ? format(new Date(value), 'MMM d, HH:mm') : 'Never'}
       </div>
     ),
@@ -77,7 +77,8 @@ const columns = [
 export default function Users() {
   const [page, setPage] = useState(1);
   const [limit, setLimit] = useState(20);
-  const navigate = useNavigate();
+  const [showUserModal, setShowUserModal] = useState(false);
+  const [selectedUser, setSelectedUser] = useState<User | null>(null);
   const queryClient = useQueryClient();
 
   const { data, isLoading } = useQuery(
@@ -124,18 +125,28 @@ export default function Users() {
     }
   };
 
+  const handleEditUser = (user: User) => {
+    setSelectedUser(user);
+    setShowUserModal(true);
+  };
+
+  const handleCreateUser = () => {
+    setSelectedUser(null);
+    setShowUserModal(true);
+  };
+
   const renderActions = (user: User) => (
     <div className="flex items-center space-x-2">
       <button
-        onClick={() => navigate(`/users/${user.id}/edit`)}
-        className="text-blue-600 hover:text-blue-900"
+        onClick={() => handleEditUser(user)}
+        className="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300"
         title="Edit User"
       >
         <Edit className="h-4 w-4" />
       </button>
       <button
         onClick={() => handleToggleStatus(user)}
-        className="text-gray-600 hover:text-gray-900"
+        className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
         title={user.status === 'active' ? 'Deactivate' : 'Activate'}
       >
         {user.status === 'active' ? (
@@ -146,7 +157,7 @@ export default function Users() {
       </button>
       <button
         onClick={() => handleDelete(user)}
-        className="text-red-600 hover:text-red-900"
+        className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
         title="Delete User"
       >
         <Trash2 className="h-4 w-4" />
@@ -158,13 +169,13 @@ export default function Users() {
     <div>
       <div className="mb-8 flex items-center justify-between">
         <div>
-          <h1 className="text-3xl font-bold text-gray-900">Users</h1>
-          <p className="mt-2 text-gray-600">
+          <h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Users</h1>
+          <p className="mt-2 text-gray-600 dark:text-gray-400">
             Manage platform users and their access permissions
           </p>
         </div>
         <button
-          onClick={() => navigate('/users/create')}
+          onClick={handleCreateUser}
           className="btn-primary flex items-center"
         >
           <Plus className="mr-2 h-4 w-4" />
@@ -194,6 +205,179 @@ export default function Users() {
           render: renderActions,
         }}
       />
+
+      {/* User Modal */}
+      {showUserModal && (
+        <UserModal
+          user={selectedUser}
+          onClose={() => {
+            setShowUserModal(false);
+            setSelectedUser(null);
+          }}
+        />
+      )}
+    </div>
+  );
+}
+
+// User Modal Component
+function UserModal({ user, onClose }: { user: User | null; onClose: () => void }) {
+  const [formData, setFormData] = useState({
+    email: user?.email || '',
+    password: '',
+    status: user?.status || 'active',
+  });
+  const [errors, setErrors] = useState<{ [key: string]: string }>({});
+  const queryClient = useQueryClient();
+
+  const mutation = useMutation(
+    (data: any) =>
+      user
+        ? apiService.updateUser(user.id, data)
+        : apiService.createUser(data),
+    {
+      onSuccess: () => {
+        toast.success(`User ${user ? 'updated' : 'created'} successfully`);
+        queryClient.invalidateQueries('users');
+        onClose();
+      },
+      onError: (error: any) => {
+        toast.error(error.response?.data?.error || `Failed to ${user ? 'update' : 'create'} user`);
+      },
+    }
+  );
+
+  const validateForm = () => {
+    const newErrors: { [key: string]: string } = {};
+
+    if (!formData.email) {
+      newErrors.email = 'Email is required';
+    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+      newErrors.email = 'Invalid email format';
+    }
+
+    if (!user && !formData.password) {
+      newErrors.password = 'Password is required for new users';
+    }
+
+    setErrors(newErrors);
+    return Object.keys(newErrors).length === 0;
+  };
+
+  const handleSubmit = (e: React.FormEvent) => {
+    e.preventDefault();
+
+    if (!validateForm()) {
+      return;
+    }
+
+    const submitData: any = {
+      email: formData.email,
+      status: formData.status,
+    };
+
+    if (!user || formData.password) {
+      submitData.password = formData.password;
+    }
+
+    mutation.mutate(submitData);
+  };
+
+  return (
+    <div className="fixed inset-0 z-50 overflow-y-auto">
+      <div className="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
+        <div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onClick={onClose} />
+
+        <div className="inline-block w-full max-w-md my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 rounded-lg shadow-xl">
+          {/* Header */}
+          <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
+            <div>
+              <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
+                {user ? 'Edit User' : 'Add New User'}
+              </h3>
+              <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
+                {user ? 'Update user information' : 'Create a new user account'}
+              </p>
+            </div>
+            <button
+              onClick={onClose}
+              className="text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-none"
+            >
+              <X className="w-6 h-6" />
+            </button>
+          </div>
+
+          {/* Form */}
+          <form onSubmit={handleSubmit} className="px-6 py-4 space-y-4">
+            {/* Email */}
+            <div>
+              <label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
+                Email Address *
+              </label>
+              <input
+                type="email"
+                id="email"
+                value={formData.email}
+                onChange={(e) => setFormData({ ...formData, email: e.target.value })}
+                className={`mt-1 block w-full px-3 py-2 border ${
+                  errors.email ? 'border-red-300 dark:border-red-600' : 'border-gray-300 dark:border-gray-600'
+                } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100`}
+                placeholder="user@example.com"
+              />
+              {errors.email && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.email}</p>}
+            </div>
+
+            {/* Password */}
+            <div>
+              <label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
+                Password {user ? '(leave empty to keep current)' : '*'}
+              </label>
+              <input
+                type="password"
+                id="password"
+                value={formData.password}
+                onChange={(e) => setFormData({ ...formData, password: e.target.value })}
+                className={`mt-1 block w-full px-3 py-2 border ${
+                  errors.password ? 'border-red-300 dark:border-red-600' : 'border-gray-300 dark:border-gray-600'
+                } rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100`}
+                placeholder={user ? 'Leave empty to keep current' : 'Enter password'}
+              />
+              {errors.password && <p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.password}</p>}
+            </div>
+
+            {/* Status */}
+            <div>
+              <label htmlFor="status" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
+                Status
+              </label>
+              <select
+                id="status"
+                value={formData.status}
+                onChange={(e) => setFormData({ ...formData, status: e.target.value })}
+                className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-800 dark:text-gray-100"
+              >
+                <option value="active">Active</option>
+                <option value="inactive">Inactive</option>
+                <option value="suspended">Suspended</option>
+              </select>
+            </div>
+
+            {/* Footer */}
+            <div className="pt-4 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3">
+              <button type="button" onClick={onClose} className="btn-secondary">
+                Cancel
+              </button>
+              <button
+                type="submit"
+                disabled={mutation.isLoading}
+                className="btn-primary disabled:opacity-50 disabled:cursor-not-allowed"
+              >
+                {mutation.isLoading ? (user ? 'Updating...' : 'Creating...') : (user ? 'Update User' : 'Create User')}
+              </button>
+            </div>
+          </form>
+        </div>
+      </div>
     </div>
   );
 }

+ 7 - 2
dashboard/src/services/api.ts

@@ -64,8 +64,8 @@ class ApiService {
         return Promise.reject(error);
       }
 
-      const message = error.response?.data?.error || error.message || 'An error occurred';
-      toast.error(message);
+      // Don't show toast here - let components handle error display
+      // This prevents duplicate toasts when mutations have onError handlers
       return Promise.reject(error);
     };
 
@@ -211,6 +211,11 @@ class ApiService {
     return response.data;
   }
 
+  async modifyTableColumn(tableName: string, columnName: string, modifications: any) {
+    const response = await this.api.patch(`/database/tables/${tableName}/columns/${columnName}`, modifications);
+    return response.data;
+  }
+
   async renameTable(tableName: string, newTableName: string) {
     const response = await this.api.patch(`/database/tables/${tableName}/rename`, { newTableName });
     return response.data;

+ 7 - 7
dashboard/src/styles/globals.css

@@ -8,13 +8,13 @@
   html {
     font-family: 'Inter', system-ui, sans-serif;
   }
-  
+
   body {
-    @apply bg-gray-50 text-gray-900 antialiased;
+    @apply bg-gray-50 text-gray-900 antialiased transition-colors duration-200 dark:bg-gray-900 dark:text-gray-100;
   }
-  
+
   * {
-    @apply border-gray-200;
+    @apply border-gray-200 transition-colors duration-200 dark:border-gray-700;
   }
 }
 
@@ -24,14 +24,14 @@
   }
   
   .btn-secondary {
-    @apply bg-white text-gray-700 px-4 py-2 rounded-lg font-medium border border-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition-colors;
+    @apply bg-white text-gray-700 px-4 py-2 rounded-lg font-medium border border-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition-colors dark:bg-gray-800 dark:text-gray-200 dark:border-gray-600 dark:hover:bg-gray-700;
   }
   
   .card {
-    @apply bg-white rounded-xl shadow-sm border border-gray-200 p-6;
+    @apply bg-white rounded-xl shadow-sm border border-gray-200 p-6 transition-colors duration-200 dark:bg-gray-800 dark:border-gray-700;
   }
   
   .input-field {
-    @apply block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent;
+    @apply block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-colors duration-200 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400;
   }
 }

+ 1 - 12
dashboard/tailwind.config.js

@@ -4,6 +4,7 @@ export default {
     "./index.html",
     "./src/**/*.{js,ts,jsx,tsx}",
   ],
+  darkMode: 'class',
   theme: {
     extend: {
       colors: {
@@ -19,18 +20,6 @@ export default {
           800: '#1e40af',
           900: '#1e3a8a',
         },
-        gray: {
-          50: '#f9fafb',
-          100: '#f3f4f6',
-          200: '#e5e7eb',
-          300: '#d1d5db',
-          400: '#9ca3af',
-          500: '#6b7280',
-          600: '#4b5563',
-          700: '#374151',
-          800: '#1f2937',
-          900: '#111827',
-        },
       },
       fontFamily: {
         sans: ['Inter', 'system-ui', 'sans-serif'],

+ 1113 - 0
deploy.sh

@@ -0,0 +1,1113 @@
+#!/bin/bash
+
+set -e  # Exit on error
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Script directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR"
+
+# Configuration file
+CONFIG_FILE=".env"
+
+# Log function
+log() {
+    echo -e "${BLUE}[INFO]${NC} $1"
+}
+
+success() {
+    echo -e "${GREEN}[SUCCESS]${NC} $1"
+}
+
+error() {
+    echo -e "${RED}[ERROR]${NC} $1"
+}
+
+warning() {
+    echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+# Banner
+banner() {
+    echo ""
+    echo -e "${BLUE}╔════════════════════════════════════════════════════╗${NC}"
+    echo -e "${BLUE}║                                                    ║${NC}"
+    echo -e "${BLUE}║        ${GREEN}SaaS Platform Deployment Script${BLUE}           ║${NC}"
+    echo -e "${BLUE}║                                                    ║${NC}"
+    echo -e "${BLUE}╚════════════════════════════════════════════════════╝${NC}"
+    echo ""
+}
+
+# Check if running as root
+check_root() {
+    if [ "$EUID" -eq 0 ]; then
+        error "Please do not run this script as root"
+        error "Run without sudo, the script will ask for permissions when needed"
+        exit 1
+    fi
+}
+
+# Check system requirements
+check_requirements() {
+    log "Checking system requirements..."
+
+    local missing_deps=()
+
+    # Check Docker
+    if ! command -v docker &> /dev/null; then
+        missing_deps+=("docker")
+    fi
+
+    # Check Docker Compose
+    if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
+        missing_deps+=("docker-compose")
+    fi
+
+    # Check dig (for DNS validation)
+    if ! command -v dig &> /dev/null; then
+        missing_deps+=("dnsutils/bind-tools")
+    fi
+
+    # Check curl
+    if ! command -v curl &> /dev/null; then
+        missing_deps+=("curl")
+    fi
+
+    # Check openssl
+    if ! command -v openssl &> /dev/null; then
+        missing_deps+=("openssl")
+    fi
+
+    if [ ${#missing_deps[@]} -ne 0 ]; then
+        error "Missing required dependencies: ${missing_deps[*]}"
+        echo ""
+        log "Please install the missing dependencies:"
+        echo ""
+        echo "Ubuntu/Debian:"
+        echo "  sudo apt update"
+        echo "  sudo apt install -y docker.io docker-compose dnsutils curl openssl"
+        echo ""
+        echo "CentOS/RHEL:"
+        echo "  sudo yum install -y docker docker-compose bind-utils curl openssl"
+        echo ""
+        exit 1
+    fi
+
+    success "All requirements met"
+}
+
+# Validate domain format
+validate_domain_format() {
+    local domain=$1
+
+    # Check if domain matches pattern (basic validation)
+    if [[ ! "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
+        return 1
+    fi
+
+    # Check if domain has at least one dot
+    if [[ ! "$domain" =~ \. ]]; then
+        return 1
+    fi
+
+    return 0
+}
+
+# Check DNS records
+check_dns_records() {
+    local domain=$1
+    local console_domain="console.$domain"
+    local api_domain="api.$domain"
+    local wss_domain="wss.$domain"
+
+    log "Checking DNS records for $domain..."
+
+    # Check A record for main domain
+    log "Checking A record for $domain..."
+    if ! dig +short A "$domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
+        error "No A record found for $domain"
+        warning "Please add an A record pointing to your server's IP address"
+        return 1
+    fi
+
+    local domain_ip=$(dig +short A "$domain" | head -n1)
+    log "Found A record: $domain → $domain_ip"
+
+    # Check A record for console subdomain
+    log "Checking A record for $console_domain..."
+    if ! dig +short A "$console_domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
+        error "No A record found for $console_domain"
+        warning "Please add an A record for $console_domain pointing to your server's IP address"
+        return 1
+    fi
+
+    local console_ip=$(dig +short A "$console_domain" | head -n1)
+    log "Found A record: $console_domain → $console_ip"
+
+    # Check A record for API subdomain
+    log "Checking A record for $api_domain..."
+    if ! dig +short A "$api_domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
+        error "No A record found for $api_domain"
+        warning "Please add an A record for $api_domain pointing to your server's IP address"
+        return 1
+    fi
+
+    local api_ip=$(dig +short A "$api_domain" | head -n1)
+    log "Found A record: $api_domain → $api_ip"
+
+    # Check A record for WSS subdomain
+    log "Checking A record for $wss_domain..."
+    if ! dig +short A "$wss_domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
+        error "No A record found for $wss_domain"
+        warning "Please add an A record for $wss_domain pointing to your server's IP address"
+        return 1
+    fi
+
+    local wss_ip=$(dig +short A "$wss_domain" | head -n1)
+    log "Found A record: $wss_domain → $wss_ip"
+
+    # Check wildcard record
+    log "Checking wildcard DNS record for *.$domain..."
+    local test_subdomain="test-$(date +%s).$domain"
+    if ! dig +short A "$test_subdomain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' &> /dev/null; then
+        error "No wildcard (*.$domain) DNS record found"
+        warning "Please add a wildcard A record: *.$domain → your server's IP"
+        warning "This is required for hosting applications on subdomains"
+        return 1
+    fi
+
+    local wildcard_ip=$(dig +short A "$test_subdomain" | head -n1)
+    log "Found wildcard record: *.$domain → $wildcard_ip"
+
+    # Verify all IPs match
+    if [ "$domain_ip" != "$console_ip" ] || [ "$domain_ip" != "$api_ip" ] || [ "$domain_ip" != "$wss_ip" ] || [ "$domain_ip" != "$wildcard_ip" ]; then
+        warning "DNS records point to different IP addresses:"
+        warning "  $domain → $domain_ip"
+        warning "  $console_domain → $console_ip"
+        warning "  $api_domain → $api_ip"
+        warning "  $wss_domain → $wss_ip"
+        warning "  *.$domain → $wildcard_ip"
+        warning "They should all point to the same IP address"
+
+        read -p "Continue anyway? (y/N): " -n 1 -r
+        echo
+        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+            return 1
+        fi
+    fi
+
+    success "DNS records validated successfully"
+    return 0
+}
+
+# Get user input for domain
+get_domain_input() {
+    # Detect server's public IP
+    local server_ip=$(curl -s ifconfig.me 2>/dev/null || curl -s icanhazip.com 2>/dev/null || echo "your-server-ip")
+
+    echo ""
+    log "Domain Configuration"
+    echo ""
+    echo "Please enter your domain name (e.g., example.com)"
+    echo ""
+    log "Detected server IP: ${GREEN}$server_ip${NC}"
+    echo ""
+    echo "Make sure you have the following DNS records configured:"
+    echo "  - A record: yourdomain.com → $server_ip"
+    echo "  - A record: console.yourdomain.com → $server_ip"
+    echo "  - A record: api.yourdomain.com → $server_ip"
+    echo "  - A record: wss.yourdomain.com → $server_ip"
+    echo "  - A record: *.yourdomain.com → $server_ip (wildcard)"
+    echo ""
+    echo "Replace 'yourdomain.com' with your actual domain name."
+    echo ""
+
+    while true; do
+        read -p "Domain name: " DOMAIN
+
+        # Trim whitespace
+        DOMAIN=$(echo "$DOMAIN" | xargs)
+
+        if [ -z "$DOMAIN" ]; then
+            error "Domain cannot be empty"
+            continue
+        fi
+
+        # Validate domain format
+        if ! validate_domain_format "$DOMAIN"; then
+            error "Invalid domain format: $DOMAIN"
+            error "Please enter a valid domain name (e.g., example.com)"
+            continue
+        fi
+
+        # Show DNS configuration with actual domain and IP
+        echo ""
+        log "Required DNS Configuration for: ${GREEN}$DOMAIN${NC}"
+        echo ""
+        echo "  ┌─────────────────────────────────────────────────────────┐"
+        echo "  │ Configure these DNS A records in your DNS provider:    │"
+        echo "  ├─────────────────────────────────────────────────────────┤"
+        echo "  │ Record: $DOMAIN"
+        echo "  │ Type:   A"
+        echo "  │ Value:  $server_ip"
+        echo "  ├─────────────────────────────────────────────────────────┤"
+        echo "  │ Record: console.$DOMAIN"
+        echo "  │ Type:   A"
+        echo "  │ Value:  $server_ip"
+        echo "  ├─────────────────────────────────────────────────────────┤"
+        echo "  │ Record: api.$DOMAIN"
+        echo "  │ Type:   A"
+        echo "  │ Value:  $server_ip"
+        echo "  ├─────────────────────────────────────────────────────────┤"
+        echo "  │ Record: wss.$DOMAIN"
+        echo "  │ Type:   A"
+        echo "  │ Value:  $server_ip"
+        echo "  ├─────────────────────────────────────────────────────────┤"
+        echo "  │ Record: *.$DOMAIN (wildcard)"
+        echo "  │ Type:   A"
+        echo "  │ Value:  $server_ip"
+        echo "  └─────────────────────────────────────────────────────────┘"
+        echo ""
+
+        # Check DNS records
+        if check_dns_records "$DOMAIN"; then
+            success "Domain validated: $DOMAIN"
+            break
+        else
+            echo ""
+            warning "DNS validation failed for $DOMAIN"
+            echo ""
+            log "Make sure all 5 DNS records point to: $server_ip"
+            log "DNS propagation can take up to 48 hours"
+            echo ""
+            read -p "Try again with the same domain (wait for DNS)? (Y/n): " -n 1 -r
+            echo
+            if [[ $REPLY =~ ^[Nn]$ ]]; then
+                read -p "Enter a different domain? (Y/n): " -n 1 -r
+                echo
+                if [[ $REPLY =~ ^[Nn]$ ]]; then
+                    exit 1
+                fi
+            else
+                # Wait a bit and try again
+                log "Waiting 10 seconds for DNS propagation..."
+                sleep 10
+            fi
+        fi
+    done
+}
+
+# Get user email for Let's Encrypt
+get_email_input() {
+    echo ""
+    log "Let's Encrypt Configuration"
+    echo ""
+    echo "Let's Encrypt requires an email address for:"
+    echo "  - Certificate expiration notifications"
+    echo "  - Important account updates"
+    echo ""
+
+    while true; do
+        read -p "Email address: " LE_EMAIL
+
+        # Trim whitespace
+        LE_EMAIL=$(echo "$LE_EMAIL" | xargs)
+
+        if [ -z "$LE_EMAIL" ]; then
+            error "Email cannot be empty"
+            continue
+        fi
+
+        # Basic email validation
+        if [[ ! "$LE_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
+            error "Invalid email format"
+            continue
+        fi
+
+        success "Email validated: $LE_EMAIL"
+        break
+    done
+}
+
+# Install Certbot
+install_certbot() {
+    log "Checking Certbot installation..."
+
+    if command -v certbot &> /dev/null; then
+        success "Certbot is already installed"
+        certbot --version
+        return 0
+    fi
+
+    log "Installing Certbot..."
+
+    # Detect OS
+    if [ -f /etc/debian_version ]; then
+        # Debian/Ubuntu
+        sudo apt update
+        sudo apt install -y certbot python3-certbot-nginx
+    elif [ -f /etc/redhat-release ]; then
+        # CentOS/RHEL
+        sudo yum install -y certbot python3-certbot-nginx
+    else
+        error "Unsupported operating system"
+        error "Please install Certbot manually: https://certbot.eff.org/"
+        exit 1
+    fi
+
+    success "Certbot installed successfully"
+}
+
+# Generate Let's Encrypt certificates
+generate_letsencrypt_certs() {
+    local domain=$1
+    local email=$2
+    local console_domain="console.$domain"
+    local api_domain="api.$domain"
+    local wss_domain="wss.$domain"
+
+    log "Generating Let's Encrypt certificates..."
+    echo ""
+    log "This will request certificates for:"
+    log "  - $domain"
+    log "  - $console_domain"
+    log "  - $api_domain"
+    log "  - $wss_domain"
+    echo ""
+
+    # Stop nginx if running (to free port 80)
+    if docker ps | grep saas-gateway &> /dev/null; then
+        log "Stopping Nginx container to free port 80..."
+        docker stop saas-gateway || true
+    fi
+
+    # Request certificate using standalone mode
+    log "Requesting certificate (this may take a minute)..."
+
+    sudo certbot certonly \
+        --standalone \
+        --non-interactive \
+        --agree-tos \
+        --email "$email" \
+        --domains "$domain,$console_domain,$api_domain,$wss_domain" \
+        --preferred-challenges http \
+        || {
+            error "Failed to generate Let's Encrypt certificate"
+            error "Common issues:"
+            error "  1. Port 80 is not accessible from the internet"
+            error "  2. DNS records are not properly configured"
+            error "  3. Domain is already registered with Let's Encrypt"
+            exit 1
+        }
+
+    success "Let's Encrypt certificates generated successfully"
+
+    # Copy certificates to ssl directory
+    log "Copying certificates to ssl directory..."
+    sudo cp "/etc/letsencrypt/live/$domain/fullchain.pem" "$SCRIPT_DIR/ssl/certificate.crt"
+    sudo cp "/etc/letsencrypt/live/$domain/privkey.pem" "$SCRIPT_DIR/ssl/private.key"
+    sudo chown $(whoami):$(whoami) "$SCRIPT_DIR/ssl/certificate.crt" "$SCRIPT_DIR/ssl/private.key"
+    chmod 644 "$SCRIPT_DIR/ssl/certificate.crt"
+    chmod 600 "$SCRIPT_DIR/ssl/private.key"
+
+    success "Certificates copied to $SCRIPT_DIR/ssl/"
+}
+
+# Generate DH parameters if not exists
+generate_dhparam() {
+    if [ -f "$SCRIPT_DIR/ssl/dhparam.pem" ]; then
+        log "DH parameters already exist"
+        return 0
+    fi
+
+    log "Generating DH parameters (this may take several minutes)..."
+    openssl dhparam -out "$SCRIPT_DIR/ssl/dhparam.pem" 2048
+    chmod 644 "$SCRIPT_DIR/ssl/dhparam.pem"
+    success "DH parameters generated"
+}
+
+# Create environment file
+create_env_file() {
+    local domain=$1
+
+    log "Creating environment configuration..."
+
+    # Generate random secrets
+    local db_password=$(openssl rand -base64 32)
+    local redis_password=$(openssl rand -base64 32)
+    local jwt_secret=$(openssl rand -base64 64)
+    local minio_access_key=$(openssl rand -hex 16)
+    local minio_secret_key=$(openssl rand -base64 32)
+
+    cat > "$CONFIG_FILE" <<EOF
+# Database Configuration
+POSTGRES_DB=saas_platform
+POSTGRES_USER=saas_user
+POSTGRES_PASSWORD=$db_password
+
+# Redis Configuration
+REDIS_PASSWORD=$redis_password
+
+# JWT Configuration
+JWT_SECRET=$jwt_secret
+
+# Domain Configuration
+DOMAIN=$domain
+CONSOLE_DOMAIN=console.$domain
+
+# MinIO Configuration (S3-compatible storage)
+MINIO_ROOT_USER=$minio_access_key
+MINIO_ROOT_PASSWORD=$minio_secret_key
+
+# Application Configuration
+NODE_ENV=production
+PORT=3000
+
+# Let's Encrypt Email
+LETSENCRYPT_EMAIL=$LE_EMAIL
+EOF
+
+    chmod 600 "$CONFIG_FILE"
+    success "Environment file created: $CONFIG_FILE"
+}
+
+# Update Nginx configuration for production
+update_nginx_config() {
+    local domain=$1
+    local console_domain="console.$domain"
+    local api_domain="api.$domain"
+    local wss_domain="wss.$domain"
+
+    log "Updating Nginx configuration for production..."
+
+    # Backup existing config
+    if [ -f "nginx/conf.d/https.conf" ]; then
+        cp "nginx/conf.d/https.conf" "nginx/conf.d/https.conf.bak"
+    fi
+
+    # Create production Nginx config
+    cat > "nginx/conf.d/production.conf" <<EOF
+# Production HTTPS Configuration
+# Main domain: $domain
+# Console: $console_domain
+# API: $api_domain
+# WebSocket: $wss_domain
+
+# API subdomain
+server {
+    listen 443 ssl http2;
+    server_name $api_domain;
+
+    # SSL Configuration
+    ssl_certificate /etc/nginx/ssl/certificate.crt;
+    ssl_certificate_key /etc/nginx/ssl/private.key;
+    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
+
+    # SSL Security Settings
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
+    ssl_prefer_server_ciphers off;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 10m;
+    ssl_session_tickets off;
+
+    # HSTS
+    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+    # Security headers
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+    add_header X-XSS-Protection "1; mode=block";
+    add_header Referrer-Policy "strict-origin-when-cross-origin";
+
+    # Authentication routes
+    location /auth {
+        rewrite ^/auth/(.*) /auth/\$1 break;
+        proxy_pass http://auth_service;
+        proxy_set_header Host \$host;
+        proxy_set_header X-Real-IP \$remote_addr;
+        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto \$scheme;
+
+        add_header Access-Control-Allow-Origin * always;
+        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
+        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
+    }
+
+    # API routes (root level - no /api prefix)
+    location / {
+        proxy_pass http://api_service;
+        proxy_set_header Host \$host;
+        proxy_set_header X-Real-IP \$remote_addr;
+        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto \$scheme;
+
+        add_header Access-Control-Allow-Origin * always;
+        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
+        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
+    }
+
+    # Health check
+    location /health {
+        access_log off;
+        return 200 "healthy\n";
+        add_header Content-Type text/plain;
+    }
+}
+
+# WebSocket subdomain (wss.domain.com)
+server {
+    listen 443 ssl http2;
+    server_name $wss_domain;
+
+    # SSL Configuration
+    ssl_certificate /etc/nginx/ssl/certificate.crt;
+    ssl_certificate_key /etc/nginx/ssl/private.key;
+    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
+
+    # SSL Security Settings
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
+    ssl_prefer_server_ciphers off;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 10m;
+    ssl_session_tickets off;
+
+    # HSTS
+    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+    # Security headers
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+    add_header X-XSS-Protection "1; mode=block";
+
+    # WebSocket endpoint (root level)
+    location / {
+        proxy_pass http://realtime_service;
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade \$http_upgrade;
+        proxy_set_header Connection "upgrade";
+        proxy_set_header Host \$host;
+        proxy_set_header X-Real-IP \$remote_addr;
+        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto \$scheme;
+        proxy_read_timeout 86400s;
+        proxy_send_timeout 86400s;
+        proxy_connect_timeout 10s;
+        proxy_buffering off;
+
+        add_header Access-Control-Allow-Origin * always;
+        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
+        add_header Access-Control-Allow-Headers "Authorization, Content-Type, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol" always;
+    }
+}
+
+# Console subdomain (Dashboard)
+server {
+    listen 443 ssl http2;
+    server_name $console_domain;
+
+    # SSL Configuration
+    ssl_certificate /etc/nginx/ssl/certificate.crt;
+    ssl_certificate_key /etc/nginx/ssl/private.key;
+    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
+
+    # SSL Security Settings
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
+    ssl_prefer_server_ciphers off;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 10m;
+    ssl_session_tickets off;
+
+    # HSTS
+    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+    # Security headers
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+    add_header X-XSS-Protection "1; mode=block";
+    add_header Referrer-Policy "strict-origin-when-cross-origin";
+
+    # Storage routes
+    location /storage/ {
+        client_max_body_size 100M;
+        proxy_pass http://storage_service;
+        proxy_set_header Host \$host;
+        proxy_set_header X-Real-IP \$remote_addr;
+        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto \$scheme;
+    }
+
+    # Health check
+    location /health {
+        access_log off;
+        return 200 "healthy\n";
+        add_header Content-Type text/plain;
+    }
+
+    # Dashboard (default)
+    location / {
+        proxy_pass http://dashboard_service;
+        proxy_set_header Host \$host;
+        proxy_set_header X-Real-IP \$remote_addr;
+        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto \$scheme;
+    }
+}
+
+# Main domain (will serve hello world app)
+server {
+    listen 443 ssl http2;
+    server_name $domain;
+
+    # SSL Configuration
+    ssl_certificate /etc/nginx/ssl/certificate.crt;
+    ssl_certificate_key /etc/nginx/ssl/private.key;
+    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
+
+    # SSL Security Settings
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
+    ssl_prefer_server_ciphers off;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 10m;
+    ssl_session_tickets off;
+
+    # HSTS
+    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+    # Security headers
+    add_header X-Frame-Options SAMEORIGIN;
+    add_header X-Content-Type-Options nosniff;
+    add_header X-XSS-Protection "1; mode=block";
+
+    # Serve hello world application
+    location / {
+        root /usr/share/nginx/html/hello-world;
+        index index.html;
+        try_files \$uri \$uri/ /index.html;
+    }
+
+    # Health check
+    location /health {
+        access_log off;
+        return 200 "healthy\n";
+        add_header Content-Type text/plain;
+    }
+}
+
+# Wildcard subdomain for hosted applications (will be enabled when app deployment is ready)
+server {
+    listen 443 ssl http2;
+    server_name *.$domain;
+
+    # Exclude reserved subdomains
+    if (\$host ~ "^(console|api|wss)\.$domain\$") {
+        return 404;
+    }
+
+    # SSL Configuration
+    ssl_certificate /etc/nginx/ssl/certificate.crt;
+    ssl_certificate_key /etc/nginx/ssl/private.key;
+    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
+
+    # SSL Security Settings (same as above)
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
+    ssl_prefer_server_ciphers off;
+
+    # Security headers
+    add_header X-Frame-Options SAMEORIGIN;
+    add_header X-Content-Type-Options nosniff;
+
+    # Placeholder for application routing
+    location / {
+        return 503 "Application deployment feature coming soon";
+        add_header Content-Type text/plain;
+    }
+}
+
+# HTTP to HTTPS redirect
+server {
+    listen 80;
+    server_name $domain $console_domain $api_domain $wss_domain *.$domain;
+
+    # Let's Encrypt ACME challenge
+    location /.well-known/acme-challenge/ {
+        root /var/www/certbot;
+    }
+
+    # Health check (allow over HTTP)
+    location /health {
+        access_log off;
+        return 200 "healthy\n";
+        add_header Content-Type text/plain;
+    }
+
+    # Redirect all other traffic to HTTPS
+    location / {
+        return 301 https://\$server_name\$request_uri;
+    }
+}
+EOF
+
+    # Disable default and https configs
+    if [ -f "nginx/conf.d/default.conf" ]; then
+        mv "nginx/conf.d/default.conf" "nginx/conf.d/default.conf.disabled"
+    fi
+    if [ -f "nginx/conf.d/https.conf" ]; then
+        mv "nginx/conf.d/https.conf" "nginx/conf.d/https.conf.disabled"
+    fi
+
+    success "Nginx production configuration created"
+}
+
+# Create hello world application
+create_hello_world_app() {
+    local domain=$1
+    local server_ip=$(curl -s ifconfig.me || curl -s icanhazip.com || echo "Unknown")
+
+    log "Creating hello world application..."
+
+    mkdir -p "apps/hello-world"
+
+    cat > "apps/hello-world/index.html" <<EOF
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Welcome to $domain</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 20px;
+        }
+
+        .container {
+            background: white;
+            border-radius: 20px;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+            padding: 60px;
+            max-width: 600px;
+            text-align: center;
+        }
+
+        h1 {
+            font-size: 3em;
+            color: #333;
+            margin-bottom: 20px;
+        }
+
+        .emoji {
+            font-size: 4em;
+            margin-bottom: 20px;
+            animation: wave 1s ease-in-out infinite;
+        }
+
+        @keyframes wave {
+            0%, 100% { transform: rotate(0deg); }
+            25% { transform: rotate(20deg); }
+            75% { transform: rotate(-20deg); }
+        }
+
+        p {
+            font-size: 1.2em;
+            color: #666;
+            margin-bottom: 30px;
+            line-height: 1.6;
+        }
+
+        .links {
+            display: flex;
+            gap: 15px;
+            justify-content: center;
+            flex-wrap: wrap;
+        }
+
+        a {
+            display: inline-block;
+            padding: 15px 30px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            text-decoration: none;
+            border-radius: 10px;
+            font-weight: 600;
+            transition: transform 0.2s, box-shadow 0.2s;
+        }
+
+        a:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
+        }
+
+        .info {
+            margin-top: 40px;
+            padding-top: 40px;
+            border-top: 2px solid #eee;
+        }
+
+        .info-item {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin: 10px 0;
+            padding: 10px;
+            background: #f8f9fa;
+            border-radius: 8px;
+        }
+
+        .info-label {
+            font-weight: 600;
+            color: #667eea;
+        }
+
+        .info-value {
+            color: #666;
+            font-family: monospace;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="emoji">👋</div>
+        <h1>Hello World!</h1>
+        <p>Your SaaS Platform is successfully deployed and running!</p>
+
+        <div class="links">
+            <a href="https://console.$domain">Open Dashboard</a>
+            <a href="https://api.$domain/health">API Health</a>
+        </div>
+
+        <div class="info">
+            <div class="info-item">
+                <span class="info-label">Main Domain:</span>
+                <span class="info-value">$domain</span>
+            </div>
+            <div class="info-item">
+                <span class="info-label">Server IP:</span>
+                <span class="info-value">$server_ip</span>
+            </div>
+            <div class="info-item">
+                <span class="info-label">Dashboard:</span>
+                <span class="info-value">console.$domain</span>
+            </div>
+            <div class="info-item">
+                <span class="info-label">API:</span>
+                <span class="info-value">api.$domain</span>
+            </div>
+            <div class="info-item">
+                <span class="info-label">WebSocket:</span>
+                <span class="info-value">wss.$domain</span>
+            </div>
+            <div class="info-item">
+                <span class="info-label">Status:</span>
+                <span class="info-value">🟢 Running</span>
+            </div>
+            <div class="info-item">
+                <span class="info-label">SSL:</span>
+                <span class="info-value">🔒 Let's Encrypt</span>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
+EOF
+
+    success "Hello world application created"
+}
+
+# Update docker-compose for production
+update_docker_compose() {
+    local domain=$1
+
+    log "Updating docker-compose.yml for production..."
+
+    # Update api-gateway to expose port 443 and mount hello-world app
+    if ! grep -q "443:443" docker-compose.yml; then
+        log "Updating Nginx ports configuration..."
+        # This is a simplified approach - in production you'd use yq or similar
+        sed -i 's/- "8443:443"/- "443:443"/' docker-compose.yml || true
+    fi
+
+    # Add hello-world volume mount if not exists
+    if ! grep -q "hello-world" docker-compose.yml; then
+        log "Adding hello-world application mount..."
+        # Note: This would need proper YAML editing in production
+    fi
+
+    success "Docker Compose configuration updated"
+}
+
+# Start services
+start_services() {
+    log "Starting services with Docker Compose..."
+
+    # Pull latest images
+    log "Pulling Docker images..."
+    docker-compose pull
+
+    # Build services
+    log "Building services..."
+    docker-compose build
+
+    # Start services
+    log "Starting all services..."
+    docker-compose up -d
+
+    # Wait for services to be healthy
+    log "Waiting for services to be healthy..."
+    sleep 10
+
+    # Check service status
+    docker-compose ps
+
+    success "All services started successfully"
+}
+
+# Setup certbot auto-renewal
+setup_cert_renewal() {
+    local domain=$1
+
+    log "Setting up automatic certificate renewal..."
+
+    # Create renewal script
+    cat > "$SCRIPT_DIR/renew-certs.sh" <<'EOF'
+#!/bin/bash
+# Auto-renewal script for Let's Encrypt certificates
+
+# Stop nginx
+docker stop saas-gateway
+
+# Renew certificates
+certbot renew --quiet
+
+# Copy renewed certificates
+cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem /data/appserver/ssl/certificate.crt
+cp /etc/letsencrypt/live/$DOMAIN/privkey.pem /data/appserver/ssl/private.key
+
+# Start nginx
+docker start saas-gateway
+
+# Reload nginx
+docker exec saas-gateway nginx -s reload
+EOF
+
+    chmod +x "$SCRIPT_DIR/renew-certs.sh"
+
+    # Add cron job (runs twice daily)
+    (crontab -l 2>/dev/null; echo "0 0,12 * * * $SCRIPT_DIR/renew-certs.sh >> $SCRIPT_DIR/logs/cert-renewal.log 2>&1") | crontab -
+
+    success "Certificate auto-renewal configured"
+}
+
+# Print deployment summary
+print_summary() {
+    local domain=$1
+    local console_domain="console.$domain"
+    local api_domain="api.$domain"
+    local wss_domain="wss.$domain"
+
+    echo ""
+    echo -e "${GREEN}╔════════════════════════════════════════════════════╗${NC}"
+    echo -e "${GREEN}║                                                    ║${NC}"
+    echo -e "${GREEN}║          ${BLUE}Deployment Completed Successfully!${GREEN}        ║${NC}"
+    echo -e "${GREEN}║                                                    ║${NC}"
+    echo -e "${GREEN}╚════════════════════════════════════════════════════╝${NC}"
+    echo ""
+    echo -e "${BLUE}📍 Your Platform URLs:${NC}"
+    echo ""
+    echo -e "   🌐 Main Website:    ${GREEN}https://$domain${NC}"
+    echo -e "   🎛️  Dashboard:       ${GREEN}https://$console_domain${NC}"
+    echo -e "   🔌 API Endpoint:    ${GREEN}https://$api_domain${NC}"
+    echo -e "   📡 WebSocket (WSS): ${GREEN}wss://$wss_domain${NC}"
+    echo -e "   📊 API Health:      ${GREEN}https://$api_domain/health${NC}"
+    echo ""
+    echo -e "${BLUE}🔐 Security:${NC}"
+    echo ""
+    echo -e "   ✅ SSL/TLS certificates: Let's Encrypt"
+    echo -e "   ✅ Auto-renewal: Configured (twice daily)"
+    echo -e "   ✅ HTTPS redirect: Enabled"
+    echo -e "   ✅ Security headers: Enabled"
+    echo ""
+    echo -e "${BLUE}📁 Important Files:${NC}"
+    echo ""
+    echo -e "   📄 Configuration: ${YELLOW}$CONFIG_FILE${NC}"
+    echo -e "   🔑 Certificates:  ${YELLOW}$SCRIPT_DIR/ssl/${NC}"
+    echo -e "   📝 Logs:          ${YELLOW}docker-compose logs -f${NC}"
+    echo ""
+    echo -e "${BLUE}🚀 Next Steps:${NC}"
+    echo ""
+    echo "   1. Visit https://$console_domain to access the dashboard"
+    echo "   2. Create your admin account"
+    echo "   3. Check https://$domain to see the hello world app"
+    echo "   4. Monitor services: docker-compose ps"
+    echo "   5. View logs: docker-compose logs -f [service-name]"
+    echo ""
+    echo -e "${YELLOW}⚠️  Important:${NC}"
+    echo ""
+    echo "   - Keep your $CONFIG_FILE file secure (contains passwords)"
+    echo "   - Backup your database regularly"
+    echo "   - Monitor certificate expiration (auto-renewed)"
+    echo ""
+    success "Deployment completed! 🎉"
+    echo ""
+}
+
+# Main deployment function
+main() {
+    banner
+
+    check_root
+    check_requirements
+
+    get_domain_input
+    get_email_input
+
+    echo ""
+    log "Deployment Summary:"
+    echo ""
+    echo "  Domain:           $DOMAIN"
+    echo "  Console:          console.$DOMAIN"
+    echo "  Email:            $LE_EMAIL"
+    echo ""
+    read -p "Proceed with deployment? (y/N): " -n 1 -r
+    echo
+    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+        warning "Deployment cancelled"
+        exit 0
+    fi
+
+    install_certbot
+    generate_letsencrypt_certs "$DOMAIN" "$LE_EMAIL"
+    generate_dhparam
+    create_env_file "$DOMAIN"
+    update_nginx_config "$DOMAIN"
+    create_hello_world_app "$DOMAIN"
+    update_docker_compose "$DOMAIN"
+    start_services
+    setup_cert_renewal "$DOMAIN"
+
+    print_summary "$DOMAIN"
+}
+
+# Run main function
+main "$@"

+ 10 - 4
docker-compose.yml

@@ -12,7 +12,7 @@ services:
       - postgres_data:/var/lib/postgresql/data
       - ./database/init:/docker-entrypoint-initdb.d
     ports:
-      - "5432:5432"
+      - "${POSTGRES_PORT:-5432}:5432"
     networks:
       - saas-network
     restart: unless-stopped
@@ -28,7 +28,7 @@ services:
     container_name: saas-redis
     command: redis-server --requirepass ${REDIS_PASSWORD:-redis_secure_password_change_me}
     ports:
-      - "6379:6379"
+      - "${REDIS_PORT:-6379}:6379"
     volumes:
       - redis_data:/data
     networks:
@@ -45,18 +45,22 @@ services:
     image: nginx:alpine
     container_name: saas-gateway
     ports:
-      - "8888:80"
-      - "8443:443"
+      - "${HTTP_PORT:-80}:80"
+      - "${HTTPS_PORT:-443}:443"
     volumes:
       - ./nginx/nginx.conf:/etc/nginx/nginx.conf
       - ./nginx/conf.d:/etc/nginx/conf.d
       - ./ssl:/etc/nginx/ssl
+      - ./apps/hello-world:/usr/share/nginx/html/hello-world
     networks:
       - saas-network
     depends_on:
       - postgres
       - redis
       - auth-service
+      - api-service
+      - dashboard
+      - realtime-service
     restart: unless-stopped
 
   # Authentication Service
@@ -137,6 +141,8 @@ services:
     container_name: saas-realtime
     environment:
       REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secure_password_change_me}@redis:6379
+      JWT_SECRET: ${JWT_SECRET:-your_jwt_secret_change_me}
+      PORT: 3002
       NODE_ENV: development
     ports:
       - "3002:3002"

BIN
docs/ARCHITECTURE.md


+ 551 - 0
docs/DEPLOYMENT.md

@@ -0,0 +1,551 @@
+# Production Deployment Guide
+
+## Overview
+
+This guide walks you through deploying the SaaS Platform on a production server with automatic SSL certificate generation using Let's Encrypt.
+
+## Prerequisites
+
+### Server Requirements
+
+- **OS**: Ubuntu 20.04+ / Debian 11+ / CentOS 8+ / RHEL 8+
+- **RAM**: Minimum 4GB (8GB+ recommended)
+- **CPU**: 2+ cores recommended
+- **Storage**: 20GB+ available disk space
+- **Network**: Public IP address with ports 80 and 443 accessible
+
+### Domain Requirements
+
+You must have a domain with the following DNS records configured:
+
+| Record Type | Name | Value | Purpose |
+|-------------|------|-------|---------|
+| A | `example.com` | `your-server-ip` | Main domain |
+| A | `console.example.com` | `your-server-ip` | Dashboard/Console |
+| A | `*.example.com` | `your-server-ip` | Wildcard for apps |
+
+**Important**: All three DNS records are **required** before running the deployment script.
+
+### Verify DNS Configuration
+
+```bash
+# Check main domain
+dig +short A example.com
+
+# Check console subdomain
+dig +short A console.example.com
+
+# Check wildcard (test with any subdomain)
+dig +short A test.example.com
+```
+
+All three should return your server's IP address.
+
+---
+
+## Quick Start
+
+### 1. Clone the Repository
+
+```bash
+# SSH into your server
+ssh user@your-server-ip
+
+# Clone the repository
+git clone https://github.com/yourusername/saas-platform.git
+cd saas-platform
+```
+
+### 2. Run the Deployment Script
+
+```bash
+chmod +x deploy.sh
+./deploy.sh
+```
+
+### 3. Follow the Prompts
+
+The script will ask for:
+
+1. **Domain Name**: Enter your domain (e.g., `example.com`)
+   - Script validates DNS records automatically
+   - Checks for A records and wildcard support
+
+2. **Email Address**: Enter your email for Let's Encrypt
+   - Used for certificate expiration notifications
+   - Required by Let's Encrypt
+
+3. **Confirmation**: Review and confirm deployment
+
+### 4. Wait for Deployment
+
+The script will:
+- ✅ Validate DNS records
+- ✅ Install Certbot (if not installed)
+- ✅ Generate Let's Encrypt SSL certificates
+- ✅ Configure Nginx with SSL/TLS
+- ✅ Create environment file with random secrets
+- ✅ Deploy hello world application
+- ✅ Start all Docker services
+- ✅ Setup automatic certificate renewal
+
+**Deployment time**: ~5-10 minutes
+
+---
+
+## What Gets Deployed
+
+### Services
+
+| Service | Port | Description |
+|---------|------|-------------|
+| Nginx | 80, 443 | API Gateway, SSL termination |
+| PostgreSQL | 5432 | Primary database |
+| Redis | 6379 | Caching and pub/sub |
+| Auth Service | 3001 | Authentication API |
+| API Service | 3000 | Core business logic |
+| Realtime Service | 3002 | WebSocket server |
+| Dashboard | 5173 | React admin UI |
+| MinIO | 9000, 9001 | S3-compatible storage |
+| Prometheus | 9090 | Metrics collection |
+| Grafana | 3003 | Monitoring dashboards |
+
+### URLs
+
+After deployment, the following URLs will be available:
+
+- **Main Site**: `https://example.com` (Hello World app)
+- **Dashboard**: `https://console.example.com`
+- **API**: `https://console.example.com/api`
+- **WebSocket**: `wss://console.example.com/ws`
+- **Health Check**: `https://console.example.com/health`
+
+### Files Created
+
+```
+/data/appserver/
+├── .env                    # Environment configuration (KEEP SECURE!)
+├── ssl/
+│   ├── certificate.crt     # SSL certificate (Let's Encrypt)
+│   ├── private.key         # Private key (KEEP SECURE!)
+│   └── dhparam.pem         # DH parameters
+├── nginx/conf.d/
+│   └── production.conf     # Production Nginx config
+├── apps/
+│   └── hello-world/        # Hello World application
+│       └── index.html
+└── renew-certs.sh          # Certificate renewal script
+```
+
+---
+
+## Post-Deployment Steps
+
+### 1. Access the Dashboard
+
+1. Visit `https://console.yourdomain.com`
+2. You may see a brief loading screen on first visit
+3. Create your admin account:
+   - Email: `admin@yourdomain.com`
+   - Password: Choose a strong password
+
+### 2. Verify Hello World App
+
+Visit `https://yourdomain.com` to see the hello world application.
+
+### 3. Check Service Health
+
+```bash
+# Check all services are running
+docker-compose ps
+
+# Check specific service logs
+docker-compose logs -f api-service
+docker-compose logs -f auth-service
+docker-compose logs -f nginx
+
+# Check API health
+curl https://console.yourdomain.com/api/health
+```
+
+### 4. Configure Monitoring (Optional)
+
+Access Grafana at `http://your-server-ip:3003`
+- Default credentials: `admin` / `admin`
+- Change password on first login
+- Pre-configured dashboards available
+
+---
+
+## Security Recommendations
+
+### 1. Secure the .env File
+
+```bash
+# The .env file contains sensitive passwords
+chmod 600 .env
+
+# Never commit .env to version control
+git add .env  # DON'T DO THIS!
+```
+
+### 2. Update Default Passwords
+
+The deployment script generates random passwords, but you should:
+
+1. **Database Backup User**: Create a separate read-only backup user
+2. **Grafana**: Change the default admin password
+3. **pgAdmin**: Set a master password on first use
+
+### 3. Enable Firewall
+
+```bash
+# Ubuntu/Debian
+sudo ufw allow 80/tcp
+sudo ufw allow 443/tcp
+sudo ufw allow 22/tcp  # SSH
+sudo ufw enable
+
+# CentOS/RHEL
+sudo firewall-cmd --permanent --add-service=http
+sudo firewall-cmd --permanent --add-service=https
+sudo firewall-cmd --permanent --add-service=ssh
+sudo firewall-cmd --reload
+```
+
+### 4. Setup Backups
+
+```bash
+# Create backup script
+cat > backup.sh <<'EOF'
+#!/bin/bash
+BACKUP_DIR="/backups"
+DATE=$(date +%Y%m%d_%H%M%S)
+
+# Backup database
+docker exec saas-postgres pg_dump -U saas_user saas_platform > "$BACKUP_DIR/db_$DATE.sql"
+
+# Backup .env and configs
+tar -czf "$BACKUP_DIR/config_$DATE.tar.gz" .env nginx/ ssl/
+
+# Keep only last 7 days
+find "$BACKUP_DIR" -name "*.sql" -mtime +7 -delete
+find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete
+EOF
+
+chmod +x backup.sh
+
+# Add to crontab (daily at 2 AM)
+(crontab -l 2>/dev/null; echo "0 2 * * * /data/appserver/backup.sh") | crontab -
+```
+
+### 5. Monitor Certificate Expiration
+
+Certificates auto-renew, but verify:
+
+```bash
+# Check certificate expiration
+openssl x509 -in ssl/certificate.crt -noout -dates
+
+# Test renewal (dry run)
+sudo certbot renew --dry-run
+```
+
+---
+
+## SSL/TLS Certificate Management
+
+### Automatic Renewal
+
+The deployment script configures automatic renewal via cron:
+- **Frequency**: Twice daily (12 AM and 12 PM)
+- **Script**: `renew-certs.sh`
+- **Logs**: `logs/cert-renewal.log`
+
+### Manual Renewal
+
+```bash
+# Stop Nginx to free port 80
+docker stop saas-gateway
+
+# Renew certificate
+sudo certbot renew
+
+# Copy renewed certificates
+sudo cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem ssl/certificate.crt
+sudo cp /etc/letsencrypt/live/yourdomain.com/privkey.pem ssl/private.key
+
+# Start Nginx
+docker start saas-gateway
+docker exec saas-gateway nginx -s reload
+```
+
+### Check Renewal Status
+
+```bash
+# View renewal logs
+cat logs/cert-renewal.log
+
+# List all certificates
+sudo certbot certificates
+```
+
+---
+
+## Troubleshooting
+
+### DNS Records Not Propagating
+
+**Issue**: Script reports DNS records not found
+
+**Solutions**:
+1. Wait 5-60 minutes for DNS propagation
+2. Verify records in your DNS provider's dashboard
+3. Use `dig @8.8.8.8 yourdomain.com` to check Google's DNS
+4. Clear local DNS cache: `sudo systemd-resolve --flush-caches`
+
+### Certificate Generation Failed
+
+**Issue**: Let's Encrypt certificate request fails
+
+**Common Causes**:
+1. **Port 80 blocked**: Ensure firewall allows port 80
+2. **Wrong IP**: DNS points to different IP than server
+3. **Rate limit**: Let's Encrypt has rate limits (5 per week)
+
+**Solutions**:
+```bash
+# Check if port 80 is accessible
+curl -I http://yourdomain.com
+
+# Verify DNS points to this server
+dig +short A yourdomain.com
+
+# Check Let's Encrypt rate limits
+# Wait 1 week or use staging server for testing
+```
+
+### Services Not Starting
+
+**Issue**: Docker containers fail to start
+
+**Solutions**:
+```bash
+# Check Docker daemon
+sudo systemctl status docker
+
+# View container logs
+docker-compose logs
+
+# Rebuild and restart
+docker-compose down
+docker-compose up -d --build
+
+# Check system resources
+free -h  # Memory
+df -h    # Disk space
+```
+
+### Cannot Access Dashboard
+
+**Issue**: `https://console.yourdomain.com` not accessible
+
+**Solutions**:
+1. **Check DNS**: `dig +short console.yourdomain.com`
+2. **Check Nginx**: `docker logs saas-gateway`
+3. **Check certificate**: `curl -vI https://console.yourdomain.com`
+4. **Check firewall**: `sudo ufw status`
+
+### Database Connection Errors
+
+**Issue**: Services cannot connect to database
+
+**Solutions**:
+```bash
+# Check PostgreSQL is running
+docker exec saas-postgres pg_isready
+
+# Check database credentials in .env
+cat .env | grep POSTGRES
+
+# View database logs
+docker logs saas-postgres
+
+# Connect to database manually
+docker exec -it saas-postgres psql -U saas_user -d saas_platform
+```
+
+---
+
+## Updating the Platform
+
+### Pull Latest Changes
+
+```bash
+cd /data/appserver
+git pull origin main
+```
+
+### Rebuild Services
+
+```bash
+# Stop services
+docker-compose down
+
+# Rebuild
+docker-compose build
+
+# Start services
+docker-compose up -d
+```
+
+### Database Migrations
+
+```bash
+# Backup database first!
+./backup.sh
+
+# Run migrations (if any)
+docker exec saas-api npm run migrate
+
+# Or restore from backup if needed
+cat backups/db_YYYYMMDD_HHMMSS.sql | docker exec -i saas-postgres psql -U saas_user -d saas_platform
+```
+
+---
+
+## Monitoring and Logs
+
+### View All Service Logs
+
+```bash
+# All services
+docker-compose logs -f
+
+# Specific service
+docker-compose logs -f api-service
+
+# Last 100 lines
+docker-compose logs --tail=100 api-service
+```
+
+### Check Service Health
+
+```bash
+# All services status
+docker-compose ps
+
+# Specific service health
+curl https://console.yourdomain.com/api/health
+curl https://console.yourdomain.com/auth/health
+
+# Database connection
+docker exec saas-postgres pg_isready
+```
+
+### Monitor Resources
+
+```bash
+# Container resource usage
+docker stats
+
+# System resources
+htop  # Install: sudo apt install htop
+```
+
+### Access Monitoring Dashboards
+
+- **Grafana**: `http://your-server-ip:3003`
+- **Prometheus**: `http://your-server-ip:9090`
+- **pgAdmin**: `http://your-server-ip:5050`
+
+---
+
+## Scaling Considerations
+
+### Vertical Scaling
+
+Increase server resources:
+- Upgrade to a larger VM/instance
+- Add more RAM and CPU cores
+- Use SSD storage for database
+
+### Horizontal Scaling
+
+For high traffic:
+1. **Load Balancer**: Add multiple API/Auth service replicas
+2. **Database**: Setup PostgreSQL replication
+3. **Redis**: Use Redis Cluster
+4. **Storage**: External S3 instead of MinIO
+
+### Database Optimization
+
+```bash
+# Increase PostgreSQL connections (in docker-compose.yml)
+environment:
+  POSTGRES_MAX_CONNECTIONS: 200
+
+# Add database indexes
+docker exec -it saas-postgres psql -U saas_user -d saas_platform
+CREATE INDEX idx_users_email ON __sys_users(email);
+```
+
+---
+
+## Uninstalling
+
+### Stop and Remove All Services
+
+```bash
+# Stop all containers
+docker-compose down
+
+# Remove volumes (WARNING: deletes all data!)
+docker-compose down -v
+
+# Remove images
+docker-compose down --rmi all
+
+# Remove SSL certificates
+sudo certbot delete --cert-name yourdomain.com
+```
+
+### Clean Up Files
+
+```bash
+cd /data/appserver
+rm -rf postgres_data redis_data minio_data
+rm .env
+```
+
+---
+
+## Support and Documentation
+
+- **Architecture**: See [ARCHITECTURE.md](./ARCHITECTURE.md)
+- **WebSocket API**: See [WEBSOCKET.md](./WEBSOCKET.md)
+- **API Documentation**: See [API_KEYS.md](../API_KEYS.md)
+- **Issues**: Report at https://github.com/yourusername/saas-platform/issues
+
+---
+
+## Deployment Checklist
+
+Before going to production:
+
+- [ ] DNS records configured (A, console, wildcard)
+- [ ] Server meets minimum requirements (4GB RAM, 2 CPU)
+- [ ] Firewall configured (ports 80, 443, 22)
+- [ ] Domain name verified with `dig` command
+- [ ] Email address ready for Let's Encrypt
+- [ ] Backup strategy planned
+- [ ] Monitoring dashboards configured
+- [ ] SSL certificates auto-renewal tested
+- [ ] Database backups scheduled
+- [ ] Admin account created
+- [ ] Security headers verified
+- [ ] Application deployment tested
+
+---
+
+*Generated by the SaaS Platform Deployment Script*

+ 295 - 0
docs/URL_STRUCTURE.md

@@ -0,0 +1,295 @@
+# URL Structure
+
+## Overview
+
+The SaaS Platform uses separate subdomains for different services to provide clean separation and better security.
+
+## DNS Requirements
+
+Before deploying, configure the following DNS A records (all pointing to your server's IP):
+
+| Record Type | Name | Example | Purpose |
+|-------------|------|---------|---------|
+| A | `@` or root | `example.com` | Main website/Hello World |
+| A | `console` | `console.example.com` | Dashboard/Admin UI |
+| A | `api` | `api.example.com` | REST API endpoints |
+| A | `wss` | `wss.example.com` | WebSocket server |
+| A | `*` (wildcard) | `*.example.com` | User-deployed apps |
+
+**Total: 5 DNS records required**
+
+---
+
+## Production URLs
+
+Assuming your domain is `example.com`:
+
+### Main Website
+- **URL**: `https://example.com`
+- **Purpose**: Public-facing hello world landing page
+- **SSL**: Let's Encrypt
+- **Served by**: Nginx (static files)
+
+### Dashboard (Console)
+- **URL**: `https://console.example.com`
+- **Purpose**: Admin dashboard for managing the platform
+- **SSL**: Let's Encrypt
+- **Features**:
+  - User management
+  - Application deployment
+  - Database editor
+  - Email templates
+  - SMTP settings
+  - RLS policy management
+  - API key management
+
+### API Endpoint
+- **URL**: `https://api.example.com`
+- **Purpose**: REST API for all backend operations
+- **SSL**: Let's Encrypt
+- **Authentication**: JWT or API Keys
+- **Endpoints**:
+  - `/auth/*` - Authentication routes
+  - `/users` - User management
+  - `/applications` - Application CRUD
+  - `/deployments` - Deployment operations
+  - `/database` - Database management
+  - `/rls` - RLS policies
+  - `/storage` - File operations
+  - `/email-queue` - Email management
+  - `/email-templates` - Template CRUD
+  - `/smtp-settings` - SMTP configuration
+  - `/health` - Health check
+
+**Example API Calls**:
+```bash
+# Health check
+curl https://api.example.com/health
+
+# Login (get JWT token)
+curl -X POST https://api.example.com/auth/login \
+  -H "Content-Type: application/json" \
+  -d '{"email":"user@example.com","password":"password"}'
+
+# Get users (with JWT)
+curl https://api.example.com/users \
+  -H "Authorization: Bearer YOUR_JWT_TOKEN"
+
+# Get users (with API key)
+curl https://api.example.com/v1/users \
+  -H "Authorization: Bearer sk_YOUR_API_KEY"
+```
+
+### WebSocket Server
+- **URL**: `wss://wss.example.com`
+- **Purpose**: Real-time bidirectional communication
+- **SSL**: Let's Encrypt (WSS protocol)
+- **Authentication**: JWT token required
+- **Connection**:
+  ```javascript
+  const ws = new WebSocket('wss://wss.example.com?token=YOUR_JWT_TOKEN');
+  ```
+
+### User Applications (Wildcard)
+- **URL Pattern**: `https://app-name.example.com`
+- **Purpose**: Host user-deployed applications
+- **SSL**: Shared Let's Encrypt wildcard certificate
+- **Examples**:
+  - `https://myapp.example.com`
+  - `https://blog.example.com`
+  - `https://shop.example.com`
+
+---
+
+## Development URLs
+
+For local development (using `.env.development`):
+
+| Service | Production | Development |
+|---------|-----------|-------------|
+| Main Site | `https://example.com` | `http://localhost:8888` |
+| Dashboard | `https://console.example.com` | `http://localhost:8888` |
+| API | `https://api.example.com` | `http://localhost:8888/api` |
+| WebSocket | `wss://wss.example.com` | `ws://localhost:8888/ws` |
+
+**Note**: In development, all services are accessed through a single port (8888) with different paths.
+
+---
+
+## Migration from Old URL Structure
+
+### Old Structure (Before Update)
+- Dashboard: `https://console.example.com`
+- API: `https://console.example.com/api/*`
+- WebSocket: `wss://console.example.com/ws`
+
+### New Structure (Current)
+- Dashboard: `https://console.example.com`
+- API: `https://api.example.com/*` ✨ **Changed**
+- WebSocket: `wss://wss.example.com` ✨ **Changed**
+
+### Why Change?
+
+1. **Cleaner URLs**: No more `/api` or `/ws` prefixes
+2. **Better Security**: Separate subdomains for different services
+3. **Easier CORS**: Independent CORS policies per subdomain
+4. **Load Balancing**: Can scale API and WSS independently
+5. **Certificate Management**: Dedicated SSL per service
+6. **Professional**: Industry standard practice
+
+---
+
+## Frontend Configuration
+
+### React/JavaScript
+
+```javascript
+// config.js
+const API_URL = process.env.NODE_ENV === 'production'
+  ? 'https://api.yourdomain.com'
+  : 'http://localhost:8888/api';
+
+const WSS_URL = process.env.NODE_ENV === 'production'
+  ? 'wss://wss.yourdomain.com'
+  : 'ws://localhost:8888/ws';
+
+const CONSOLE_URL = process.env.NODE_ENV === 'production'
+  ? 'https://console.yourdomain.com'
+  : 'http://localhost:8888';
+
+export { API_URL, WSS_URL, CONSOLE_URL };
+```
+
+### Environment Variables
+
+```bash
+# .env.production
+VITE_API_URL=https://api.yourdomain.com
+VITE_WSS_URL=wss://wss.yourdomain.com
+VITE_CONSOLE_URL=https://console.yourdomain.com
+
+# .env.development
+VITE_API_URL=http://localhost:8888/api
+VITE_WSS_URL=ws://localhost:8888/ws
+VITE_CONSOLE_URL=http://localhost:8888
+```
+
+---
+
+## Nginx Routing
+
+### Simplified Routing Map
+
+```
+Port 443 (HTTPS):
+├── example.com → Hello World (static files)
+├── console.example.com → Dashboard (React app)
+├── api.example.com → API Service (Node.js)
+├── wss.example.com → WebSocket Service (Node.js)
+└── *.example.com → User Apps (dynamic routing)
+
+Port 80 (HTTP):
+└── All domains → Redirect to HTTPS (301)
+```
+
+---
+
+## Security Considerations
+
+### CORS Configuration
+
+Each subdomain has independent CORS:
+
+- **console.example.com**: Strict CORS (same-origin only)
+- **api.example.com**: Permissive CORS (allow all origins for API access)
+- **wss.example.com**: WebSocket CORS (upgrade headers)
+
+### SSL/TLS Certificates
+
+- **Single Certificate**: Covers all subdomains
+  - `example.com`
+  - `console.example.com`
+  - `api.example.com`
+  - `wss.example.com`
+- **Wildcard Support**: `*.example.com` (requires separate certificate or SAN)
+- **Auto-renewal**: Certbot renews automatically
+
+### Firewall Rules
+
+```bash
+# Only open ports 80 and 443
+sudo ufw allow 80/tcp
+sudo ufw allow 443/tcp
+sudo ufw allow 22/tcp  # SSH
+sudo ufw enable
+```
+
+---
+
+## Testing DNS Configuration
+
+```bash
+# Check all DNS records
+dig +short A example.com
+dig +short A console.example.com
+dig +short A api.example.com
+dig +short A wss.example.com
+dig +short A test.example.com  # Test wildcard
+
+# All should return the same IP address
+```
+
+---
+
+## Troubleshooting
+
+### "DNS not found" Error
+
+**Problem**: Cannot resolve subdomain
+**Solution**: Wait for DNS propagation (up to 48 hours) or check DNS configuration
+
+```bash
+# Use Google DNS for testing
+dig @8.8.8.8 api.example.com
+```
+
+### SSL Certificate Error
+
+**Problem**: Certificate doesn't cover subdomain
+**Solution**: Regenerate certificate with all subdomains
+
+```bash
+sudo certbot certonly --standalone \
+  -d example.com \
+  -d console.example.com \
+  -d api.example.com \
+  -d wss.example.com
+```
+
+### WebSocket Connection Failed
+
+**Problem**: Cannot connect to WSS
+**Solution**: Check if wss.example.com resolves and has SSL
+
+```bash
+# Test WSS domain
+curl -I https://wss.example.com
+
+# Should return HTTP 200 or 400 (not 404 or connection refused)
+```
+
+---
+
+## Summary Table
+
+| URL | Service | Port | SSL | Authentication | Purpose |
+|-----|---------|------|-----|----------------|---------|
+| `https://example.com` | Nginx | 443 | ✅ | None | Landing page |
+| `https://console.example.com` | Dashboard | 443 | ✅ | JWT | Admin UI |
+| `https://api.example.com` | API | 443 | ✅ | JWT/API Key | REST API |
+| `wss://wss.example.com` | WebSocket | 443 | ✅ | JWT | Real-time |
+| `https://*.example.com` | User Apps | 443 | ✅ | Varies | Hosted apps |
+
+---
+
+*For deployment instructions, see [DEPLOYMENT.md](./DEPLOYMENT.md)*

+ 664 - 0
docs/WEBSOCKET.md

@@ -0,0 +1,664 @@
+# WebSocket (WS/WSS) Implementation Guide
+
+## Overview
+
+The SaaS Platform includes a production-ready WebSocket service for real-time bidirectional communication between clients and the server. The service supports both **WS** (unencrypted) and **WSS** (encrypted over TLS) protocols.
+
+## Connection URLs
+
+- **Development (HTTP/WS)**: `ws://localhost:8888/ws`
+- **Production (HTTPS/WSS)**: `wss://localhost:8443/ws` or `wss://yourdomain.com/ws`
+
+## Features
+
+✅ **JWT Authentication** - Secure connection with JWT tokens
+✅ **Channel-based Pub/Sub** - Subscribe to specific event channels
+✅ **Authorization** - Role-based and user-specific channel access
+✅ **Heartbeat/Ping-Pong** - Automatic connection health monitoring
+✅ **Redis Integration** - Scalable pub/sub messaging
+✅ **Connection Management** - Track and manage all active connections
+✅ **Graceful Reconnection** - Client reconnection support
+✅ **Real-time Events** - Live updates for apps, users, database, deployments
+
+---
+
+## Authentication
+
+### Method 1: Query Parameter (Recommended)
+
+```javascript
+const token = 'your_jwt_token';
+const ws = new WebSocket(`ws://localhost:8888/ws?token=${token}`);
+```
+
+### Method 2: Authorization Header (Node.js/Advanced)
+
+```javascript
+const WebSocket = require('ws');
+
+const ws = new WebSocket('ws://localhost:8888/ws', {
+  headers: {
+    'Authorization': `Bearer ${token}`
+  }
+});
+```
+
+### Method 3: Sec-WebSocket-Protocol (Browser Compatibility)
+
+```javascript
+const ws = new WebSocket('ws://localhost:8888/ws', [`token-${token}`]);
+```
+
+---
+
+## Connection Flow
+
+```
+1. Client connects to ws://localhost:8888/ws?token=JWT
+2. Server extracts and verifies JWT token
+3. Server sends "connected" message with user info and authorized channels
+4. Client can now subscribe to channels and receive events
+```
+
+---
+
+## Message Protocol
+
+All messages are JSON-formatted strings.
+
+### Client → Server Messages
+
+#### Subscribe to Channel
+
+```json
+{
+  "type": "subscribe",
+  "channel": "apps:created"
+}
+```
+
+#### Unsubscribe from Channel
+
+```json
+{
+  "type": "unsubscribe",
+  "channel": "apps:created"
+}
+```
+
+#### Ping (Keep-Alive)
+
+```json
+{
+  "type": "ping"
+}
+```
+
+#### List Available Channels
+
+```json
+{
+  "type": "list_channels"
+}
+```
+
+#### Get Connection Stats (Admin Only)
+
+```json
+{
+  "type": "stats"
+}
+```
+
+### Server → Client Messages
+
+#### Connected (Welcome Message)
+
+```json
+{
+  "type": "connected",
+  "connectionId": "uuid-v4",
+  "user": {
+    "userId": "user-uuid",
+    "email": "user@example.com",
+    "role": "admin"
+  },
+  "authorizedChannels": [
+    "apps:created",
+    "apps:updated",
+    "user:user-uuid",
+    "system:health"
+  ],
+  "timestamp": "2025-11-25T12:00:00.000Z"
+}
+```
+
+#### Subscription Confirmed
+
+```json
+{
+  "type": "subscribed",
+  "channel": "apps:created",
+  "timestamp": "2025-11-25T12:00:00.000Z"
+}
+```
+
+#### Event Received
+
+```json
+{
+  "type": "event",
+  "channel": "apps:created",
+  "data": {
+    "id": "app-uuid",
+    "name": "my-app",
+    "status": "pending"
+  },
+  "timestamp": "2025-11-25T12:00:00.000Z"
+}
+```
+
+#### Pong (Heartbeat Response)
+
+```json
+{
+  "type": "pong",
+  "timestamp": "2025-11-25T12:00:00.000Z"
+}
+```
+
+#### Error
+
+```json
+{
+  "type": "error",
+  "message": "Not authorized to subscribe to this channel",
+  "timestamp": "2025-11-25T12:00:00.000Z"
+}
+```
+
+---
+
+## Available Channels
+
+### Application Events
+
+- `apps:created` - New application created
+- `apps:updated` - Application updated
+- `apps:deleted` - Application deleted
+- `apps:deployed` - Application deployed
+- `apps:status` - Application status changed
+
+### User Events
+
+- `users:created` - New user registered
+- `users:updated` - User profile updated
+- `users:deleted` - User deleted
+
+### User-Specific Channel (Private)
+
+- `user:{userId}` - Private channel for specific user (only accessible by the user or admin)
+
+### Database Events
+
+- `database:table_created` - New table created
+- `database:table_updated` - Table schema updated
+- `database:table_deleted` - Table deleted
+- `database:row_updated` - Row data changed
+
+### Deployment Events
+
+- `deployments:started` - Deployment started
+- `deployments:completed` - Deployment finished successfully
+- `deployments:failed` - Deployment failed
+- `deployments:status` - Deployment status update
+
+### Storage Events
+
+- `storage:uploaded` - File uploaded
+- `storage:deleted` - File deleted
+- `storage:updated` - File updated
+
+### System Events (Admin Only)
+
+- `system:health` - System health updates
+- `system:metrics` - Performance metrics
+- `system:alerts` - System alerts
+
+### Public Channels (No Auth Required)
+
+- `announcements` - Public announcements
+
+---
+
+## Channel Authorization
+
+Channels have different authorization requirements:
+
+| Channel Pattern | Requires Auth | Allowed Roles | Special Rules |
+|----------------|---------------|---------------|---------------|
+| `announcements` | No | All | Public channel |
+| `apps:*` | Yes | All authenticated | - |
+| `users:*` | Yes | All authenticated | - |
+| `user:{userId}` | Yes | All authenticated | Only own user or admin |
+| `database:*` | Yes | All authenticated | - |
+| `deployments:*` | Yes | All authenticated | - |
+| `storage:*` | Yes | All authenticated | - |
+| `system:*` | Yes | Admin only | Restricted to admins |
+
+---
+
+## JavaScript Client Example
+
+### Basic Connection
+
+```javascript
+class RealtimeClient {
+  constructor(token, url = 'ws://localhost:8888/ws') {
+    this.token = token;
+    this.url = url;
+    this.ws = null;
+    this.reconnectAttempts = 0;
+    this.maxReconnectAttempts = 5;
+    this.reconnectDelay = 1000;
+  }
+
+  connect() {
+    // Connect with token as query parameter
+    this.ws = new WebSocket(`${this.url}?token=${this.token}`);
+
+    this.ws.onopen = () => {
+      console.log('WebSocket connected');
+      this.reconnectAttempts = 0;
+    };
+
+    this.ws.onmessage = (event) => {
+      const message = JSON.parse(event.data);
+      this.handleMessage(message);
+    };
+
+    this.ws.onerror = (error) => {
+      console.error('WebSocket error:', error);
+    };
+
+    this.ws.onclose = () => {
+      console.log('WebSocket closed');
+      this.reconnect();
+    };
+  }
+
+  handleMessage(message) {
+    console.log('Received:', message);
+
+    switch (message.type) {
+      case 'connected':
+        console.log('Connection ID:', message.connectionId);
+        console.log('Authorized channels:', message.authorizedChannels);
+        break;
+
+      case 'subscribed':
+        console.log(`Subscribed to ${message.channel}`);
+        break;
+
+      case 'event':
+        console.log(`Event on ${message.channel}:`, message.data);
+        // Handle your event here
+        break;
+
+      case 'error':
+        console.error('Server error:', message.message);
+        break;
+
+      case 'pong':
+        console.log('Pong received');
+        break;
+    }
+  }
+
+  subscribe(channel) {
+    this.send({
+      type: 'subscribe',
+      channel: channel
+    });
+  }
+
+  unsubscribe(channel) {
+    this.send({
+      type: 'unsubscribe',
+      channel: channel
+    });
+  }
+
+  send(data) {
+    if (this.ws.readyState === WebSocket.OPEN) {
+      this.ws.send(JSON.stringify(data));
+    } else {
+      console.error('WebSocket is not open');
+    }
+  }
+
+  reconnect() {
+    if (this.reconnectAttempts < this.maxReconnectAttempts) {
+      this.reconnectAttempts++;
+      const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
+      console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
+
+      setTimeout(() => {
+        this.connect();
+      }, delay);
+    } else {
+      console.error('Max reconnection attempts reached');
+    }
+  }
+
+  disconnect() {
+    if (this.ws) {
+      this.ws.close();
+    }
+  }
+}
+
+// Usage
+const token = 'your_jwt_token_here';
+const client = new RealtimeClient(token, 'ws://localhost:8888/ws');
+client.connect();
+
+// Subscribe to channels
+client.ws.onopen = () => {
+  client.subscribe('apps:created');
+  client.subscribe('users:updated');
+  client.subscribe('user:my-user-id');
+};
+```
+
+### React Hook Example
+
+```javascript
+import { useEffect, useRef, useState } from 'react';
+
+export function useWebSocket(token, channels = []) {
+  const [connected, setConnected] = useState(false);
+  const [messages, setMessages] = useState([]);
+  const wsRef = useRef(null);
+
+  useEffect(() => {
+    if (!token) return;
+
+    const ws = new WebSocket(`ws://localhost:8888/ws?token=${token}`);
+    wsRef.current = ws;
+
+    ws.onopen = () => {
+      console.log('WebSocket connected');
+      setConnected(true);
+
+      // Subscribe to channels
+      channels.forEach(channel => {
+        ws.send(JSON.stringify({ type: 'subscribe', channel }));
+      });
+    };
+
+    ws.onmessage = (event) => {
+      const message = JSON.parse(event.data);
+
+      if (message.type === 'event') {
+        setMessages(prev => [...prev, message]);
+      }
+    };
+
+    ws.onclose = () => {
+      console.log('WebSocket disconnected');
+      setConnected(false);
+    };
+
+    ws.onerror = (error) => {
+      console.error('WebSocket error:', error);
+    };
+
+    return () => {
+      ws.close();
+    };
+  }, [token, channels]);
+
+  const send = (data) => {
+    if (wsRef.current?.readyState === WebSocket.OPEN) {
+      wsRef.current.send(JSON.stringify(data));
+    }
+  };
+
+  return { connected, messages, send };
+}
+
+// Usage in component
+function MyComponent() {
+  const { connected, messages, send } = useWebSocket(
+    localStorage.getItem('token'),
+    ['apps:created', 'users:updated']
+  );
+
+  return (
+    <div>
+      <p>Status: {connected ? 'Connected' : 'Disconnected'}</p>
+      <ul>
+        {messages.map((msg, i) => (
+          <li key={i}>
+            {msg.channel}: {JSON.stringify(msg.data)}
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+}
+```
+
+---
+
+## Publishing Events from Backend
+
+To publish events to WebSocket clients from your backend services:
+
+```typescript
+import { createClient } from 'redis';
+
+const redisPublisher = createClient({
+  url: process.env.REDIS_URL
+});
+
+await redisPublisher.connect();
+
+// Publish an event
+await redisPublisher.publish(
+  'apps:created',
+  JSON.stringify({
+    id: 'app-uuid',
+    name: 'my-new-app',
+    status: 'pending',
+    createdAt: new Date().toISOString()
+  })
+);
+```
+
+---
+
+## Testing WebSocket Connection
+
+### Using websocat (CLI Tool)
+
+```bash
+# Install websocat
+brew install websocat  # macOS
+# or
+cargo install websocat  # Rust
+
+# Connect
+websocat "ws://localhost:8888/ws?token=YOUR_JWT_TOKEN"
+
+# Send messages
+{"type":"subscribe","channel":"apps:created"}
+{"type":"ping"}
+```
+
+### Using Browser Console
+
+```javascript
+const token = 'your_jwt_token';
+const ws = new WebSocket(`ws://localhost:8888/ws?token=${token}`);
+
+ws.onmessage = (event) => {
+  console.log('Received:', JSON.parse(event.data));
+};
+
+ws.onopen = () => {
+  // Subscribe to a channel
+  ws.send(JSON.stringify({
+    type: 'subscribe',
+    channel: 'apps:created'
+  }));
+};
+```
+
+---
+
+## Troubleshooting
+
+### Connection Refused
+
+- **Check if the realtime service is running**: `docker ps | grep realtime`
+- **Check Nginx logs**: `docker logs saas-gateway`
+- **Verify port forwarding**: Port 8888 (HTTP/WS) or 8443 (HTTPS/WSS)
+
+### Authentication Failed
+
+- **Check JWT token validity**: Token must not be expired
+- **Verify JWT_SECRET**: Must match between auth and realtime services
+- **Check token format**: Should be passed as query parameter or header
+
+### Subscription Denied
+
+- **Check user role**: Some channels require admin role
+- **Verify channel name**: Must match authorized channel patterns
+- **Check user-specific channels**: `user:{userId}` must match your userId
+
+### Connection Drops
+
+- **Check heartbeat**: Server sends ping every 30 seconds
+- **Network issues**: Check firewall and proxy settings
+- **Timeout settings**: Nginx WebSocket timeout is set to 24 hours
+
+---
+
+## Security Considerations
+
+1. **Always use WSS in production** - Encrypt WebSocket traffic over TLS
+2. **Validate JWT tokens** - Tokens are verified on connection
+3. **Channel authorization** - Users can only subscribe to authorized channels
+4. **Rate limiting** - Consider implementing rate limits for message sending
+5. **Input validation** - All messages are validated before processing
+
+---
+
+## Performance
+
+- **Heartbeat interval**: 30 seconds (configurable)
+- **Heartbeat timeout**: 35 seconds
+- **Max connections**: Limited by system resources
+- **Redis pub/sub**: Efficient channel-based broadcasting
+- **Connection pooling**: Nginx keepalive for upstream connections
+
+---
+
+## Monitoring
+
+### Health Check
+
+```bash
+curl http://localhost:8888/health
+```
+
+Response:
+```json
+{
+  "status": "ok",
+  "service": "realtime",
+  "connections": 5,
+  "authenticated": 3,
+  "activeChannels": 8,
+  "timestamp": "2025-11-25T12:00:00.000Z"
+}
+```
+
+### Connection Stats (Admin Only via WebSocket)
+
+```json
+{
+  "type": "stats"
+}
+```
+
+Response:
+```json
+{
+  "type": "stats",
+  "data": {
+    "totalConnections": 10,
+    "authenticatedConnections": 8,
+    "anonymousConnections": 2,
+    "totalSubscriptions": 45,
+    "connectionsByChannel": {
+      "apps:created": 5,
+      "users:updated": 3,
+      "system:health": 1
+    }
+  },
+  "timestamp": "2025-11-25T12:00:00.000Z"
+}
+```
+
+---
+
+## Production Deployment
+
+### 1. Generate Production SSL Certificates
+
+```bash
+# Use Let's Encrypt for production
+sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
+
+# Copy certificates to ssl directory
+cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem ./ssl/certificate.crt
+cp /etc/letsencrypt/live/yourdomain.com/privkey.pem ./ssl/private.key
+```
+
+### 2. Update Environment Variables
+
+```bash
+# .env file
+NODE_ENV=production
+JWT_SECRET=your_production_secret_here
+REDIS_URL=redis://your-redis-host:6379
+```
+
+### 3. Enable HTTPS Redirect
+
+The `https.conf` Nginx configuration automatically redirects HTTP to HTTPS in production.
+
+### 4. Update WebSocket URL in Frontend
+
+```javascript
+const WS_URL = process.env.NODE_ENV === 'production'
+  ? 'wss://yourdomain.com/ws'
+  : 'ws://localhost:8888/ws';
+```
+
+---
+
+## Next Steps
+
+- [ ] Implement custom event handlers in your application
+- [ ] Set up monitoring and alerting for WebSocket connections
+- [ ] Configure auto-scaling for the realtime service
+- [ ] Implement message rate limiting
+- [ ] Add custom channels for your application's specific needs
+
+---
+
+*For more information, see the [Architecture Documentation](./ARCHITECTURE.md)*

+ 29 - 0
nginx/conf.d/default.conf

@@ -86,6 +86,35 @@ server {
         proxy_set_header X-Forwarded-Proto $scheme;
     }
 
+    # WebSocket endpoint for real-time connections
+    location /ws {
+        proxy_pass http://realtime_service;
+        proxy_http_version 1.1;
+
+        # WebSocket upgrade headers
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+
+        # Standard proxy headers
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        # WebSocket timeout settings (keep connections alive longer)
+        proxy_read_timeout 86400s;
+        proxy_send_timeout 86400s;
+        proxy_connect_timeout 10s;
+
+        # Disable buffering for WebSocket
+        proxy_buffering off;
+
+        # CORS headers for WebSocket
+        add_header Access-Control-Allow-Origin * always;
+        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
+        add_header Access-Control-Allow-Headers "Authorization, Content-Type, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol, Sec-WebSocket-Extensions" always;
+    }
+
     # Default route - Dashboard (serve as main application)
     location / {
         proxy_pass http://dashboard_service;

+ 147 - 0
nginx/conf.d/https.conf

@@ -0,0 +1,147 @@
+# HTTPS/WSS Configuration
+# This file enables secure WebSocket connections (WSS) over HTTPS
+
+server {
+    listen 443 ssl http2;
+    server_name localhost;
+
+    # SSL Configuration
+    ssl_certificate /etc/nginx/ssl/certificate.crt;
+    ssl_certificate_key /etc/nginx/ssl/private.key;
+    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
+
+    # SSL Security Settings
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
+    ssl_prefer_server_ciphers off;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 10m;
+    ssl_session_tickets off;
+
+    # HSTS (HTTP Strict Transport Security)
+    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+    # Security headers
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+    add_header X-XSS-Protection "1; mode=block";
+    add_header Referrer-Policy "strict-origin-when-cross-origin";
+
+    # Authentication routes
+    location /auth/ {
+        proxy_pass http://auth_service;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        # CORS
+        add_header Access-Control-Allow-Origin * always;
+        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
+        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
+
+        if ($request_method = 'OPTIONS') {
+            add_header Access-Control-Allow-Origin * always;
+            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
+            add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
+            add_header Access-Control-Max-Age 86400 always;
+            return 204;
+        }
+    }
+
+    # API routes
+    location /api/ {
+        rewrite ^/api/(.*) /$1 break;
+        proxy_pass http://api_service;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        # CORS
+        add_header Access-Control-Allow-Origin * always;
+        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
+        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
+
+        if ($request_method = 'OPTIONS') {
+            add_header Access-Control-Allow-Origin * always;
+            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
+            add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
+            add_header Access-Control-Max-Age 86400 always;
+            return 204;
+        }
+    }
+
+    # Secure WebSocket endpoint (WSS)
+    location /ws {
+        proxy_pass http://realtime_service;
+        proxy_http_version 1.1;
+
+        # WebSocket upgrade headers
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+
+        # Standard proxy headers
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        # WebSocket timeout settings (24 hours)
+        proxy_read_timeout 86400s;
+        proxy_send_timeout 86400s;
+        proxy_connect_timeout 10s;
+
+        # Disable buffering for WebSocket
+        proxy_buffering off;
+
+        # CORS headers for WebSocket
+        add_header Access-Control-Allow-Origin * always;
+        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
+        add_header Access-Control-Allow-Headers "Authorization, Content-Type, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol, Sec-WebSocket-Extensions" always;
+    }
+
+    # Health check
+    location /health {
+        access_log off;
+        return 200 "healthy\n";
+        add_header Content-Type text/plain;
+    }
+
+    # Storage routes
+    location /storage/ {
+        client_max_body_size 100M;
+        proxy_pass http://storage_service;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+
+    # Default route - Dashboard
+    location / {
+        proxy_pass http://dashboard_service;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+}
+
+# Redirect HTTP to HTTPS
+server {
+    listen 80;
+    server_name localhost;
+
+    # Allow health checks over HTTP
+    location /health {
+        access_log off;
+        return 200 "healthy\n";
+        add_header Content-Type text/plain;
+    }
+
+    # Redirect all other traffic to HTTPS
+    location / {
+        return 301 https://$server_name$request_uri;
+    }
+}

+ 5 - 0
nginx/nginx.conf

@@ -36,6 +36,11 @@ http {
         server storage:9000;
     }
 
+    upstream realtime_service {
+        server realtime-service:3002;
+        keepalive 64;
+    }
+
     # Load site-specific configurations
     include /etc/nginx/conf.d/*.conf;
 }

+ 97 - 0
services/api/src/routes/database.ts

@@ -715,6 +715,103 @@ router.patch('/tables/:tableName/rename', async (req: any, res) => {
   }
 });
 
+// Modify/alter a column
+router.patch('/tables/:tableName/columns/:columnName', async (req: any, res) => {
+  try {
+    const { tableName, columnName } = req.params;
+    const { newColumnName, dataType, nullable, defaultValue, dropDefault } = req.body;
+    const userId = req.user.userId;
+
+    // Prevent modifying system tables
+    if (tableName.startsWith('__sys_')) {
+      return res.status(403).json({ error: 'Cannot modify system tables' });
+    }
+
+    // Prevent modifying protected columns
+    const protectedColumns = ['id', 'created_at', 'updated_at'];
+    if (protectedColumns.includes(columnName.toLowerCase())) {
+      return res.status(403).json({ error: `Cannot modify protected column: ${columnName}` });
+    }
+
+    // Validate table exists
+    const tableCheck = await pool.query(`
+      SELECT table_name FROM information_schema.tables
+      WHERE table_schema = 'public' AND table_name = $1
+    `, [tableName]);
+
+    if (tableCheck.rows.length === 0) {
+      return res.status(404).json({ error: 'Table not found' });
+    }
+
+    // Check if column exists
+    const columnCheck = await pool.query(`
+      SELECT column_name FROM information_schema.columns
+      WHERE table_schema = 'public' AND table_name = $1 AND column_name = $2
+    `, [tableName, columnName]);
+
+    if (columnCheck.rows.length === 0) {
+      return res.status(404).json({ error: 'Column not found' });
+    }
+
+    const alterStatements = [];
+
+    // Rename column
+    if (newColumnName && newColumnName !== columnName) {
+      if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(newColumnName)) {
+        return res.status(400).json({ error: 'Invalid new column name' });
+      }
+      alterStatements.push(`ALTER TABLE "${tableName}" RENAME COLUMN "${columnName}" TO "${newColumnName}"`);
+    }
+
+    const targetColumnName = newColumnName || columnName;
+
+    // Change data type
+    if (dataType) {
+      alterStatements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${targetColumnName}" TYPE ${dataType.toUpperCase()}`);
+    }
+
+    // Set/drop NOT NULL
+    if (nullable !== undefined) {
+      if (nullable) {
+        alterStatements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${targetColumnName}" DROP NOT NULL`);
+      } else {
+        alterStatements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${targetColumnName}" SET NOT NULL`);
+      }
+    }
+
+    // Set/drop default value
+    if (dropDefault) {
+      alterStatements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${targetColumnName}" DROP DEFAULT`);
+    } else if (defaultValue !== undefined && defaultValue !== '') {
+      let formattedDefault = defaultValue;
+      if (dataType && (dataType.toLowerCase().includes('varchar') || dataType.toLowerCase().includes('text'))) {
+        formattedDefault = `'${defaultValue.replace(/'/g, "''")}'`;
+      }
+      alterStatements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${targetColumnName}" SET DEFAULT ${formattedDefault}`);
+    }
+
+    // Execute all alter statements
+    for (const statement of alterStatements) {
+      await pool.query(statement);
+    }
+
+    // Log audit
+    try {
+      await pool.query(`
+        INSERT INTO __sys_audit_logs (user_id, action, resource_type, resource_id, details)
+        VALUES ($1, 'modify_column', 'table', $2, $3)
+      `, [userId, tableName, { tableName, columnName, changes: req.body }]);
+    } catch (auditError) {
+      console.error('Failed to log modify column audit:', auditError);
+    }
+
+    res.json({ message: 'Column modified successfully' });
+  } catch (error: any) {
+    console.error('Error modifying column:', error);
+    res.status(500).json({ error: error.message || 'Failed to modify column' });
+  }
+});
+
 // Drop a table
 router.delete('/tables/:tableName', async (req: any, res) => {
   try {

+ 27 - 15
services/api/src/routes/rls.ts

@@ -175,11 +175,15 @@ router.post('/tables/:tableName/enable', async (req: any, res) => {
     // Enable RLS
     await pool.query(`ALTER TABLE "${tableName}" ENABLE ROW LEVEL SECURITY`);
 
-    // Log audit
-    await pool.query(`
-      INSERT INTO __sys_audit_logs (user_id, action, resource_type, resource_id, details)
-      VALUES ($1, 'enable_rls', 'table', $2, $3)
-    `, [userId, tableName, { tableName }]);
+    // Log audit (don't fail if audit logging fails)
+    try {
+      await pool.query(`
+        INSERT INTO __sys_audit_logs (user_id, action, resource_type, resource_id, details)
+        VALUES ($1, 'enable_rls', 'table', $2, $3)
+      `, [userId, tableName, JSON.stringify({ tableName })]);
+    } catch (auditError) {
+      logger.error('Failed to log RLS enable audit:', auditError);
+    }
 
     logger.info(`RLS enabled on table ${tableName} by user ${userId}`);
     res.json({ message: 'RLS enabled successfully', tableName });
@@ -245,11 +249,15 @@ router.post('/policies', async (req: any, res) => {
 
     await pool.query(policySQL);
 
-    // Log audit
-    await pool.query(`
-      INSERT INTO __sys_audit_logs (user_id, action, resource_type, resource_id, details)
-      VALUES ($1, 'create_policy', 'rls_policy', $2, $3)
-    `, [userId, `${tableName}:${policyName}`, { tableName, policyName, command, using, withCheck }]);
+    // Log audit (don't fail if audit logging fails)
+    try {
+      await pool.query(`
+        INSERT INTO __sys_audit_logs (user_id, action, resource_type, resource_id, details)
+        VALUES ($1, 'create_policy', 'rls_policy', $2, $3)
+      `, [userId, `${tableName}:${policyName}`, JSON.stringify({ tableName, policyName, command, using, withCheck })]);
+    } catch (auditError) {
+      logger.error('Failed to log policy creation audit:', auditError);
+    }
 
     logger.info(`RLS policy ${policyName} created on table ${tableName} by user ${userId}`);
     res.status(201).json({ message: 'Policy created successfully', policyName });
@@ -277,11 +285,15 @@ router.delete('/policies/:tableName/:policyName', async (req: any, res) => {
 
     await pool.query(`DROP POLICY IF EXISTS "${policyName}" ON "${tableName}"`);
 
-    // Log audit
-    await pool.query(`
-      INSERT INTO __sys_audit_logs (user_id, action, resource_type, resource_id, details)
-      VALUES ($1, 'drop_policy', 'rls_policy', $2, $3)
-    `, [userId, `${tableName}:${policyName}`, { tableName, policyName }]);
+    // Log audit (don't fail if audit logging fails)
+    try {
+      await pool.query(`
+        INSERT INTO __sys_audit_logs (user_id, action, resource_type, resource_id, details)
+        VALUES ($1, 'drop_policy', 'rls_policy', $2, $3)
+      `, [userId, `${tableName}:${policyName}`, JSON.stringify({ tableName, policyName })]);
+    } catch (auditError) {
+      logger.error('Failed to log policy drop audit:', auditError);
+    }
 
     logger.info(`RLS policy ${policyName} dropped from table ${tableName} by user ${userId}`);
     res.json({ message: 'Policy dropped successfully' });

+ 296 - 68
services/realtime/src/index.ts

@@ -1,8 +1,16 @@
 import { createServer } from 'http';
-import { WebSocketServer } from 'ws';
+import { WebSocketServer, WebSocket } from 'ws';
 import { createClient } from 'redis';
 import dotenv from 'dotenv';
+import { v4 as uuidv4 } from 'uuid';
 import { logger } from './utils/logger';
+import { authenticateConnection, JWTPayload } from './utils/auth';
+import {
+  isChannelAuthorized,
+  isValidChannelName,
+  getAuthorizedChannels,
+} from './utils/channels';
+import { ConnectionManager } from './utils/connectionManager';
 
 dotenv.config();
 
@@ -10,119 +18,308 @@ const PORT = process.env.PORT || 3002;
 
 const server = createServer();
 const wss = new WebSocketServer({ server });
+const connectionManager = new ConnectionManager();
 
-// Redis connection
-const redisClient = createClient({
-  url: process.env.REDIS_URL,
+// Redis clients - separate for pub and sub
+const redisPublisher = createClient({
+  url: process.env.REDIS_URL || 'redis://redis:6379',
 });
 
-redisClient.on('error', (err) => logger.error('Redis Client Error:', err));
-redisClient.on('connect', () => logger.info('Redis Client Connected'));
+const redisSubscriber = createClient({
+  url: process.env.REDIS_URL || 'redis://redis:6379',
+});
+
+redisPublisher.on('error', (err) => logger.error('Redis Publisher Error:', err));
+redisPublisher.on('connect', () => logger.info('Redis Publisher Connected'));
+
+redisSubscriber.on('error', (err) => logger.error('Redis Subscriber Error:', err));
+redisSubscriber.on('connect', () => logger.info('Redis Subscriber Connected'));
+
+// Track Redis channel subscriptions
+const activeChannels = new Set<string>();
 
 // WebSocket setup
-wss.on('connection', (ws, req) => {
-  logger.info(`Real-time client connected from ${req.socket.remoteAddress}`);
+wss.on('connection', async (ws, req) => {
+  const connectionId = uuidv4();
+  logger.info(
+    `WebSocket connection attempt from ${req.socket.remoteAddress} (ID: ${connectionId})`
+  );
 
+  // Authenticate the connection
+  const user = await authenticateConnection(req);
+
+  // Add connection to manager
+  const connection = connectionManager.addConnection(connectionId, ws, user);
+
+  // Send welcome message with connection info
+  ws.send(
+    JSON.stringify({
+      type: 'connected',
+      connectionId,
+      user: user
+        ? {
+            userId: user.userId,
+            email: user.email,
+            role: user.role,
+          }
+        : null,
+      authorizedChannels: getAuthorizedChannels(user),
+      timestamp: new Date().toISOString(),
+    })
+  );
+
+  // Handle incoming messages
   ws.on('message', async (data) => {
     try {
       const message = JSON.parse(data.toString());
-      logger.info('Real-time message received:', message);
 
       // Handle different message types
       switch (message.type) {
         case 'subscribe':
-          // Subscribe to channel
-          await handleSubscription(ws, message.channel);
+          await handleSubscription(connectionId, message.channel, user);
           break;
+
         case 'unsubscribe':
-          // Unsubscribe from channel
-          await handleUnsubscription(ws, message.channel);
+          await handleUnsubscription(connectionId, message.channel);
           break;
+
         case 'ping':
-          ws.send(JSON.stringify({ type: 'pong', timestamp: new Date().toISOString() }));
+        case 'pong':
+          connectionManager.updatePing(connectionId);
+          ws.send(
+            JSON.stringify({
+              type: 'pong',
+              timestamp: new Date().toISOString(),
+            })
+          );
+          break;
+
+        case 'stats':
+          // Send connection stats (admin only)
+          if (user?.role === 'admin') {
+            const stats = connectionManager.getStats();
+            ws.send(
+              JSON.stringify({
+                type: 'stats',
+                data: stats,
+                timestamp: new Date().toISOString(),
+              })
+            );
+          } else {
+            sendError(ws, 'Unauthorized to view stats');
+          }
+          break;
+
+        case 'list_channels':
+          // List channels the user can subscribe to
+          const channels = getAuthorizedChannels(user);
+          ws.send(
+            JSON.stringify({
+              type: 'channel_list',
+              channels,
+              timestamp: new Date().toISOString(),
+            })
+          );
           break;
+
         default:
-          logger.warn('Unknown real-time message type:', message.type);
+          logger.warn(`Unknown message type: ${message.type}`);
+          sendError(ws, `Unknown message type: ${message.type}`);
       }
     } catch (error) {
-      logger.error('Real-time message error:', error);
+      logger.error('Message processing error:', error);
+      sendError(ws, 'Failed to process message');
     }
   });
 
+  // Handle pong responses (from client)
+  ws.on('pong', () => {
+    connectionManager.updatePing(connectionId);
+  });
+
+  // Handle connection close
   ws.on('close', () => {
-    logger.info('Real-time client disconnected');
+    logger.info(`WebSocket closed: ${connectionId}`);
+    connectionManager.removeConnection(connectionId);
   });
 
+  // Handle errors
   ws.on('error', (error) => {
-    logger.error('Real-time WebSocket error:', error);
+    logger.error(`WebSocket error for ${connectionId}:`, error);
   });
-
-  // Send welcome message
-  ws.send(JSON.stringify({ 
-    type: 'welcome',
-    message: 'Connected to SaaS Platform Real-time Service',
-    timestamp: new Date().toISOString()
-  }));
 });
 
-async function handleSubscription(ws: any, channel: string) {
+/**
+ * Handle channel subscription
+ */
+async function handleSubscription(
+  connectionId: string,
+  channel: string,
+  user: JWTPayload | null
+): Promise<void> {
+  const connection = connectionManager.getConnection(connectionId);
+  if (!connection) {
+    logger.error(`Connection not found: ${connectionId}`);
+    return;
+  }
+
   try {
-    // Subscribe to Redis channel
-    await redisClient.subscribe(channel, (message) => {
-      if (ws.readyState === ws.OPEN) {
-        ws.send(JSON.stringify({
-          type: 'message',
-          channel,
-          data: JSON.parse(message),
-          timestamp: new Date().toISOString()
-        }));
-      }
-    });
+    // Validate channel name format
+    if (!isValidChannelName(channel)) {
+      sendError(connection.ws, `Invalid channel name: ${channel}`);
+      return;
+    }
 
-    ws.send(JSON.stringify({
-      type: 'subscribed',
-      channel,
-      timestamp: new Date().toISOString()
-    }));
+    // Check authorization
+    const authResult = isChannelAuthorized(channel, user);
+    if (!authResult.authorized) {
+      sendError(
+        connection.ws,
+        authResult.reason || 'Not authorized to subscribe to this channel'
+      );
+      logger.warn(
+        `Unauthorized subscription attempt: ${connectionId} -> ${channel} (${authResult.reason})`
+      );
+      return;
+    }
+
+    // Subscribe to Redis channel if not already subscribed
+    if (!activeChannels.has(channel)) {
+      await redisSubscriber.subscribe(channel, (message) => {
+        handleRedisMessage(channel, message);
+      });
+      activeChannels.add(channel);
+      logger.info(`Redis subscribed to channel: ${channel}`);
+    }
+
+    // Add channel to connection's subscriptions
+    connectionManager.subscribeToChannel(connectionId, channel);
 
-    logger.info(`Client subscribed to channel: ${channel}`);
+    // Send confirmation
+    connection.ws.send(
+      JSON.stringify({
+        type: 'subscribed',
+        channel,
+        timestamp: new Date().toISOString(),
+      })
+    );
+
+    logger.info(`Connection ${connectionId} subscribed to channel: ${channel}`);
   } catch (error) {
     logger.error('Subscription error:', error);
-    ws.send(JSON.stringify({
-      type: 'error',
-      message: 'Failed to subscribe to channel',
-      timestamp: new Date().toISOString()
-    }));
+    sendError(connection.ws, 'Failed to subscribe to channel');
+  }
+}
+
+/**
+ * Handle channel unsubscription
+ */
+async function handleUnsubscription(
+  connectionId: string,
+  channel: string
+): Promise<void> {
+  const connection = connectionManager.getConnection(connectionId);
+  if (!connection) {
+    logger.error(`Connection not found: ${connectionId}`);
+    return;
+  }
+
+  try {
+    // Remove channel from connection's subscriptions
+    connectionManager.unsubscribeFromChannel(connectionId, channel);
+
+    // Check if any other connections are still subscribed to this channel
+    const subscribersCount =
+      connectionManager.getConnectionsByChannel(channel).length;
+
+    // If no one is subscribed, unsubscribe from Redis
+    if (subscribersCount === 0 && activeChannels.has(channel)) {
+      await redisSubscriber.unsubscribe(channel);
+      activeChannels.delete(channel);
+      logger.info(`Redis unsubscribed from channel: ${channel}`);
+    }
+
+    // Send confirmation
+    connection.ws.send(
+      JSON.stringify({
+        type: 'unsubscribed',
+        channel,
+        timestamp: new Date().toISOString(),
+      })
+    );
+
+    logger.info(
+      `Connection ${connectionId} unsubscribed from channel: ${channel}`
+    );
+  } catch (error) {
+    logger.error('Unsubscription error:', error);
+    sendError(connection.ws, 'Failed to unsubscribe from channel');
   }
 }
 
-async function handleUnsubscription(ws: any, channel: string) {
+/**
+ * Handle messages from Redis pub/sub
+ */
+function handleRedisMessage(channel: string, message: string): void {
   try {
-    await redisClient.unsubscribe(channel);
-    
-    ws.send(JSON.stringify({
-      type: 'unsubscribed',
+    const data = JSON.parse(message);
+
+    // Broadcast to all connections subscribed to this channel
+    const sentCount = connectionManager.broadcastToChannel(channel, {
+      type: 'event',
       channel,
-      timestamp: new Date().toISOString()
-    }));
+      data,
+      timestamp: new Date().toISOString(),
+    });
 
-    logger.info(`Client unsubscribed from channel: ${channel}`);
+    logger.debug(
+      `Broadcasted message on ${channel} to ${sentCount} clients`
+    );
   } catch (error) {
-    logger.error('Unsubscription error:', error);
+    logger.error(`Error handling Redis message on ${channel}:`, error);
+  }
+}
+
+/**
+ * Send error message to client
+ */
+function sendError(ws: WebSocket, message: string): void {
+  if (ws.readyState === WebSocket.OPEN) {
+    ws.send(
+      JSON.stringify({
+        type: 'error',
+        message,
+        timestamp: new Date().toISOString(),
+      })
+    );
   }
 }
 
 // Health check endpoint
 server.on('request', (req, res) => {
   if (req.url === '/health') {
+    const stats = connectionManager.getStats();
+    res.writeHead(200, { 'Content-Type': 'application/json' });
+    res.end(
+      JSON.stringify({
+        status: 'ok',
+        service: 'realtime',
+        connections: stats.totalConnections,
+        authenticated: stats.authenticatedConnections,
+        activeChannels: activeChannels.size,
+        timestamp: new Date().toISOString(),
+      })
+    );
+    return;
+  }
+
+  if (req.url === '/stats') {
+    const stats = connectionManager.getStats();
     res.writeHead(200, { 'Content-Type': 'application/json' });
-    res.end(JSON.stringify({
-      status: 'ok',
-      service: 'realtime',
-      timestamp: new Date().toISOString()
-    }));
+    res.end(JSON.stringify(stats));
     return;
   }
+
   res.writeHead(404);
   res.end();
 });
@@ -130,11 +327,18 @@ server.on('request', (req, res) => {
 // Start server
 async function startServer() {
   try {
-    await redisClient.connect();
-    logger.info('Redis connected successfully');
+    // Connect both Redis clients
+    await redisPublisher.connect();
+    await redisSubscriber.connect();
+    logger.info('Redis clients connected successfully');
+
+    // Start heartbeat monitoring
+    connectionManager.startHeartbeat();
 
     server.listen(PORT, () => {
-      logger.info(`Real-time service running on port ${PORT}`);
+      logger.info(`Real-time WebSocket service running on port ${PORT}`);
+      logger.info(`Health check available at http://localhost:${PORT}/health`);
+      logger.info(`Stats available at http://localhost:${PORT}/stats`);
     });
   } catch (error) {
     logger.error('Failed to start server:', error);
@@ -147,7 +351,31 @@ startServer();
 // Graceful shutdown
 process.on('SIGINT', async () => {
   logger.info('Shutting down gracefully...');
-  await redisClient.quit();
-  server.close();
-  process.exit(0);
+
+  // Stop heartbeat
+  connectionManager.stopHeartbeat();
+
+  // Close all WebSocket connections
+  connectionManager.cleanup();
+
+  // Disconnect Redis clients
+  await redisPublisher.quit();
+  await redisSubscriber.quit();
+
+  // Close HTTP server
+  server.close(() => {
+    logger.info('Server closed');
+    process.exit(0);
+  });
+
+  // Force exit after 10 seconds
+  setTimeout(() => {
+    logger.error('Forced shutdown after timeout');
+    process.exit(1);
+  }, 10000);
+});
+
+process.on('SIGTERM', async () => {
+  logger.info('SIGTERM received, shutting down...');
+  process.emit('SIGINT' as any);
 });

+ 104 - 0
services/realtime/src/utils/auth.ts

@@ -0,0 +1,104 @@
+import jwt from 'jsonwebtoken';
+import { IncomingMessage } from 'http';
+import { logger } from './logger';
+
+export interface JWTPayload {
+  userId: string;
+  email: string;
+  role: string;
+  iat: number;
+  exp: number;
+}
+
+/**
+ * Extract JWT token from WebSocket connection request
+ * Supports multiple methods:
+ * 1. Query parameter: ws://localhost:3002?token=...
+ * 2. Authorization header: Authorization: Bearer ...
+ * 3. Sec-WebSocket-Protocol header
+ */
+export function extractToken(request: IncomingMessage): string | null {
+  try {
+    // Method 1: Query parameter
+    const url = new URL(request.url || '', `http://${request.headers.host}`);
+    const tokenFromQuery = url.searchParams.get('token');
+    if (tokenFromQuery) {
+      return tokenFromQuery;
+    }
+
+    // Method 2: Authorization header
+    const authHeader = request.headers.authorization;
+    if (authHeader && authHeader.startsWith('Bearer ')) {
+      return authHeader.substring(7);
+    }
+
+    // Method 3: Sec-WebSocket-Protocol (for browsers that don't support custom headers)
+    const protocol = request.headers['sec-websocket-protocol'];
+    if (protocol) {
+      const protocols = Array.isArray(protocol) ? protocol : protocol.split(',');
+      const tokenProtocol = protocols.find(p => p.trim().startsWith('token-'));
+      if (tokenProtocol) {
+        return tokenProtocol.trim().substring(6); // Remove 'token-' prefix
+      }
+    }
+
+    return null;
+  } catch (error) {
+    logger.error('Error extracting token:', error);
+    return null;
+  }
+}
+
+/**
+ * Verify JWT token and return payload
+ */
+export async function verifyToken(token: string): Promise<JWTPayload | null> {
+  try {
+    const jwtSecret = process.env.JWT_SECRET;
+    if (!jwtSecret) {
+      logger.error('JWT_SECRET is not configured');
+      return null;
+    }
+
+    const payload = jwt.verify(token, jwtSecret) as JWTPayload;
+
+    // Check if token is expired
+    if (payload.exp && payload.exp < Date.now() / 1000) {
+      logger.warn('Token is expired');
+      return null;
+    }
+
+    return payload;
+  } catch (error) {
+    if (error instanceof jwt.JsonWebTokenError) {
+      logger.warn('Invalid JWT token:', error.message);
+    } else if (error instanceof jwt.TokenExpiredError) {
+      logger.warn('JWT token expired');
+    } else {
+      logger.error('Error verifying token:', error);
+    }
+    return null;
+  }
+}
+
+/**
+ * Authenticate WebSocket connection
+ */
+export async function authenticateConnection(request: IncomingMessage): Promise<JWTPayload | null> {
+  const token = extractToken(request);
+
+  if (!token) {
+    logger.warn('No token provided in WebSocket connection');
+    return null;
+  }
+
+  const payload = await verifyToken(token);
+
+  if (!payload) {
+    logger.warn('Token verification failed');
+    return null;
+  }
+
+  logger.info(`User authenticated: ${payload.email} (${payload.userId})`);
+  return payload;
+}

+ 206 - 0
services/realtime/src/utils/channels.ts

@@ -0,0 +1,206 @@
+import { JWTPayload } from './auth';
+import { logger } from './logger';
+
+export type ChannelType =
+  | 'apps'
+  | 'users'
+  | 'database'
+  | 'deployments'
+  | 'storage'
+  | 'system';
+
+export interface ChannelPermission {
+  pattern: RegExp;
+  requiresAuth: boolean;
+  allowedRoles?: string[];
+  validator?: (channel: string, user: JWTPayload) => boolean;
+}
+
+/**
+ * Channel permission configuration
+ * Defines which channels users can subscribe to based on their authentication and role
+ */
+export const CHANNEL_PERMISSIONS: Record<string, ChannelPermission> = {
+  // System-wide channels (admin only)
+  'system:*': {
+    pattern: /^system:.+$/,
+    requiresAuth: true,
+    allowedRoles: ['admin'],
+  },
+
+  // Application events (authenticated users)
+  'apps:*': {
+    pattern: /^apps:(created|updated|deleted|deployed|status)$/,
+    requiresAuth: true,
+  },
+
+  // User events (authenticated users, own user or admin)
+  'users:*': {
+    pattern: /^users:(created|updated|deleted)$/,
+    requiresAuth: true,
+  },
+
+  // User-specific channel (only the user themselves or admin)
+  'user:*': {
+    pattern: /^user:[a-f0-9-]{36}$/,
+    requiresAuth: true,
+    validator: (channel: string, user: JWTPayload) => {
+      const userId = channel.split(':')[1];
+      return user.role === 'admin' || user.userId === userId;
+    },
+  },
+
+  // Database events (authenticated users)
+  'database:*': {
+    pattern: /^database:(table_created|table_updated|table_deleted|row_updated)$/,
+    requiresAuth: true,
+  },
+
+  // Deployment events (authenticated users)
+  'deployments:*': {
+    pattern: /^deployments:(started|completed|failed|status)$/,
+    requiresAuth: true,
+  },
+
+  // Storage events (authenticated users)
+  'storage:*': {
+    pattern: /^storage:(uploaded|deleted|updated)$/,
+    requiresAuth: true,
+  },
+
+  // Public announcement channel (no auth required)
+  'announcements': {
+    pattern: /^announcements$/,
+    requiresAuth: false,
+  },
+};
+
+/**
+ * Check if a user is authorized to subscribe to a channel
+ */
+export function isChannelAuthorized(
+  channel: string,
+  user: JWTPayload | null
+): { authorized: boolean; reason?: string } {
+  // Find matching permission rule
+  let matchedPermission: ChannelPermission | null = null;
+
+  for (const [key, permission] of Object.entries(CHANNEL_PERMISSIONS)) {
+    if (permission.pattern.test(channel)) {
+      matchedPermission = permission;
+      break;
+    }
+  }
+
+  // Channel not found in permissions - deny by default
+  if (!matchedPermission) {
+    logger.warn(`Unknown channel requested: ${channel}`);
+    return {
+      authorized: false,
+      reason: `Channel '${channel}' is not recognized`,
+    };
+  }
+
+  // Check if authentication is required
+  if (matchedPermission.requiresAuth && !user) {
+    return {
+      authorized: false,
+      reason: 'Authentication required for this channel',
+    };
+  }
+
+  // Check role requirements
+  if (matchedPermission.allowedRoles && user) {
+    if (!matchedPermission.allowedRoles.includes(user.role)) {
+      return {
+        authorized: false,
+        reason: `Insufficient permissions. Required roles: ${matchedPermission.allowedRoles.join(', ')}`,
+      };
+    }
+  }
+
+  // Run custom validator if provided
+  if (matchedPermission.validator && user) {
+    if (!matchedPermission.validator(channel, user)) {
+      return {
+        authorized: false,
+        reason: 'Custom validation failed for this channel',
+      };
+    }
+  }
+
+  return { authorized: true };
+}
+
+/**
+ * Get all channels a user is authorized to subscribe to
+ */
+export function getAuthorizedChannels(user: JWTPayload | null): string[] {
+  const channels: string[] = [];
+
+  // Public channels
+  channels.push('announcements');
+
+  if (!user) {
+    return channels;
+  }
+
+  // General event channels
+  channels.push(
+    'apps:created',
+    'apps:updated',
+    'apps:deleted',
+    'apps:deployed',
+    'apps:status',
+    'users:created',
+    'users:updated',
+    'users:deleted',
+    'database:table_created',
+    'database:table_updated',
+    'database:table_deleted',
+    'database:row_updated',
+    'deployments:started',
+    'deployments:completed',
+    'deployments:failed',
+    'deployments:status',
+    'storage:uploaded',
+    'storage:deleted',
+    'storage:updated'
+  );
+
+  // User-specific channel
+  channels.push(`user:${user.userId}`);
+
+  // Admin-only channels
+  if (user.role === 'admin') {
+    channels.push('system:health', 'system:metrics', 'system:alerts');
+  }
+
+  return channels;
+}
+
+/**
+ * Validate channel name format
+ */
+export function isValidChannelName(channel: string): boolean {
+  // Must be lowercase alphanumeric with colons, hyphens, or underscores
+  const validFormat = /^[a-z0-9:_-]+$/;
+
+  // Must not be too long
+  const maxLength = 100;
+
+  // Must not be empty
+  if (!channel || channel.length === 0) {
+    return false;
+  }
+
+  if (channel.length > maxLength) {
+    return false;
+  }
+
+  if (!validFormat.test(channel)) {
+    return false;
+  }
+
+  return true;
+}

+ 301 - 0
services/realtime/src/utils/connectionManager.ts

@@ -0,0 +1,301 @@
+import { WebSocket } from 'ws';
+import { JWTPayload } from './auth';
+import { logger } from './logger';
+
+export interface ClientConnection {
+  ws: WebSocket;
+  id: string;
+  user: JWTPayload | null;
+  subscribedChannels: Set<string>;
+  connectedAt: Date;
+  lastPing: Date;
+  isAlive: boolean;
+}
+
+/**
+ * Connection Manager - tracks all active WebSocket connections
+ */
+export class ConnectionManager {
+  private connections: Map<string, ClientConnection>;
+  private heartbeatInterval: NodeJS.Timeout | null = null;
+  private readonly HEARTBEAT_INTERVAL = 30000; // 30 seconds
+  private readonly HEARTBEAT_TIMEOUT = 35000; // 35 seconds
+
+  constructor() {
+    this.connections = new Map();
+  }
+
+  /**
+   * Add a new connection
+   */
+  addConnection(
+    id: string,
+    ws: WebSocket,
+    user: JWTPayload | null
+  ): ClientConnection {
+    const connection: ClientConnection = {
+      ws,
+      id,
+      user,
+      subscribedChannels: new Set(),
+      connectedAt: new Date(),
+      lastPing: new Date(),
+      isAlive: true,
+    };
+
+    this.connections.set(id, connection);
+    logger.info(
+      `Connection added: ${id} (User: ${user?.email || 'anonymous'}, Total: ${this.connections.size})`
+    );
+
+    return connection;
+  }
+
+  /**
+   * Remove a connection
+   */
+  removeConnection(id: string): void {
+    const connection = this.connections.get(id);
+    if (connection) {
+      connection.subscribedChannels.clear();
+      this.connections.delete(id);
+      logger.info(
+        `Connection removed: ${id} (Total: ${this.connections.size})`
+      );
+    }
+  }
+
+  /**
+   * Get a connection by ID
+   */
+  getConnection(id: string): ClientConnection | undefined {
+    return this.connections.get(id);
+  }
+
+  /**
+   * Get all connections
+   */
+  getAllConnections(): ClientConnection[] {
+    return Array.from(this.connections.values());
+  }
+
+  /**
+   * Get connections by user ID
+   */
+  getConnectionsByUserId(userId: string): ClientConnection[] {
+    return Array.from(this.connections.values()).filter(
+      (conn) => conn.user?.userId === userId
+    );
+  }
+
+  /**
+   * Get connections subscribed to a channel
+   */
+  getConnectionsByChannel(channel: string): ClientConnection[] {
+    return Array.from(this.connections.values()).filter((conn) =>
+      conn.subscribedChannels.has(channel)
+    );
+  }
+
+  /**
+   * Subscribe a connection to a channel
+   */
+  subscribeToChannel(connectionId: string, channel: string): boolean {
+    const connection = this.connections.get(connectionId);
+    if (!connection) {
+      return false;
+    }
+
+    connection.subscribedChannels.add(channel);
+    logger.info(`Connection ${connectionId} subscribed to channel: ${channel}`);
+    return true;
+  }
+
+  /**
+   * Unsubscribe a connection from a channel
+   */
+  unsubscribeFromChannel(connectionId: string, channel: string): boolean {
+    const connection = this.connections.get(connectionId);
+    if (!connection) {
+      return false;
+    }
+
+    connection.subscribedChannels.delete(channel);
+    logger.info(
+      `Connection ${connectionId} unsubscribed from channel: ${channel}`
+    );
+    return true;
+  }
+
+  /**
+   * Broadcast a message to all connections on a channel
+   */
+  broadcastToChannel(channel: string, message: any): number {
+    const connections = this.getConnectionsByChannel(channel);
+    let sentCount = 0;
+
+    for (const connection of connections) {
+      if (
+        connection.ws.readyState === WebSocket.OPEN &&
+        connection.isAlive
+      ) {
+        try {
+          connection.ws.send(JSON.stringify(message));
+          sentCount++;
+        } catch (error) {
+          logger.error(
+            `Error broadcasting to connection ${connection.id}:`,
+            error
+          );
+        }
+      }
+    }
+
+    logger.debug(
+      `Broadcast to channel ${channel}: ${sentCount}/${connections.length} clients`
+    );
+    return sentCount;
+  }
+
+  /**
+   * Send a message to a specific user (all their connections)
+   */
+  sendToUser(userId: string, message: any): number {
+    const connections = this.getConnectionsByUserId(userId);
+    let sentCount = 0;
+
+    for (const connection of connections) {
+      if (
+        connection.ws.readyState === WebSocket.OPEN &&
+        connection.isAlive
+      ) {
+        try {
+          connection.ws.send(JSON.stringify(message));
+          sentCount++;
+        } catch (error) {
+          logger.error(
+            `Error sending to connection ${connection.id}:`,
+            error
+          );
+        }
+      }
+    }
+
+    return sentCount;
+  }
+
+  /**
+   * Start heartbeat monitoring
+   */
+  startHeartbeat(): void {
+    if (this.heartbeatInterval) {
+      return; // Already started
+    }
+
+    logger.info('Starting heartbeat monitoring');
+
+    this.heartbeatInterval = setInterval(() => {
+      const now = Date.now();
+
+      for (const [id, connection] of this.connections.entries()) {
+        // Check if connection is still alive
+        const timeSinceLastPing = now - connection.lastPing.getTime();
+
+        if (timeSinceLastPing > this.HEARTBEAT_TIMEOUT) {
+          logger.warn(
+            `Connection ${id} timed out (${timeSinceLastPing}ms since last ping)`
+          );
+          connection.isAlive = false;
+          connection.ws.terminate();
+          this.removeConnection(id);
+          continue;
+        }
+
+        // Send ping to client
+        if (connection.ws.readyState === WebSocket.OPEN) {
+          try {
+            connection.ws.ping();
+            connection.ws.send(
+              JSON.stringify({
+                type: 'ping',
+                timestamp: new Date().toISOString(),
+              })
+            );
+          } catch (error) {
+            logger.error(`Error sending ping to ${id}:`, error);
+          }
+        }
+      }
+    }, this.HEARTBEAT_INTERVAL);
+  }
+
+  /**
+   * Stop heartbeat monitoring
+   */
+  stopHeartbeat(): void {
+    if (this.heartbeatInterval) {
+      clearInterval(this.heartbeatInterval);
+      this.heartbeatInterval = null;
+      logger.info('Heartbeat monitoring stopped');
+    }
+  }
+
+  /**
+   * Update last ping time for a connection
+   */
+  updatePing(connectionId: string): void {
+    const connection = this.connections.get(connectionId);
+    if (connection) {
+      connection.lastPing = new Date();
+      connection.isAlive = true;
+    }
+  }
+
+  /**
+   * Get statistics
+   */
+  getStats(): {
+    totalConnections: number;
+    authenticatedConnections: number;
+    anonymousConnections: number;
+    totalSubscriptions: number;
+    connectionsByChannel: Record<string, number>;
+  } {
+    const authenticated = Array.from(this.connections.values()).filter(
+      (c) => c.user !== null
+    );
+    const anonymous = Array.from(this.connections.values()).filter(
+      (c) => c.user === null
+    );
+
+    const channelCounts: Record<string, number> = {};
+    let totalSubs = 0;
+
+    for (const connection of this.connections.values()) {
+      totalSubs += connection.subscribedChannels.size;
+      for (const channel of connection.subscribedChannels) {
+        channelCounts[channel] = (channelCounts[channel] || 0) + 1;
+      }
+    }
+
+    return {
+      totalConnections: this.connections.size,
+      authenticatedConnections: authenticated.length,
+      anonymousConnections: anonymous.length,
+      totalSubscriptions: totalSubs,
+      connectionsByChannel: channelCounts,
+    };
+  }
+
+  /**
+   * Cleanup all connections
+   */
+  cleanup(): void {
+    this.stopHeartbeat();
+    for (const [id, connection] of this.connections.entries()) {
+      connection.ws.close();
+      this.removeConnection(id);
+    }
+    logger.info('All connections cleaned up');
+  }
+}