# MoreMinimore SAAS - Technical Specification ## Executive Summary Transform MoreMinimore from a local Electron app into a full-featured SAAS platform for AI-powered web application development with Easypanel deployment integration. ## Table of Contents 1. [Architecture Overview](#architecture-overview) 2. [Technology Stack](#technology-stack) 3. [Database Schema](#database-schema) 4. [Authentication & Authorization](#authentication--authorization) 5. [User Roles & Permissions](#user-roles--permissions) 6. [Core Features](#core-features) 7. [Deployment Flow](#deployment-flow) 8. [UI/UX Pro Max Integration](#uiux-pro-max-integration) 9. [Easypanel Integration](#easypanel-integration) 10. [Billing & Pricing](#billing--pricing) 11. [Code Storage Strategy](#code-storage-strategy) 12. [Migration Strategy](#migration-strategy) 13. [Security Considerations](#security-considerations) 14. [Performance Requirements](#performance-requirements) 15. [Development Phases](#development-phases) --- ## Architecture Overview ### System Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ MoreMinimore SAAS │ │ (Next.js Web Application) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Application Layer │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Auth API │ │ Project API │ │ Billing API │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ User API │ │ Deploy API │ │ Admin API │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Service Layer │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ AI Service │ │ Git Service │ │ Deploy Svc │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Billing Svc │ │ Email Svc │ │ Notif Svc │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Data Layer │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ PostgreSQL │ │ Redis │ │ Gitea │ │ │ │ (Primary) │ │ (Cache) │ │ (Backup) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ External Services │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Stripe │ │ Easypanel │ │ AI Providers│ │ │ │ (Billing) │ │ (Deploy) │ │ (OpenAI,etc)│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### Key Architectural Decisions 1. **Monolithic Next.js App**: Simplifies deployment and development 2. **PostgreSQL as Primary DB**: Robust, scalable, supports complex queries 3. **Redis for Caching**: Session management, rate limiting, temporary data 4. **Gitea for Code Backup**: Version control, collaboration, backup 5. **Easypanel for Production Deployment**: One-click deployment for users --- ## Technology Stack ### Frontend - **Framework**: Next.js 15 (App Router) - **UI Library**: React 19 - **Styling**: Tailwind CSS 4 - **Components**: shadcn/ui (Radix UI primitives) - **State Management**: Zustand (global state) + React Query (server state) - **Routing**: Next.js App Router - **Forms**: React Hook Form + Zod validation - **Markdown**: react-markdown + remark-gfm - **Code Editor**: Monaco Editor (via @monaco-editor/react) ### Backend - **Runtime**: Node.js 20+ - **API**: Next.js API Routes (App Router) - **Database ORM**: Drizzle ORM - **Authentication**: Custom JWT implementation - **File Upload**: Uploadthing or similar - **Email**: Resend or SendGrid - **Background Jobs**: BullMQ (Redis-based) or similar ### Database - **Primary**: PostgreSQL 16+ - **Cache**: Redis 7+ - **ORM**: Drizzle ORM - **Migrations**: Drizzle Kit ### DevOps - **Hosting**: VPS (shared resources) - **Reverse Proxy**: Nginx - **Process Manager**: PM2 - **SSL**: Let's Encrypt (Certbot) - **Monitoring**: Custom logging + error tracking - **CI/CD**: GitHub Actions ### External Services - **Payment**: Stripe - **Deployment**: Easypanel API - **Code Backup**: Gitea (self-hosted) - **AI Providers**: OpenAI, Anthropic, Google, etc. (bring your own key) --- ## Database Schema ### Core Tables ```sql -- Users table CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(255), role VARCHAR(50) NOT NULL, -- 'admin', 'co_admin', 'owner', 'user' avatar_url TEXT, email_verified BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), last_login_at TIMESTAMP, is_active BOOLEAN DEFAULT TRUE ); -- Organizations (for multi-tenancy) CREATE TABLE organizations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, slug VARCHAR(255) UNIQUE NOT NULL, owner_id UUID REFERENCES users(id) ON DELETE CASCADE, stripe_customer_id VARCHAR(255), subscription_tier VARCHAR(50) DEFAULT 'free', -- 'free', 'pro', 'enterprise' subscription_status VARCHAR(50) DEFAULT 'active', -- 'active', 'past_due', 'canceled', 'trialing' trial_ends_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Organization Members (for Owner/User relationships) CREATE TABLE organization_members ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE, user_id UUID REFERENCES users(id) ON DELETE CASCADE, role VARCHAR(50) NOT NULL, -- 'owner', 'admin', 'member', 'viewer' permissions JSONB, -- Granular permissions invited_by UUID REFERENCES users(id), joined_at TIMESTAMP DEFAULT NOW(), UNIQUE(organization_id, user_id) ); -- Projects (formerly 'apps') CREATE TABLE projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, description TEXT, slug VARCHAR(255) NOT NULL, gitea_repo_id INTEGER, gitea_repo_url TEXT, easypanel_project_id VARCHAR(255), easypanel_app_id VARCHAR(255), easypanel_database_id VARCHAR(255), deployment_url TEXT, install_command TEXT DEFAULT 'npm install', start_command TEXT DEFAULT 'npm start', build_command TEXT DEFAULT 'npm run build', environment_variables JSONB DEFAULT '{}', status VARCHAR(50) DEFAULT 'draft', -- 'draft', 'building', 'deployed', 'error' created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), last_deployed_at TIMESTAMP, UNIQUE(organization_id, slug) ); -- Project Versions CREATE TABLE project_versions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, version_number VARCHAR(50) NOT NULL, commit_hash VARCHAR(255), gitea_commit_id VARCHAR(255), is_current BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW(), UNIQUE(project_id, version_number) ); -- Chats (conversations) CREATE TABLE chats ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, title VARCHAR(255), created_by UUID REFERENCES users(id), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Messages CREATE TABLE messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), chat_id UUID REFERENCES chats(id) ON DELETE CASCADE, role VARCHAR(50) NOT NULL, -- 'user', 'assistant', 'system' content TEXT NOT NULL, metadata JSONB, -- Tool calls, tokens used, etc. created_at TIMESTAMP DEFAULT NOW() ); -- Prompts (templates) CREATE TABLE prompts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255) NOT NULL, description TEXT, content TEXT NOT NULL, category VARCHAR(100), is_public BOOLEAN DEFAULT FALSE, created_by UUID REFERENCES users(id), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- AI Model Providers CREATE TABLE ai_providers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, api_base_url TEXT NOT NULL, env_var_name VARCHAR(100), is_builtin BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- AI Models CREATE TABLE ai_models ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), display_name VARCHAR(255) NOT NULL, api_name VARCHAR(255) NOT NULL, provider_id UUID REFERENCES ai_providers(id) ON DELETE CASCADE, description TEXT, max_output_tokens INTEGER, context_window INTEGER, is_available BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- User API Keys (for AI providers) CREATE TABLE user_api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, provider_id UUID REFERENCES ai_providers(id) ON DELETE CASCADE, encrypted_key TEXT NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(user_id, provider_id) ); -- Design Systems (from UI/UX Pro Max) CREATE TABLE design_systems ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, pattern VARCHAR(255), style VARCHAR(255), color_palette JSONB, typography JSONB, effects JSONB, anti_patterns JSONB, generated_by_ai BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Deployment Logs CREATE TABLE deployment_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, version_id UUID REFERENCES project_versions(id), status VARCHAR(50) NOT NULL, -- 'pending', 'success', 'failed' logs TEXT, error_message TEXT, started_at TIMESTAMP DEFAULT NOW(), completed_at TIMESTAMP ); -- Billing/Invoices CREATE TABLE invoices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE, stripe_invoice_id VARCHAR(255) UNIQUE, amount DECIMAL(10, 2) NOT NULL, currency VARCHAR(3) DEFAULT 'USD', status VARCHAR(50) NOT NULL, -- 'draft', 'open', 'paid', 'void', 'uncollectible' due_date TIMESTAMP, paid_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); -- Subscription Events CREATE TABLE subscription_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE, event_type VARCHAR(100) NOT NULL, -- 'created', 'updated', 'canceled', 'trial_ended' stripe_event_id VARCHAR(255), metadata JSONB, created_at TIMESTAMP DEFAULT NOW() ); -- Audit Logs CREATE TABLE audit_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, organization_id UUID REFERENCES organizations(id) ON DELETE SET NULL, action VARCHAR(255) NOT NULL, resource_type VARCHAR(100), resource_id UUID, metadata JSONB, ip_address INET, user_agent TEXT, created_at TIMESTAMP DEFAULT NOW() ); -- Sessions (for JWT refresh tokens) CREATE TABLE sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, refresh_token_hash VARCHAR(255) NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT NOW(), device_info JSONB ); -- Email Verification Tokens CREATE TABLE email_verification_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(255) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); -- Password Reset Tokens CREATE TABLE password_reset_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(255) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); -- Indexes for performance CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_organizations_slug ON organizations(slug); CREATE INDEX idx_organizations_owner ON organizations(owner_id); CREATE INDEX idx_org_members_org ON organization_members(organization_id); CREATE INDEX idx_org_members_user ON organization_members(user_id); CREATE INDEX idx_projects_org ON projects(organization_id); CREATE INDEX idx_projects_slug ON projects(organization_id, slug); CREATE INDEX idx_chats_project ON chats(project_id); CREATE INDEX idx_messages_chat ON messages(chat_id); CREATE INDEX idx_messages_created ON messages(created_at); CREATE INDEX idx_deployment_logs_project ON deployment_logs(project_id); CREATE INDEX idx_audit_logs_user ON audit_logs(user_id); CREATE INDEX idx_audit_logs_org ON audit_logs(organization_id); CREATE INDEX idx_audit_logs_created ON audit_logs(created_at); ``` --- ## Authentication & Authorization ### Authentication Flow ``` ┌─────────────┐ │ User │ └──────┬──────┘ │ │ 1. POST /api/auth/register │ { email, password, fullName } ▼ ┌─────────────────┐ │ Next.js API │ │ - Validate │ │ - Hash password│ │ - Create user │ │ - Send email │ └────────┬────────┘ │ │ 2. Email verification ▼ ┌─────────────────┐ │ User clicks │ │ verification │ │ link │ └────────┬────────┘ │ │ 3. POST /api/auth/login │ { email, password } ▼ ┌─────────────────┐ │ Next.js API │ │ - Verify creds │ │ - Generate JWT │ │ - Set cookies │ └────────┬────────┘ │ │ 4. Response with JWT ▼ ┌─────────────────┐ │ Client │ │ - Store JWT │ │ - Use in API │ └─────────────────┘ ``` ### JWT Structure ```typescript // Access Token (15 minutes) interface AccessTokenPayload { userId: string; email: string; role: UserRole; organizationId?: string; // For non-admin users iat: number; exp: number; } // Refresh Token (7 days) interface RefreshTokenPayload { userId: string; sessionId: string; iat: number; exp: number; } ``` ### Authorization Middleware ```typescript // Middleware to check permissions export async function requireAuth( request: NextRequest, allowedRoles?: UserRole[], ) { const token = request.cookies.get("access_token")?.value; if (!token) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } try { const payload = verifyJWT(token) as AccessTokenPayload; if (allowedRoles && !allowedRoles.includes(payload.role)) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } return payload; } catch (error) { return NextResponse.json({ error: "Invalid token" }, { status: 401 }); } } // Check organization membership export async function requireOrgMembership( request: NextRequest, orgId: string, ) { const user = await requireAuth(request); if (user.role === "admin" || user.role === "co_admin") { return user; } // Check if user is member of organization const member = await db.query.organizationMembers.findFirst({ where: eq(organizationMembers.organizationId, orgId), with: { user: true }, }); if (!member) { return NextResponse.json({ error: "Not a member" }, { status: 403 }); } return { ...user, orgRole: member.role, permissions: member.permissions }; } ``` --- ## User Roles & Permissions ### Role Definitions #### 1. Admin - **Scope**: Global system control - **Permissions**: - Full access to all organizations and projects - Manage all users (create, update, delete, ban) - Manage system settings - View all audit logs - Manage billing for all organizations - Access admin dashboard - Override any restrictions #### 2. Co-Admin - **Scope**: Global settings and AI model management - **Permissions**: - Manage AI models and providers - Update system-wide settings - View system metrics and logs - Manage pricing tiers - Access admin dashboard (limited) - Cannot delete organizations or users #### 3. Owner - **Scope**: Their own organization - **Permissions**: - Full control over their organization - Create, update, delete projects - Invite and manage team members - Set member permissions - Manage organization billing - Deploy projects to Easypanel - Access organization analytics - Export/import project data #### 4. User (Organization Member) - **Scope**: Assigned projects within organization - **Permissions** (configurable by Owner): - **View**: Read-only access to assigned projects - **Edit**: Can modify code and chat - **Deploy**: Can deploy to Easypanel - **Manage**: Can manage project settings - **Invite**: Can invite other members (if granted) ### Permission Matrix | Action | Admin | Co-Admin | Owner | User (View) | User (Edit) | User (Deploy) | | ---------------------- | ----- | -------- | ----- | ----------- | ----------- | ------------- | | View all organizations | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Manage users | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Manage AI models | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | View system logs | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | Create projects | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | Edit own projects | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | | Deploy projects | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | | Invite members | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | Manage billing | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | View analytics | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | --- ## Core Features ### 1. User Management #### Registration - Email/password registration - Email verification required - Optional social login (Google, GitHub) - future enhancement #### Login - Email/password login - Remember me option - Session management with refresh tokens #### Profile Management - Update profile information - Change password - Manage API keys for AI providers - Two-factor authentication (future) #### Team Management (for Owners) - Invite team members via email - Set member roles and permissions - Remove members - View member activity ### 2. Project Management #### Create Project - Project name and description - Auto-generate unique slug - Select template (optional) - Initialize Git repository in Gitea #### Project Dashboard - Overview of project status - Recent activity - Deployment status - Team members access #### Project Settings - Update project details - Configure build commands - Set environment variables - Manage deployment settings - Delete project #### Version Control - View version history - Compare versions - Rollback to previous version - Tag releases ### 3. AI-Powered Development #### Chat Interface - Natural language conversation with AI - Context-aware responses - Code generation and modification - Real-time preview #### Code Editor - Monaco editor integration - Syntax highlighting - Auto-completion - File tree navigation - Multiple file editing #### Design System Generation (UI/UX Pro Max) - Automatic design system generation - Industry-specific recommendations - Color palette selection - Typography pairing - Style guidelines #### Preview - Live preview of generated code - Responsive design testing - Interactive components ### 4. Deployment Management #### Easypanel Integration - One-click deployment - Automatic database creation - Environment variable management - Deployment logs - Rollback capability #### Deployment History - View all deployments - Compare deployments - Rollback to previous version - Deployment analytics ### 5. Billing & Subscription #### Subscription Tiers - **Free**: 1 project, limited AI tokens, community support - **Pro**: 10 projects, unlimited AI tokens, priority support, custom domains - **Enterprise**: Unlimited projects, dedicated support, SLA, advanced features #### Billing Management - View invoices - Update payment method - Change subscription tier - Cancel subscription - Usage analytics ### 6. Admin Dashboard #### System Overview - Total users and organizations - Active projects - Revenue metrics - System health #### User Management - View all users - Manage user accounts - View user activity - Ban/unban users #### Organization Management - View all organizations - Manage subscriptions - View organization analytics #### System Settings - AI model configuration - Pricing tiers management - System-wide settings - Maintenance mode --- ## Deployment Flow ### Complete Deployment Workflow ``` ┌─────────────────────────────────────────────────────────────┐ │ 1. User develops project in MoreMinimore │ │ - Chat with AI to generate code │ │ - Use UI/UX Pro Max for design │ │ - Preview in browser │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 2. User clicks "Deploy to Easypanel" │ │ - Select deployment settings │ │ - Configure environment variables │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 3. MoreMinimore creates version │ │ - Commit changes to Gitea │ │ - Create project version record │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 4. MoreMinimore calls Easypanel API │ │ - Create database (PostgreSQL) │ │ - Create application │ │ - Configure environment variables │ │ - Set build and start commands │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 5. Easypanel provisions resources │ │ - Spins up PostgreSQL database │ │ - Deploys application container │ │ - Configures networking │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 6. Easypanel pulls code from Gitea │ │ - Clone repository │ │ - Install dependencies │ │ - Build application │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 7. Application is live │ │ - User receives deployment URL │ │ - Health checks pass │ │ - Monitoring enabled │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 8. Future updates │ │ - User makes changes in MoreMinimore │ │ - Clicks "Update Deployment" │ │ - Easypanel pulls latest code and redeploys │ └─────────────────────────────────────────────────────────────┘ ``` ### Update Deployment Flow ``` ┌─────────────────────────────────────────────────────────────┐ │ 1. User makes changes to project │ │ - Chat with AI to modify code │ │ - Test in preview │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 2. User clicks "Update Deployment" │ │ - Review changes │ │ - Confirm update │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 3. MoreMinimore commits to Gitea │ │ - Create new commit │ │ - Update project version │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 4. MoreMinimore triggers Easypanel redeploy │ │ - Call Easypanel API │ │ - Trigger deployment │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 5. Easypanel pulls latest code │ │ - Git pull from Gitea │ │ - Rebuild application │ │ - Restart services │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 6. Update complete │ │ - Zero-downtime deployment (if supported) │ │ - User notified │ └─────────────────────────────────────────────────────────────┘ ``` --- ## UI/UX Pro Max Integration ### Integration Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ MoreMinimore SAAS │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Design System Service │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ 1. Analyze user request │ │ │ │ 2. Call UI/UX Pro Max Python script │ │ │ │ 3. Parse design system output │ │ │ │ 4. Store in database │ │ │ │ 5. Return to frontend │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ UI/UX Pro Max (Python Script) │ │ - Multi-domain search │ │ - Reasoning engine │ │ - Design system generation │ └─────────────────────────────────────────────────────────────┘ ``` ### Implementation Strategy #### 1. Install UI/UX Pro Max ```bash # Install CLI globally npm install -g uipro-cli # Or copy skill files to project # .claude/skills/ui-ux-pro-max/ # .shared/ui-ux-pro-max/ ``` #### 2. Create Design System Service ```typescript // src/lib/services/design-system.service.ts import { exec } from "child_process"; import { promisify } from "util"; const execAsync = promisify(exec); export interface DesignSystem { pattern: string; style: string; colors: { primary: string; secondary: string; accent: string; background: string; text: string; }; typography: { heading: string; body: string; googleFontsUrl: string; }; effects: string[]; antiPatterns: string[]; checklist: string[]; } export class DesignSystemService { async generateDesignSystem( projectDescription: string, projectName?: string, ): Promise { const args = [ "python3", ".claude/skills/ui-ux-pro-max/scripts/search.py", `"${projectDescription}"`, "--design-system", "-f", "json", ]; if (projectName) { args.push("-p", `"${projectName}"`); } const { stdout, stderr } = await execAsync(args.join(" ")); if (stderr) { console.error("Design system generation error:", stderr); } const designSystem = JSON.parse(stdout); return designSystem; } async persistDesignSystem( projectId: string, designSystem: DesignSystem, pageName?: string, ): Promise { // Store in database await db.insert(designSystems).values({ projectId, name: designSystem.style, pattern: designSystem.pattern, style: designSystem.style, colorPalette: designSystem.colors, typography: designSystem.typography, effects: designSystem.effects, antiPatterns: designSystem.antiPatterns, generatedByAi: true, }); // Optionally persist to files for hierarchical retrieval if (pageName) { await this.persistToFile(projectId, designSystem, pageName); } } private async persistToFile( projectId: string, designSystem: DesignSystem, pageName?: string, ): Promise { // Implementation for file-based persistence // Creates design-system/MASTER.md and design-system/pages/{page}.md } async getDesignSystem( projectId: string, pageName?: string, ): Promise { // Hierarchical retrieval logic if (pageName) { const pageSystem = await db.query.designSystems.findFirst({ where: and( eq(designSystems.projectId, projectId), eq(designSystems.name, pageName), ), }); if (pageSystem) { return this.deserializeDesignSystem(pageSystem); } } const masterSystem = await db.query.designSystems.findFirst({ where: eq(designSystems.projectId, projectId), }); return masterSystem ? this.deserializeDesignSystem(masterSystem) : null; } private deserializeDesignSystem(record: any): DesignSystem { return { pattern: record.pattern, style: record.style, colors: record.colorPalette, typography: record.typography, effects: record.effects, antiPatterns: record.antiPatterns, checklist: [], }; } } ``` #### 3. AI Prompt Enhancement When generating code, the AI will be instructed to: 1. Read the design system from the database 2. Apply the design system guidelines 3. Generate code that matches the design specifications 4. Validate against anti-patterns ```typescript // Example prompt enhancement const enhancedPrompt = ` You are building a ${projectDescription}. Design System: ${JSON.stringify(designSystem, null, 2)} Please generate code that follows this design system: - Use the specified color palette - Apply the recommended typography - Implement the suggested effects - Avoid the anti-patterns listed - Follow the pre-delivery checklist Generate production-ready code with proper styling and best practices. `; ``` --- ## Easypanel Integration ### Overview Easypanel is used for deploying user applications to production. The integration uses Easypanel's tRPC API for authentication, database creation, application deployment, and service management. **Base URL**: `https://panel.moreminimore.com/api` **API Documentation**: https://panel.moreminimore.com/api#/ --- ### Authentication Easypanel uses email/password authentication to obtain a bearer token. #### Login Endpoint **URL**: `POST /api/trpc/auth.login` **Request Body**: ```json { "json": { "email": "${EASYPANEL_EMAIL}", "password": "${EASYPANEL_PASSWORD}" } } ``` **Response**: ```json [ { "result": { "data": { "json": { "token": "cmkko1glb000p07pfa226232b" } } } } ] ``` **Environment Variables**: - `EASYPANEL_EMAIL`: Easypanel admin email - `EASYPANEL_PASSWORD`: Easypanel admin password **Implementation**: ```typescript async login(): Promise { const response = await fetch(`${this.baseUrl}/api/trpc/auth.login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { email: process.env.EASYPANEL_EMAIL, password: process.env.EASYPANEL_PASSWORD } }) }); const data = await response.json(); return data[0].result.data.json.token; } ``` --- ### Service Naming Convention All services follow a consistent naming pattern for easy identification and troubleshooting. **Format**: `{username}-{project_id}` **Examples**: - App: `kunthawat-More` - Database: `kunthawat-More-db` **Duplicate Handling**: If service name already exists, append running number: - `kunthawat-More-2` - `kunthawat-More-3` **Implementation**: ```typescript async generateServiceName( username: string, projectId: string, type: 'app' | 'database' ): Promise { const baseName = `${username}-${projectId}`; const serviceName = type === 'database' ? `${baseName}-db` : baseName; const exists = await this.serviceExists(serviceName); if (!exists) { return serviceName; } let counter = 2; let uniqueName = `${serviceName}-${counter}`; while (await this.serviceExists(uniqueName)) { counter++; uniqueName = `${serviceName}-${counter}`; } return uniqueName; } ``` --- ### Database Creation Create a MariaDB database for the application. #### Create Database Endpoint **URL**: `POST /api/trpc/services.mariadb.createService` **Headers**: ``` Authorization: Bearer {token} ``` **Request Body**: ```json { "json": { "projectName": "database", "serviceName": "{username}-{project_id}-db", "databaseName": "{username}-{project_id}-db", "user": "wp_user", "image": "mariadb:11", "exposedPort": 0, "password": "{generated_password}", "rootPassword": "{generated_root_password}", "resources": { "memoryReservation": 0, "memoryLimit": 0, "cpuReservation": 0, "cpuLimit": 0 }, "backup": { "enabled": false, "schedule": "", "destinationId": "", "prefix": "", "databaseName": "" }, "phpMyAdmin": { "enabled": false, "token": "" }, "dbGate": { "enabled": false, "token": "" } } } ``` **Response**: ```json [ { "result": { "data": { "json": { "type": "mariadb", "projectName": "database", "name": "db-justtest", "databaseName": "db-justtest", "user": "wp_user", "image": "mariadb:11", "enabled": true, "exposedPort": 0, "password": "5edwdr930g4jtpawzzpy", "rootPassword": "caab9krg3udej4cg2m46", "env": null, "command": null }, "meta": { "values": { "env": ["undefined"], "command": ["undefined"] } } } } } ] ``` **Database Connection String**: ``` mariadb://{username}:{password}@{projectName}_{serviceName}:3306/{databaseName} ``` **Example**: ``` mariadb://wp_user:5edwdr930g4jtpawzzpy@database_kunthawat-More-db:3306/kunthawat-More-db ``` **Implementation**: ```typescript async createDatabase( username: string, projectId: string ): Promise { const serviceName = await this.generateServiceName(username, projectId, 'database'); const password = this.generatePassword(); const rootPassword = this.generatePassword(); const response = await fetch(`${this.baseUrl}/api/trpc/services.mariadb.createService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName, databaseName: serviceName, user: 'wp_user', image: 'mariadb:11', exposedPort: 0, password, rootPassword, resources: { memoryReservation: 0, memoryLimit: 0, cpuReservation: 0, cpuLimit: 0 }, backup: { enabled: false, schedule: '', destinationId: '', prefix: '', databaseName: '' }, phpMyAdmin: { enabled: false, token: '' }, dbGate: { enabled: false, token: '' } } }) }); const data = await response.json(); const result = data[0].result.data.json; const connectionString = `mariadb://${result.user}:${result.password}@${result.projectName}_${result.name}:3306/${result.databaseName}`; return { serviceName: result.name, databaseName: result.databaseName, user: result.user, password: result.password, connectionString }; } ``` --- ### Application Deployment Deploy an application from Gitea repository. #### Create Application Endpoint **URL**: `POST /api/trpc/services.app.createService` **Headers**: ``` Authorization: Bearer {token} ``` **Request Body**: ```json { "json": { "projectName": "database", "serviceName": "{username}-{project_id}", "source": { "type": "git", "repo": "https://gitea.moreminimore.com/{username}/{username}-{project_id}.git", "ref": "main", "path": "/" }, "build": { "type": "dockerfile", "file": "Dockerfile" }, "domains": [ { "host": "{username}-{serviceName}.moreminimore.com", "https": true, "path": "/", "wildcard": false, "destinationType": "service", "serviceDestination": { "protocol": "http", "port": 80, "path": "/", "projectName": "database", "serviceName": "{username}-{project_id}" } } ], "mounts": [ { "type": "volume", "name": "wp_data", "mountPath": "/var/www/html" } ], "env": "DATABASE_URL={database_connection_string}\nNODE_ENV=production" } } ``` **Response**: ```json [ { "result": { "data": { "json": { "projectName": "database", "name": "justtest2", "type": "app", "enabled": true, "token": "9dd5b81fef7fe4bf9f3b2e2e7bd83b19a6fe311d4a88b101", "primaryDomainId": "cmkkq46xb000t07pf81mp63qv", "source": { "type": "git", "repo": "https://github.com/kunthawat/TradingAgents-crypto.git", "ref": "feature/gold-trading-support", "path": "/" }, "build": { "type": "dockerfile", "file": "Dockerfile" }, "env": "", "deploy": { "replicas": 1, "command": null, "zeroDowntime": true }, "mounts": [ { "type": "volume", "name": "wp_data", "mountPath": "/var/www/html" } ], "ports": [] } } } } ] ``` **Implementation**: ```typescript async createApplication( username: string, projectId: string, giteaRepoUrl: string, databaseConnectionString: string, customDomain?: string ): Promise { const serviceName = await this.generateServiceName(username, projectId, 'app'); const defaultDomain = `${username}-${serviceName}.moreminimore.com`; const domain = customDomain || defaultDomain; const response = await fetch(`${this.baseUrl}/api/trpc/services.app.createService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName, source: { type: 'git', repo: giteaRepoUrl, ref: 'main', path: '/' }, build: { type: 'dockerfile', file: 'Dockerfile' }, domains: [ { host: domain, https: true, path: '/', wildcard: false, destinationType: 'service', serviceDestination: { protocol: 'http', port: 80, path: '/', projectName: 'database', serviceName } } ], mounts: [ { type: 'volume', name: 'wp_data', mountPath: '/var/www/html' } ], env: `DATABASE_URL=${databaseConnectionString}\nNODE_ENV=production` } }) }); const data = await response.json(); const result = data[0].result.data.json; return { serviceName: result.name, domain, primaryDomainId: result.primaryDomainId, deploymentUrl: `https://${domain}` }; } ``` --- ### Update/Redeploy Application Trigger a redeploy of an existing application. #### Update Deploy Endpoint **URL**: `POST /api/trpc/services.app.updateDeploy` **Headers**: ``` Authorization: Bearer {token} ``` **Request Body**: ```json { "json": { "projectName": "database", "serviceName": "{username}-{project_id}", "deploy": {} } } ``` **Implementation**: ```typescript async updateDeploy( username: string, projectId: string ): Promise { const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/services.app.updateDeploy`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName, deploy: {} } }) }); } ``` --- ### Deployment Status Check the status and details of a deployed application. #### Inspect Service Endpoint **URL**: `GET /api/trpc/services.app.inspectService` **Query Parameters**: - `projectName`: "database" - `serviceName`: "{username}-{project_id}" **Headers**: ``` Authorization: Bearer {token} ``` **Implementation**: ```typescript async inspectService( username: string, projectId: string ): Promise { const serviceName = await this.getServiceName(username, projectId); const response = await fetch( `${this.baseUrl}/api/trpc/services.app.inspectService?projectName=database&serviceName=${serviceName}`, { method: 'GET', headers: { 'Authorization': `Bearer ${this.token}` } } ); const data = await response.json(); return data[0].result.data.json; } ``` --- ### List Services List all projects and services. #### List Projects and Services Endpoint **URL**: `GET /api/trpc/projects.listProjectsAndServices` **Headers**: ``` Authorization: Bearer {token} ``` **Implementation**: ```typescript async listProjectsAndServices(): Promise { const response = await fetch( `${this.baseUrl}/api/trpc/projects.listProjectsAndServices`, { method: 'GET', headers: { 'Authorization': `Bearer ${this.token}` } } ); const data = await response.json(); return data[0].result.data.json; } ``` --- ### Delete Service Delete an application service. #### Destroy Service Endpoint **URL**: `POST /api/trpc/services.app.destroyService` **Headers**: ``` Authorization: Bearer {token} ``` **Request Body**: ```json { "json": { "projectName": "database", "serviceName": "{username}-{project_id}" } } ``` **Implementation**: ```typescript async destroyService( username: string, projectId: string ): Promise { const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/services.app.destroyService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName } }) }); } ``` --- ### Stop/Start Service Control the running state of a service. #### Stop Service Endpoint **URL**: `POST /api/trpc/services.app.stopService` **Headers**: ``` Authorization: Bearer {token} ``` **Request Body**: ```json { "json": { "projectName": "database", "serviceName": "{username}-{project_id}" } } ``` #### Start Service Endpoint **URL**: `POST /api/trpc/services.app.startService` **Headers**: ``` Authorization: Bearer {token} ``` **Request Body**: ```json { "json": { "projectName": "database", "serviceName": "{username}-{project_id}" } } ``` **Implementation**: ```typescript async stopService( username: string, projectId: string ): Promise { const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/services.app.stopService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName } }) }); } async startService( username: string, projectId: string ): Promise { const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/services.app.startService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName } }) }); } ``` --- ### Domain Management Update the domain of a deployed application. #### Update Domain Endpoint **URL**: `POST /api/trpc/domains.updateDomain` **Headers**: ``` Authorization: Bearer {token} ``` **Request Body**: ```json { "json": { "id": "{domain_id}", "host": "{custom_domain}", "https": true, "path": "/", "middlewares": [], "certificateResolver": "letsencrypt", "wildcard": false, "destinationType": "service", "serviceDestination": { "protocol": "http", "port": 80, "path": "/", "projectName": "database", "serviceName": "{username}-{project_id}" } } } ``` **Workflow**: 1. Get domain ID from `inspectService` endpoint 2. Call `updateDomain` with new domain **Implementation**: ```typescript async updateDomain( username: string, projectId: string, newDomain: string ): Promise { const serviceInfo = await this.inspectService(username, projectId); const domainId = serviceInfo.primaryDomainId; const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/domains.updateDomain`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { id: domainId, host: newDomain, https: true, path: '/', middlewares: [], certificateResolver: 'letsencrypt', wildcard: false, destinationType: 'service', serviceDestination: { protocol: 'http', port: 80, path: '/', projectName: 'database', serviceName } } }) }); } ``` --- ### Dockerfile Generation Applications must have a Dockerfile for deployment. The system will auto-generate a Dockerfile based on the project type. #### Next.js Dockerfile Template ```dockerfile FROM node:20-alpine AS base # Install dependencies only when needed FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json* ./ COPY npmrc* ./ RUN npm ci # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build # Production image, copy all the files and run next FROM base AS runner WORKDIR /app ENV NODE_ENV production RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT 3000 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"] ``` #### React Dockerfile Template ```dockerfile FROM node:20-alpine AS builder WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --from=builder /app/build /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` **Implementation**: ```typescript async generateDockerfile(projectType: 'nextjs' | 'react' | 'node'): Promise { const templates = { nextjs: this.getNextjsDockerfile(), react: this.getReactDockerfile(), node: this.getNodeDockerfile() }; return templates[projectType]; } ``` --- ### Complete Easypanel Service ```typescript // src/lib/services/easypanel.service.ts export class EasypanelService { private token: string | null = null; private baseUrl: string = 'https://panel.moreminimore.com/api'; constructor() { this.token = null; } private async ensureAuthenticated(): Promise { if (!this.token) { this.token = await this.login(); } } async login(): Promise { const response = await fetch(`${this.baseUrl}/api/trpc/auth.login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { email: process.env.EASYPANEL_EMAIL, password: process.env.EASYPANEL_PASSWORD } }) }); const data = await response.json(); this.token = data[0].result.data.json.token; return this.token; } async generateServiceName( username: string, projectId: string, type: 'app' | 'database' ): Promise { const baseName = `${username}-${projectId}`; const serviceName = type === 'database' ? `${baseName}-db` : baseName; const exists = await this.serviceExists(serviceName); if (!exists) { return serviceName; } let counter = 2; let uniqueName = `${serviceName}-${counter}`; while (await this.serviceExists(uniqueName)) { counter++; uniqueName = `${serviceName}-${counter}`; } return uniqueName; } async createDatabase( username: string, projectId: string ): Promise { await this.ensureAuthenticated(); const serviceName = await this.generateServiceName(username, projectId, 'database'); const password = this.generatePassword(); const rootPassword = this.generatePassword(); const response = await fetch(`${this.baseUrl}/api/trpc/services.mariadb.createService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName, databaseName: serviceName, user: 'wp_user', image: 'mariadb:11', exposedPort: 0, password, rootPassword, resources: { memoryReservation: 0, memoryLimit: 0, cpuReservation: 0, cpuLimit: 0 }, backup: { enabled: false, schedule: '', destinationId: '', prefix: '', databaseName: '' }, phpMyAdmin: { enabled: false, token: '' }, dbGate: { enabled: false, token: '' } } }) }); const data = await response.json(); const result = data[0].result.data.json; const connectionString = `mariadb://${result.user}:${result.password}@${result.projectName}_${result.name}:3306/${result.databaseName}`; return { serviceName: result.name, databaseName: result.databaseName, user: result.user, password: result.password, connectionString }; } async createApplication( username: string, projectId: string, giteaRepoUrl: string, databaseConnectionString: string, customDomain?: string ): Promise { await this.ensureAuthenticated(); const serviceName = await this.generateServiceName(username, projectId, 'app'); const defaultDomain = `${username}-${serviceName}.moreminimore.com`; const domain = customDomain || defaultDomain; const response = await fetch(`${this.baseUrl}/api/trpc/services.app.createService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName, source: { type: 'git', repo: giteaRepoUrl, ref: 'main', path: '/' }, build: { type: 'dockerfile', file: 'Dockerfile' }, domains: [ { host: domain, https: true, path: '/', wildcard: false, destinationType: 'service', serviceDestination: { protocol: 'http', port: 80, path: '/', projectName: 'database', serviceName } } ], mounts: [ { type: 'volume', name: 'wp_data', mountPath: '/var/www/html' } ], env: `DATABASE_URL=${databaseConnectionString}\nNODE_ENV=production` } }) }); const data = await response.json(); const result = data[0].result.data.json; return { serviceName: result.name, domain, primaryDomainId: result.primaryDomainId, deploymentUrl: `https://${domain}` }; } async updateDeploy( username: string, projectId: string ): Promise { await this.ensureAuthenticated(); const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/services.app.updateDeploy`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName, deploy: {} } }) }); } async inspectService( username: string, projectId: string ): Promise { await this.ensureAuthenticated(); const serviceName = await this.getServiceName(username, projectId); const response = await fetch( `${this.baseUrl}/api/trpc/services.app.inspectService?projectName=database&serviceName=${serviceName}`, { method: 'GET', headers: { 'Authorization': `Bearer ${this.token}` } } ); const data = await response.json(); return data[0].result.data.json; } async updateDomain( username: string, projectId: string, newDomain: string ): Promise { await this.ensureAuthenticated(); const serviceInfo = await this.inspectService(username, projectId); const domainId = serviceInfo.primaryDomainId; const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/domains.updateDomain`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { id: domainId, host: newDomain, https: true, path: '/', middlewares: [], certificateResolver: 'letsencrypt', wildcard: false, destinationType: 'service', serviceDestination: { protocol: 'http', port: 80, path: '/', projectName: 'database', serviceName } } }) }); } async destroyService( username: string, projectId: string ): Promise { await this.ensureAuthenticated(); const serviceName = await this.getServiceName(username, projectId); await fetch(`${this.baseUrl}/api/trpc/services.app.destroyService`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ json: { projectName: 'database', serviceName } }) }); } private generatePassword(): string { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } private async serviceExists(serviceName: string): Promise { // Implementation to check if service exists // Use listProjectsAndServices endpoint return false; } private async getServiceName(username: string, projectId: string): Promise { // Implementation to retrieve stored service name // Should be stored in database when created return `${username}-${projectId}`; } } // Type definitions interface DatabaseInfo { serviceName: string; databaseName: string; user: string; password: string; connectionString: string; } interface ApplicationInfo { serviceName: string; domain: string; primaryDomainId: string; deploymentUrl: string; } interface ServiceInfo { projectName: string; name: string; type: string; enabled: boolean; primaryDomainId: string; source: { type: string; repo: string; ref: string; path: string; }; build: { type: string; file: string; }; deploy: { replicas: number; command: string | null; zeroDowntime: boolean; }; mounts: Array<{ type: string; name: string; mountPath: string; }>; } ``` --- ### Error Handling When deployment fails, display user-friendly error message: ``` "Deployment failed. Please contact us." ``` Include the actual API response for debugging purposes (logged internally). **Implementation**: ```typescript try { await this.easypanelService.createApplication(...); } catch (error) { console.error('Easypanel API error:', error); throw new Error('Deployment failed. Please contact us.'); } ``` --- ### Environment Variables Add to `.env.local`: ```env # Easypanel EASYPANEL_EMAIL=kunthawat@moreminimore.com EASYPANEL_PASSWORD=Coolm@n1234mo EASYPANEL_API_URL=https://panel.moreminimore.com/api ``` --- ### API Reference For complete API documentation, visit: https://panel.moreminimore.com/api#/ Key endpoints: - Authentication: `/api/trpc/auth.login` - Database: `/api/trpc/services.mariadb.createService` - Application: `/api/trpc/services.app.createService` - Update Deploy: `/api/trpc/services.app.updateDeploy` - Inspect Service: `/api/trpc/services.app.inspectService` - List Services: `/api/trpc/projects.listProjectsAndServices` - Delete Service: `/api/trpc/services.app.destroyService` - Stop Service: `/api/trpc/services.app.stopService` - Start Service: `/api/trpc/services.app.startService` - Update Domain: `/api/trpc/domains.updateDomain` --- ## Billing & Pricing ### Subscription Tiers | Feature | Free | Pro | Enterprise | | ----------------- | ------------ | --------- | ---------- | | Price | $0/month | $29/month | Custom | | Projects | 1 | 10 | Unlimited | | AI Tokens | 10,000/month | Unlimited | Unlimited | | Team Members | 1 | 5 | Unlimited | | Deployments | 1 | 10 | Unlimited | | Custom Domains | ❌ | ✅ | ✅ | | Priority Support | ❌ | ✅ | ✅ | | SLA | ❌ | ❌ | 99.9% | | Advanced Features | ❌ | ✅ | ✅ | ### Stripe Integration ```typescript // src/lib/services/billing.service.ts import Stripe from "stripe"; export class BillingService { private stripe: Stripe; constructor(secretKey: string) { this.stripe = new Stripe(secretKey); } async createCustomer(organizationId: string, email: string): Promise { const customer = await this.stripe.customers.create({ email, metadata: { organizationId }, }); // Update organization with Stripe customer ID await db .update(organizations) .set({ stripeCustomerId: customer.id }) .where(eq(organizations.id, organizationId)); return customer.id; } async createSubscription( customerId: string, priceId: string, organizationId: string, ): Promise { const subscription = await this.stripe.subscriptions.create({ customer: customerId, items: [{ price: priceId }], payment_behavior: "default_incomplete", payment_settings: { save_default_payment_method: "on_subscription" }, expand: ["latest_invoice.payment_intent"], }); // Update organization subscription await db .update(organizations) .set({ subscriptionTier: this.getTierFromPriceId(priceId), subscriptionStatus: subscription.status, trialEndsAt: subscription.trial_end ? new Date(subscription.trial_end * 1000) : null, }) .where(eq(organizations.id, organizationId)); // Log subscription event await this.logSubscriptionEvent(organizationId, "created", subscription.id); return subscription; } async handleWebhook(event: Stripe.Event): Promise { switch (event.type) { case "customer.subscription.created": case "customer.subscription.updated": await this.handleSubscriptionUpdated( event.data.object as Stripe.Subscription, ); break; case "customer.subscription.deleted": await this.handleSubscriptionDeleted( event.data.object as Stripe.Subscription, ); break; case "invoice.payment_succeeded": await this.handleInvoicePaymentSucceeded( event.data.object as Stripe.Invoice, ); break; case "invoice.payment_failed": await this.handleInvoicePaymentFailed( event.data.object as Stripe.Invoice, ); break; default: console.log(`Unhandled event type: ${event.type}`); } } private async handleSubscriptionUpdated( subscription: Stripe.Subscription, ): Promise { const organizationId = subscription.metadata.organizationId; await db .update(organizations) .set({ subscriptionStatus: subscription.status, subscriptionTier: this.getTierFromPriceId( subscription.items.data[0].price.id, ), }) .where(eq(organizations.id, organizationId)); await this.logSubscriptionEvent(organizationId, "updated", subscription.id); } private async handleSubscriptionDeleted( subscription: Stripe.Subscription, ): Promise { const organizationId = subscription.metadata.organizationId; await db .update(organizations) .set({ subscriptionStatus: "canceled", subscriptionTier: "free", }) .where(eq(organizations.id, organizationId)); await this.logSubscriptionEvent( organizationId, "canceled", subscription.id, ); } private async handleInvoicePaymentSucceeded( invoice: Stripe.Invoice, ): Promise { const organizationId = invoice.customer_metadata?.organizationId; // Create invoice record await db.insert(invoices).values({ organizationId, stripeInvoiceId: invoice.id, amount: invoice.amount_paid / 100, currency: invoice.currency.toUpperCase(), status: "paid", paidAt: new Date(invoice.status_transitions.paid_at! * 1000), }); // Update organization status if it was past_due if (invoice.subscription) { await db .update(organizations) .set({ subscriptionStatus: "active" }) .where( eq( organizations.stripeSubscriptionId, invoice.subscription as string, ), ); } } private async handleInvoicePaymentFailed( invoice: Stripe.Invoice, ): Promise { const organizationId = invoice.customer_metadata?.organizationId; // Create invoice record await db.insert(invoices).values({ organizationId, stripeInvoiceId: invoice.id, amount: invoice.amount_due / 100, currency: invoice.currency.toUpperCase(), status: "open", dueDate: new Date(invoice.due_date! * 1000), }); // Update organization status if (invoice.subscription) { await db .update(organizations) .set({ subscriptionStatus: "past_due" }) .where( eq( organizations.stripeSubscriptionId, invoice.subscription as string, ), ); } } private getTierFromPriceId(priceId: string): SubscriptionTier { // Map price IDs to tiers if (priceId.includes("pro")) return "pro"; if (priceId.includes("enterprise")) return "enterprise"; return "free"; } private async logSubscriptionEvent( organizationId: string, eventType: string, stripeEventId: string, ): Promise { await db.insert(subscriptionEvents).values({ organizationId, eventType, stripeEventId, createdAt: new Date(), }); } } ``` --- ## Code Storage Strategy ### Decision: PostgreSQL for Code Storage **Rationale:** - **Simplicity**: Single database for all data - **Consistency**: ACID transactions for code + metadata - **Backup**: Easy to backup entire system - **Scalability**: PostgreSQL can handle large text fields - **Performance**: Good enough for typical project sizes - **Gitea for Backup**: Gitea provides version control and backup ### Implementation ```typescript // Store project files in database CREATE TABLE project_files ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, path TEXT NOT NULL, -- File path (e.g., "src/app/page.tsx") content TEXT NOT NULL, language VARCHAR(50), -- File type (typescript, javascript, etc.) size INTEGER, -- File size in bytes created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(project_id, path) ); // Index for performance CREATE INDEX idx_project_files_project ON project_files(project_id); CREATE INDEX idx_project_files_path ON project_files(project_id, path); ``` ### File Operations ```typescript // src/lib/services/file.service.ts export class FileService { async getFile(projectId: string, path: string): Promise { return await db.query.projectFiles.findFirst({ where: and( eq(projectFiles.projectId, projectId), eq(projectFiles.path, path), ), }); } async saveFile( projectId: string, path: string, content: string, ): Promise { await db .insert(projectFiles) .values({ projectId, path, content, language: this.getLanguageFromPath(path), size: Buffer.byteLength(content, "utf8"), }) .onConflictDoUpdate({ target: [projectFiles.projectId, projectFiles.path], set: { content, updatedAt: new Date(), }, }); } async deleteFile(projectId: string, path: string): Promise { await db .delete(projectFiles) .where( and(eq(projectFiles.projectId, projectId), eq(projectFiles.path, path)), ); } async listFiles( projectId: string, directory?: string, ): Promise { const conditions = [eq(projectFiles.projectId, projectId)]; if (directory) { conditions.push(like(projectFiles.path, `${directory}%`)); } return await db.query.projectFiles.findMany({ where: and(...conditions), orderBy: [asc(projectFiles.path)], }); } async exportProject(projectId: string): Promise> { const files = await this.listFiles(projectId); const exportData: Record = {}; for (const file of files) { exportData[file.path] = file.content; } return exportData; } private getLanguageFromPath(path: string): string { const ext = path.split(".").pop(); const languageMap: Record = { ts: "typescript", tsx: "typescript", js: "javascript", jsx: "javascript", css: "css", html: "html", json: "json", md: "markdown", }; return languageMap[ext || ""] || "text"; } } ``` ### Gitea Integration for Backup ```typescript // src/lib/services/gitea.service.ts export class GiteaService { private apiUrl: string; private token: string; constructor(apiUrl: string, token: string) { this.apiUrl = apiUrl; this.token = token; } async createRepository( projectId: string, projectName: string, ): Promise { const response = await fetch(`${this.apiUrl}/api/v1/user/repos`, { method: "POST", headers: { Authorization: `token ${this.token}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: projectName, private: true, auto_init: true, }), }); if (!response.ok) { throw new Error("Failed to create Gitea repository"); } const repo = await response.json(); // Update project with Gitea info await db .update(projects) .set({ giteaRepoId: repo.id, giteaRepoUrl: repo.clone_url, }) .where(eq(projects.id, projectId)); return repo; } async commitProject(projectId: string): Promise { const project = await this.getProject(projectId); if (!project.giteaRepoUrl) { throw new Error("Project not linked to Gitea"); } // Export all files const files = await this.fileService.exportProject(projectId); // Create commit with all files const commit = await this.createCommit( project.giteaRepoId, "main", `Update project - ${new Date().toISOString()}`, files, ); return commit; } private async createCommit( repoId: number, branch: string, message: string, files: Record, ): Promise { // Implementation for creating commit in Gitea // This would use Gitea's API to create/update files // For simplicity, this is a placeholder const response = await fetch( `${this.apiUrl}/api/v1/repos/${repoId}/contents`, { method: "POST", headers: { Authorization: `token ${this.token}`, "Content-Type": "application/json", }, body: JSON.stringify({ branch, message, files, }), }, ); return await response.json(); } } ``` --- ## Migration Strategy ### Local to Cloud Migration Options When users deploy to Easypanel, they will be asked: ``` ┌─────────────────────────────────────────────────────────────┐ │ Deploy to Easypanel │ ├─────────────────────────────────────────────────────────────┤ │ │ │ How would you like to handle your local data? │ │ │ │ ○ Start Fresh │ │ Create a new project without importing local data │ │ │ │ ● Import Local Data │ │ Import your chats, messages, and project files │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Import Options: │ │ │ │ │ │ │ │ ☑ Import chat history │ │ │ │ ☑ Import project files │ │ │ │ ☐ Import AI model settings │ │ │ │ ☐ Import environment variables │ │ │ │ │ │ │ │ [Cancel] [Import & Deploy] │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### Migration Implementation ```typescript // src/lib/services/migration.service.ts export class MigrationService { async migrateFromLocal( localDbPath: string, projectId: string, options: MigrationOptions, ): Promise { const result: MigrationResult = { chatsImported: 0, messagesImported: 0, filesImported: 0, errors: [], }; try { // Connect to local SQLite database const localDb = await this.connectToLocalDb(localDbPath); // Import chats if selected if (options.importChats) { const localChats = await localDb.query.chats.findMany(); for (const chat of localChats) { await db.insert(chats).values({ projectId, title: chat.title, createdBy: options.userId, createdAt: chat.createdAt, updatedAt: chat.updatedAt, }); result.chatsImported++; } } // Import messages if selected if (options.importMessages) { const localMessages = await localDb.query.messages.findMany(); for (const message of localMessages) { await db.insert(messages).values({ chatId: this.mapChatId(message.chatId, projectId), role: message.role, content: message.content, metadata: { approvalState: message.approvalState, sourceCommitHash: message.sourceCommitHash, commitHash: message.commitHash, requestId: message.requestId, maxTokensUsed: message.maxTokensUsed, }, createdAt: message.createdAt, }); result.messagesImported++; } } // Import project files if selected if (options.importFiles) { const projectPath = options.localProjectPath; const files = await this.scanProjectFiles(projectPath); for (const file of files) { const content = await fs.readFile(file.path, "utf-8"); const relativePath = path.relative(projectPath, file.path); await db.insert(projectFiles).values({ projectId, path: relativePath, content, language: this.getLanguageFromPath(relativePath), size: file.size, }); result.filesImported++; } } return result; } catch (error) { result.errors.push(error.message); throw error; } } private async connectToLocalDb(dbPath: string): Promise { // Connect to local SQLite database const connection = await connect(dbPath); return drizzle(connection, { schema: localSchema }); } private async scanProjectFiles(projectPath: string): Promise { const files: File[] = []; const entries = await fs.readdir(projectPath, { recursive: true }); for (const entry of entries) { const fullPath = path.join(projectPath, entry); const stats = await fs.stat(fullPath); if (stats.isFile()) { files.push({ path: fullPath, size: stats.size, }); } } return files; } } ``` --- ## Security Considerations ### Authentication Security 1. **Password Hashing**: Use bcrypt with cost factor 12 2. **JWT Security**: - Use strong secret keys (environment variables) - Short access token expiration (15 minutes) - Refresh token rotation - Token revocation on logout 3. **Rate Limiting**: - Login attempts: 5 per 15 minutes - API requests: 100 per minute per user - Password reset: 3 per hour ### Data Security 1. **Encryption**: - Encrypt sensitive data at rest (API keys, secrets) - Use TLS 1.3 for all connections - Encrypt database backups 2. **Input Validation**: - Validate all user inputs - Sanitize file uploads - Prevent SQL injection (use parameterized queries) 3. **Authorization**: - Role-based access control (RBAC) - Resource-level permissions - Audit logging for sensitive actions ### API Security 1. **CORS**: Configure allowed origins 2. **CSRF Protection**: Implement CSRF tokens 3. **XSS Prevention**: Sanitize user-generated content 4. **Security Headers**: - Content-Security-Policy - X-Frame-Options - X-Content-Type-Options - Strict-Transport-Security ### Easypanel API Security 1. **API Key Management**: - Store encrypted in database - Rotate regularly - Use separate keys for dev/prod 2. **Webhook Verification**: - Verify webhook signatures - Validate event types - Handle idempotency ### Gitea Security 1. **Access Tokens**: - Use personal access tokens - Limit token permissions - Rotate tokens regularly 2. **Repository Security**: - Private repositories by default - Branch protection rules - Require pull requests --- ## Performance Requirements ### Response Times - **API Endpoints**: < 200ms (p95) - **Page Load**: < 2s (p95) - **Code Generation**: < 30s for typical requests - **Deployment**: < 5 minutes for initial deploy ### Scalability - **Concurrent Users**: 1000+ concurrent users - **Projects**: 10,000+ projects - **Database**: Handle 1M+ records - **File Storage**: 10GB+ per project ### Caching Strategy 1. **Redis Cache**: - Session data - Frequently accessed project data - API responses - Rate limiting counters 2. **Database Optimization**: - Proper indexing - Query optimization - Connection pooling - Read replicas (if needed) 3. **CDN**: - Static assets - Generated code previews - Design system assets --- ## Development Phases ### Phase 1: Foundation (Weeks 1-4) **Goal**: Set up core infrastructure **Tasks**: 1. Initialize Next.js project with TypeScript 2. Set up PostgreSQL database 3. Configure Drizzle ORM 4. Implement authentication system 5. Create user management APIs 6. Set up Redis for caching 7. Configure CI/CD pipeline **Deliverables**: - Working Next.js application - Database schema implemented - User registration/login working - Basic admin dashboard ### Phase 2: Core Features (Weeks 5-8) **Goal**: Implement project management and AI features **Tasks**: 1. Create project management system 2. Implement chat interface 3. Integrate AI providers 4. Set up code editor 5. Implement file management 6. Create preview system 7. Add version control **Deliverables**: - Users can create and manage projects - AI-powered code generation working - Code editor with syntax highlighting - Live preview functionality ### Phase 3: UI/UX Pro Max Integration (Weeks 9-10) **Goal**: Integrate design system generation **Tasks**: 1. Install UI/UX Pro Max 2. Create design system service 3. Implement design system generation 4. Integrate with AI prompts 5. Create design system UI 6. Add design system persistence **Deliverables**: - Automatic design system generation - Design system applied to generated code - Design system management UI ### Phase 4: Easypanel Integration (Weeks 11-13) **Goal**: Implement deployment functionality **Tasks**: 1. Create Easypanel API client 2. Implement database creation 3. Implement application deployment 4. Add deployment management 5. Create deployment UI 6. Implement update deployment 7. Add deployment logs **Deliverables**: - One-click deployment to Easypanel - Automatic database creation - Deployment management UI - Update deployment functionality ### Phase 5: Gitea Integration (Weeks 14-15) **Goal**: Implement code backup and version control **Tasks**: 1. Create Gitea API client 2. Implement repository creation 3. Add commit functionality 4. Implement code backup 5. Add version history 6. Create version comparison UI **Deliverables**: - Automatic code backup to Gitea - Version history tracking - Version comparison UI ### Phase 6: Billing & Subscription (Weeks 16-18) **Goal**: Implement billing system **Tasks**: 1. Integrate Stripe 2. Create subscription management 3. Implement pricing tiers 4. Add billing UI 5. Set up webhooks 6. Create invoice management 7. Add usage analytics **Deliverables**: - Stripe integration working - Subscription management - Billing UI - Invoice system ### Phase 7: Migration & Cleanup (Weeks 19-20) **Goal**: Remove external services and clean up **Tasks**: 1. Remove Supabase integration 2. Remove Neon integration 3. Remove Vercel integration 4. Remove Electron dependencies 5. Clean up unused code 6. Update documentation 7. Remove "dyad" branding **Deliverables**: - All external services removed - Clean codebase - Updated documentation ### Phase 8: Testing & Optimization (Weeks 21-22) **Goal**: Ensure quality and performance **Tasks**: 1. Write unit tests 2. Write integration tests 3. Write E2E tests 4. Performance optimization 5. Security audit 6. Load testing 7. Bug fixes **Deliverables**: - Comprehensive test suite - Performance benchmarks - Security audit report - Bug-free application ### Phase 9: Deployment & Launch (Weeks 23-24) **Goal**: Deploy to production **Tasks**: 1. Set up production VPS 2. Configure Nginx 3. Set up SSL certificates 4. Deploy application 5. Configure monitoring 6. Set up backups 7. Create launch checklist 8. Launch application **Deliverables**: - Production deployment - Monitoring and alerting - Backup system - Live application --- ## Appendix ### Environment Variables ```env # Database DATABASE_URL=postgresql://user:password@localhost:5432/moreminimore REDIS_URL=redis://localhost:6379 # Authentication JWT_SECRET=your-super-secret-jwt-key JWT_REFRESH_SECRET=your-super-secret-refresh-key # AI Providers OPENAI_API_KEY=sk-... ANTHROPIC_API_KEY=sk-ant-... GOOGLE_API_KEY=... # Easypanel EASYPANEL_API_KEY=your-easypanel-api-key EASYPANEL_API_URL=https://panel.moreminimore.com/api # Gitea GITEA_API_URL=https://gitea.moreminimore.com/api/v1 GITEA_TOKEN=your-gitea-token # Stripe STRIPE_SECRET_KEY=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_PRICE_ID_FREE=price_... STRIPE_PRICE_ID_PRO=price_... STRIPE_PRICE_ID_ENTERPRISE=price_... # Email RESEND_API_KEY=re_... # Application NEXT_PUBLIC_APP_URL=https://app.moreminimore.com NEXT_PUBLIC_API_URL=https://api.moreminimore.com ``` ### API Endpoints #### Authentication - `POST /api/auth/register` - Register new user - `POST /api/auth/login` - Login user - `POST /api/auth/logout` - Logout user - `POST /api/auth/refresh` - Refresh access token - `POST /api/auth/forgot-password` - Request password reset - `POST /api/auth/reset-password` - Reset password - `POST /api/auth/verify-email` - Verify email #### Users - `GET /api/users/me` - Get current user - `PATCH /api/users/me` - Update current user - `GET /api/users/:id` - Get user by ID (admin only) - `GET /api/users` - List all users (admin only) #### Organizations - `POST /api/organizations` - Create organization - `GET /api/organizations` - List user's organizations - `GET /api/organizations/:id` - Get organization details - `PATCH /api/organizations/:id` - Update organization - `DELETE /api/organizations/:id` - Delete organization #### Organization Members - `POST /api/organizations/:id/members` - Invite member - `GET /api/organizations/:id/members` - List members - `PATCH /api/organizations/:id/members/:memberId` - Update member - `DELETE /api/organizations/:id/members/:memberId` - Remove member #### Projects - `POST /api/projects` - Create project - `GET /api/projects` - List user's projects - `GET /api/projects/:id` - Get project details - `PATCH /api/projects/:id` - Update project - `DELETE /api/projects/:id` - Delete project #### Project Files - `GET /api/projects/:id/files` - List project files - `GET /api/projects/:id/files/*` - Get file content - `PUT /api/projects/:id/files/*` - Save file - `DELETE /api/projects/:id/files/*` - Delete file #### Chats - `POST /api/projects/:id/chats` - Create chat - `GET /api/projects/:id/chats` - List chats - `GET /api/chats/:id` - Get chat details - `DELETE /api/chats/:id` - Delete chat #### Messages - `POST /api/chats/:id/messages` - Send message - `GET /api/chats/:id/messages` - List messages #### Deployment - `POST /api/projects/:id/deploy` - Deploy project - `POST /api/projects/:id/deploy/update` - Update deployment - `GET /api/projects/:id/deployments` - List deployments - `GET /api/deployments/:id/logs` - Get deployment logs #### Design Systems - `POST /api/projects/:id/design-system` - Generate design system - `GET /api/projects/:id/design-system` - Get design system - `PATCH /api/projects/:id/design-system` - Update design system #### Billing - `POST /api/billing/checkout` - Create checkout session - `GET /api/billing/subscription` - Get subscription details - `POST /api/billing/portal` - Create customer portal session - `GET /api/billing/invoices` - List invoices #### Admin - `GET /api/admin/users` - List all users - `GET /api/admin/organizations` - List all organizations - `GET /api/admin/analytics` - Get system analytics - `PATCH /api/admin/settings` - Update system settings --- ## Conclusion This specification provides a comprehensive roadmap for transforming MoreMinimore from a local Electron app into a full-featured SAAS platform. The architecture is designed to be scalable, secure, and maintainable while providing an excellent user experience. Key highlights: - **Modern Tech Stack**: Next.js 15, PostgreSQL, Redis, Drizzle ORM - **Robust Authentication**: Custom JWT with role-based access control - **AI-Powered**: Integration with multiple AI providers and UI/UX Pro Max - **Easy Deployment**: One-click deployment to Easypanel - **Code Backup**: Automatic version control with Gitea - **Billing Integration**: Stripe for subscription management - **Scalable Architecture**: Designed for growth and performance The development is organized into 9 phases over 24 weeks, with clear deliverables and milestones. This phased approach ensures steady progress and allows for iterative improvements based on feedback. --- **Document Version**: 1.0 **Last Updated**: January 19, 2026 **Author**: MoreMinimore Development Team