96 KiB
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
- Architecture Overview
- Technology Stack
- Database Schema
- Authentication & Authorization
- User Roles & Permissions
- Core Features
- Deployment Flow
- UI/UX Pro Max Integration
- Easypanel Integration
- Billing & Pricing
- Code Storage Strategy
- Migration Strategy
- Security Considerations
- Performance Requirements
- 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
- Monolithic Next.js App: Simplifies deployment and development
- PostgreSQL as Primary DB: Robust, scalable, supports complex queries
- Redis for Caching: Session management, rate limiting, temporary data
- Gitea for Code Backup: Version control, collaboration, backup
- 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
-- 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
// 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
// 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
# 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
// 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<DesignSystem> {
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<void> {
// 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<void> {
// Implementation for file-based persistence
// Creates design-system/MASTER.md and design-system/pages/{page}.md
}
async getDesignSystem(
projectId: string,
pageName?: string,
): Promise<DesignSystem | null> {
// 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:
- Read the design system from the database
- Apply the design system guidelines
- Generate code that matches the design specifications
- Validate against anti-patterns
// 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": {
"email": "${EASYPANEL_EMAIL}",
"password": "${EASYPANEL_PASSWORD}"
}
}
Response:
[
{
"result": {
"data": {
"json": {
"token": "cmkko1glb000p07pfa226232b"
}
}
}
}
]
Environment Variables:
EASYPANEL_EMAIL: Easypanel admin emailEASYPANEL_PASSWORD: Easypanel admin password
Implementation:
async login(): Promise<string> {
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-2kunthawat-More-3
Implementation:
async generateServiceName(
username: string,
projectId: string,
type: 'app' | 'database'
): Promise<string> {
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": {
"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:
[
{
"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:
async createDatabase(
username: string,
projectId: string
): Promise<DatabaseInfo> {
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": {
"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:
[
{
"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:
async createApplication(
username: string,
projectId: string,
giteaRepoUrl: string,
databaseConnectionString: string,
customDomain?: string
): Promise<ApplicationInfo> {
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": {
"projectName": "database",
"serviceName": "{username}-{project_id}",
"deploy": {}
}
}
Implementation:
async updateDeploy(
username: string,
projectId: string
): Promise<void> {
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:
async inspectService(
username: string,
projectId: string
): Promise<ServiceInfo> {
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:
async listProjectsAndServices(): Promise<Project[]> {
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": {
"projectName": "database",
"serviceName": "{username}-{project_id}"
}
}
Implementation:
async destroyService(
username: string,
projectId: string
): Promise<void> {
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": {
"projectName": "database",
"serviceName": "{username}-{project_id}"
}
}
Start Service Endpoint
URL: POST /api/trpc/services.app.startService
Headers:
Authorization: Bearer {token}
Request Body:
{
"json": {
"projectName": "database",
"serviceName": "{username}-{project_id}"
}
}
Implementation:
async stopService(
username: string,
projectId: string
): Promise<void> {
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<void> {
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": {
"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:
- Get domain ID from
inspectServiceendpoint - Call
updateDomainwith new domain
Implementation:
async updateDomain(
username: string,
projectId: string,
newDomain: string
): Promise<void> {
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
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
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:
async generateDockerfile(projectType: 'nextjs' | 'react' | 'node'): Promise<string> {
const templates = {
nextjs: this.getNextjsDockerfile(),
react: this.getReactDockerfile(),
node: this.getNodeDockerfile()
};
return templates[projectType];
}
Complete Easypanel Service
// 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<void> {
if (!this.token) {
this.token = await this.login();
}
}
async login(): Promise<string> {
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<string> {
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<DatabaseInfo> {
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<ApplicationInfo> {
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<void> {
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<ServiceInfo> {
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<void> {
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<void> {
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<boolean> {
// Implementation to check if service exists
// Use listProjectsAndServices endpoint
return false;
}
private async getServiceName(username: string, projectId: string): Promise<string> {
// 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:
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:
# 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
// 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<string> {
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<Stripe.Subscription> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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
// 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
// src/lib/services/file.service.ts
export class FileService {
async getFile(projectId: string, path: string): Promise<ProjectFile | null> {
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<void> {
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<void> {
await db
.delete(projectFiles)
.where(
and(eq(projectFiles.projectId, projectId), eq(projectFiles.path, path)),
);
}
async listFiles(
projectId: string,
directory?: string,
): Promise<ProjectFile[]> {
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<Record<string, string>> {
const files = await this.listFiles(projectId);
const exportData: Record<string, string> = {};
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<string, string> = {
ts: "typescript",
tsx: "typescript",
js: "javascript",
jsx: "javascript",
css: "css",
html: "html",
json: "json",
md: "markdown",
};
return languageMap[ext || ""] || "text";
}
}
Gitea Integration for Backup
// 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<GiteaRepo> {
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<GiteaCommit> {
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<string, string>,
): Promise<GiteaCommit> {
// 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
// src/lib/services/migration.service.ts
export class MigrationService {
async migrateFromLocal(
localDbPath: string,
projectId: string,
options: MigrationOptions,
): Promise<MigrationResult> {
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<Database> {
// Connect to local SQLite database
const connection = await connect(dbPath);
return drizzle(connection, { schema: localSchema });
}
private async scanProjectFiles(projectPath: string): Promise<File[]> {
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
- Password Hashing: Use bcrypt with cost factor 12
- JWT Security:
- Use strong secret keys (environment variables)
- Short access token expiration (15 minutes)
- Refresh token rotation
- Token revocation on logout
- Rate Limiting:
- Login attempts: 5 per 15 minutes
- API requests: 100 per minute per user
- Password reset: 3 per hour
Data Security
- Encryption:
- Encrypt sensitive data at rest (API keys, secrets)
- Use TLS 1.3 for all connections
- Encrypt database backups
- Input Validation:
- Validate all user inputs
- Sanitize file uploads
- Prevent SQL injection (use parameterized queries)
- Authorization:
- Role-based access control (RBAC)
- Resource-level permissions
- Audit logging for sensitive actions
API Security
- CORS: Configure allowed origins
- CSRF Protection: Implement CSRF tokens
- XSS Prevention: Sanitize user-generated content
- Security Headers:
- Content-Security-Policy
- X-Frame-Options
- X-Content-Type-Options
- Strict-Transport-Security
Easypanel API Security
- API Key Management:
- Store encrypted in database
- Rotate regularly
- Use separate keys for dev/prod
- Webhook Verification:
- Verify webhook signatures
- Validate event types
- Handle idempotency
Gitea Security
- Access Tokens:
- Use personal access tokens
- Limit token permissions
- Rotate tokens regularly
- 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
-
Redis Cache:
- Session data
- Frequently accessed project data
- API responses
- Rate limiting counters
-
Database Optimization:
- Proper indexing
- Query optimization
- Connection pooling
- Read replicas (if needed)
-
CDN:
- Static assets
- Generated code previews
- Design system assets
Development Phases
Phase 1: Foundation (Weeks 1-4)
Goal: Set up core infrastructure
Tasks:
- Initialize Next.js project with TypeScript
- Set up PostgreSQL database
- Configure Drizzle ORM
- Implement authentication system
- Create user management APIs
- Set up Redis for caching
- 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:
- Create project management system
- Implement chat interface
- Integrate AI providers
- Set up code editor
- Implement file management
- Create preview system
- 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:
- Install UI/UX Pro Max
- Create design system service
- Implement design system generation
- Integrate with AI prompts
- Create design system UI
- 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:
- Create Easypanel API client
- Implement database creation
- Implement application deployment
- Add deployment management
- Create deployment UI
- Implement update deployment
- 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:
- Create Gitea API client
- Implement repository creation
- Add commit functionality
- Implement code backup
- Add version history
- 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:
- Integrate Stripe
- Create subscription management
- Implement pricing tiers
- Add billing UI
- Set up webhooks
- Create invoice management
- 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:
- Remove Supabase integration
- Remove Neon integration
- Remove Vercel integration
- Remove Electron dependencies
- Clean up unused code
- Update documentation
- 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:
- Write unit tests
- Write integration tests
- Write E2E tests
- Performance optimization
- Security audit
- Load testing
- 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:
- Set up production VPS
- Configure Nginx
- Set up SSL certificates
- Deploy application
- Configure monitoring
- Set up backups
- Create launch checklist
- Launch application
Deliverables:
- Production deployment
- Monitoring and alerting
- Backup system
- Live application
Appendix
Environment Variables
# 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 userPOST /api/auth/login- Login userPOST /api/auth/logout- Logout userPOST /api/auth/refresh- Refresh access tokenPOST /api/auth/forgot-password- Request password resetPOST /api/auth/reset-password- Reset passwordPOST /api/auth/verify-email- Verify email
Users
GET /api/users/me- Get current userPATCH /api/users/me- Update current userGET /api/users/:id- Get user by ID (admin only)GET /api/users- List all users (admin only)
Organizations
POST /api/organizations- Create organizationGET /api/organizations- List user's organizationsGET /api/organizations/:id- Get organization detailsPATCH /api/organizations/:id- Update organizationDELETE /api/organizations/:id- Delete organization
Organization Members
POST /api/organizations/:id/members- Invite memberGET /api/organizations/:id/members- List membersPATCH /api/organizations/:id/members/:memberId- Update memberDELETE /api/organizations/:id/members/:memberId- Remove member
Projects
POST /api/projects- Create projectGET /api/projects- List user's projectsGET /api/projects/:id- Get project detailsPATCH /api/projects/:id- Update projectDELETE /api/projects/:id- Delete project
Project Files
GET /api/projects/:id/files- List project filesGET /api/projects/:id/files/*- Get file contentPUT /api/projects/:id/files/*- Save fileDELETE /api/projects/:id/files/*- Delete file
Chats
POST /api/projects/:id/chats- Create chatGET /api/projects/:id/chats- List chatsGET /api/chats/:id- Get chat detailsDELETE /api/chats/:id- Delete chat
Messages
POST /api/chats/:id/messages- Send messageGET /api/chats/:id/messages- List messages
Deployment
POST /api/projects/:id/deploy- Deploy projectPOST /api/projects/:id/deploy/update- Update deploymentGET /api/projects/:id/deployments- List deploymentsGET /api/deployments/:id/logs- Get deployment logs
Design Systems
POST /api/projects/:id/design-system- Generate design systemGET /api/projects/:id/design-system- Get design systemPATCH /api/projects/:id/design-system- Update design system
Billing
POST /api/billing/checkout- Create checkout sessionGET /api/billing/subscription- Get subscription detailsPOST /api/billing/portal- Create customer portal sessionGET /api/billing/invoices- List invoices
Admin
GET /api/admin/users- List all usersGET /api/admin/organizations- List all organizationsGET /api/admin/analytics- Get system analyticsPATCH /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