# Apex SaaS Framework

**Build production-ready SaaS applications in minutes, not months!**

A modern FastAPI framework with **LangChain-style simplicity** for building multi-tenant SaaS applications. Get authentication, user management, organizations, roles, permissions, payments, and more with just 2 lines of code!

## ⚡ Super Quick Start

```python
from apex import Apex

app = Apex().app

# That's it! You now have a complete SaaS backend! 🎉
# Run with: uvicorn app:app --reload
```

**You instantly get:**
- ✅ Complete authentication (signup, login, password reset)
- ✅ User management with JSONB settings
- ✅ Organization management with multi-tenancy
- ✅ Role-based permissions (RBAC)
- ✅ PayPal payment integration
- ✅ Auto-created database tables
- ✅ Interactive API docs at `/docs`

## 📦 Installation

### From PyPI (Production - Recommended)

**Simple installation:**

```bash
pip install apex-saas-framework
```

That's it! No special flags needed! ✅

### From Test PyPI (For Testing)

If you want to test pre-release versions:

```bash
pip install --index-url https://pypi.org/simple/ --extra-index-url https://test.pypi.org/simple/ apex-saas-framework
```

## 🚀 Features

- 🎯 **LangChain-Style Simplicity** - Minimal imports, maximum functionality
- 🏢 **Multi-tenant Architecture** - Organizations with row-level isolation
- 👥 **User Management** - Complete user system with JWT authentication
- 🔐 **Authentication** - Signup, login, forgot/reset password, change password
- 🛡️ **RBAC** - Role-based access control with permissions
- 📦 **Extensible Models** - Extend base models using ORM inheritance
- 🗄️ **PostgreSQL** - SQLAlchemy 2.0 with async support
- 💳 **PayPal Integration** - Subscription management and webhooks
- ⚙️ **JSONB Settings** - Flexible user and organization settings
- 🎨 **Feature Flags** - Module system for enabling/disabling features
- 📧 **Email Integration** - SendGrid adapter with templates
- 🛠️ **CLI Tools** - Command-line utilities
- 📝 **Auto-Documentation** - Interactive API docs with FastAPI

## 💡 Simple Examples

### Minimal App (2 Lines!)
```python
from apex import Apex
app = Apex().app
```

### With Custom Models
```python
from apex import Apex, User, Organization
from sqlalchemy import Column, String

class MyUser(User):
    __tablename__ = "users"
    department = Column(String(100))

app = Apex(models=[MyUser]).app
```

### With Custom Endpoints
```python
from apex import Apex, auth_required, User

app = Apex().app

@app.get("/api/profile")
@auth_required
async def get_profile(user: User):
    return {"email": user.email, "name": user.first_name}
```

### Fluent API Style
```python
from apex import Apex

app = (
    Apex()
    .with_auth()
    .with_payments()
    .with_rbac()
    .build()
)
```

## 🎯 Quick Start Guide

### 1. Create Your Models

Start by re-exporting the Apex defaults so each table is only registered once:

```python
# app/models.py
from apex.app.models.default import (
    Organization,
    OrganizationLocation,
    Permission,
    Role,
    User,
)

__all__ = ["Organization", "OrganizationLocation", "Permission", "Role", "User"]
```

> Need custom columns? Create **new subclasses** (e.g. `class CustomUser(User): ...`)
> in a separate module and update the dependency overrides (`get_user_service`,
> `get_organization_model`, etc.) to point at them. Avoid redefining the stock
> tables in place—SQLAlchemy will raise “Table … already defined” otherwise.

### 2. Configure Settings

Copy `.env.example` to `.env` and update with your values:

```bash
cp .env.example .env
```

**Minimum required configuration:**

```env
# Database (Required)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

# Security (Required)
SECRET_KEY=your-secret-key-here-minimum-32-characters
```

**Generate a secure secret key:**

```bash
python -c "import secrets; print(secrets.token_urlsafe(32))"
```

See [Configuration Reference](#configuration) below for all available options.

### 3. Set Up Database

```python
# app/database.py
from apex.infrastructure.database import Base, engine
from app.models import User, Organization, Role, Permission

# Create tables
async def init_db():
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
```

### 4. Create Your FastAPI App

```python
# app.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from apex.api.v1.auth.router import router as auth_router
from apex.core.authentication.dependencies import get_auth_service
from apex.core.config import get_settings
from app.dependencies import get_auth_service as project_get_auth_service

settings = get_settings()

app = FastAPI(
    title=settings.APP_NAME,
    version=settings.APP_VERSION,
    debug=settings.DEBUG,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.CORS_ORIGINS,
    allow_credentials=settings.CORS_ALLOW_CREDENTIALS,
    allow_methods=settings.CORS_ALLOW_METHODS,
    allow_headers=settings.CORS_ALLOW_HEADERS,
)

app.dependency_overrides[get_auth_service] = project_get_auth_service
app.include_router(auth_router, prefix=settings.API_V1_PREFIX)

@app.get("/")
async def root():
    return {"message": "Apex"}
```

### 5. Override Auth Service

```python
# app/services.py
from apex.domain.services.auth import AuthService
from app.models import User
from sqlalchemy.ext.asyncio import AsyncSession

def get_auth_service(db: AsyncSession) -> AuthService:
    return AuthService(session=db, user_model=User)
```

### 6. Run Migrations

Set up Alembic:

```bash
alembic init migrations
```

Copy `apex/migrations/env.py` to your project and modify it to import your models:

```python
# migrations/env.py
from apex.infrastructure.database.base import Base
from app.models import User, Organization, Role, Permission

target_metadata = Base.metadata
```

Create and run migrations:

```bash
alembic revision --autogenerate -m "Initial migration"
alembic upgrade head
```

## Architecture

### Project Structure

```
apex/
├── core/                    # Core functionality
│   ├── config/              # Settings (Pydantic BaseSettings)
│   ├── security/            # Password hashing, JWT utilities
│   ├── authentication/      # Auth dependencies (get_current_user, etc.)
│   ├── permissions/         # RBAC dependencies (require_role, require_permission)
│   └── utils/               # Utility functions
├── domain/                  # Business logic layer
│   ├── models/              # Base domain models (abstract)
│   │   ├── user.py          # BaseUser model
│   │   ├── organization.py  # BaseOrganization, BaseOrganizationLocation
│   │   ├── role.py          # BaseRole model
│   │   └── permission.py    # BasePermission model
│   ├── repositories/        # Repository pattern (BaseRepository)
│   └── services/            # Business services (AuthService, UserService)
├── infrastructure/          # External adapters
│   ├── database/            # Database setup (SQLAlchemy async, session mgmt)
│   ├── email/               # Email adapters (SMTP, abstract interface)
│   ├── storage/             # File storage adapters (Local, S3-ready)
│   └── payments/            # Payment scaffolding (Stripe-ready)
├── api/                     # API layer
│   └── v1/                  # API version 1
│       ├── auth/            # Authentication routes
│       │   └── router.py    # Login, logout, refresh, me endpoints
│       └── schemas/         # Pydantic request/response schemas
│           ├── auth.py      # Auth schemas
│           └── user.py      # User schemas
├── migrations/              # Alembic templates
│   ├── env.py               # Alembic environment (copy to user project)
│   └── script.py.mako       # Migration script template
└── cli/                     # CLI utilities (Typer)
    └── main.py              # CLI commands (version, check, init-db, etc.)
```

### Design Principles

1. **Clean Architecture** - Separation of concerns with clear layers
2. **Extensibility** - Extend base classes, don't modify them
3. **Type Safety** - Full type hints throughout
4. **Async First** - Built for async/await patterns
5. **Framework Agnostic Core** - Business logic independent of FastAPI

## Usage Examples

### Authentication

```python
from apex.domain.services.auth import AuthService
from app.models import User

# Login
auth_service = AuthService(session=db, user_model=User)
user = await auth_service.authenticate_user("user@example.com", "password")
tokens = await auth_service.create_tokens(user)
```

### User Management

```python
from apex.domain.services.user import UserService

user_service = UserService(session=db, user_model=User)

# Create user
user = await user_service.create_user(
    email="user@example.com",
    password="securepassword",
    full_name="John Doe",
)

# Update user
await user_service.update_user(user, full_name="Jane Doe")

# Change password
await user_service.change_password(user, "oldpass", "newpass")
```

### Permissions

```python
from apex.core.permissions import require_permission, require_role

# In your router
@router.post("/users")
async def create_user(
    current_user: dict = Depends(require_permission("users", "create"))
):
    # User has "users:create" permission
    pass

@router.delete("/users/{id}")
async def delete_user(
    id: str,
    current_user: dict = Depends(require_role("admin"))
):
    # User has "admin" role
    pass
```

### File Storage

```python
from apex.infrastructure.storage import LocalStorageAdapter

storage = LocalStorageAdapter()

# Upload file
file_path = await storage.upload_file(
    file=uploaded_file,
    filename="document.pdf",
    folder="documents"
)

# Get file URL
url = await storage.get_file_url(file_path)
```

### Email

```python
from apex.infrastructure.email import SMTPEmailAdapter

email = SMTPEmailAdapter()

await email.send_email(
    to="user@example.com",
    subject="Welcome!",
    body="Welcome to our platform",
    html="<h1>Welcome!</h1>"
)
```

## Configuration

All configuration is done via environment variables or `.env` file. Copy `.env.example` to `.env` and customize.

### Required Settings

| Variable | Description | Example |
|----------|-------------|---------|
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/db` |
| `SECRET_KEY` | JWT secret key (min 32 chars) | Generate with `secrets.token_urlsafe(32)` |

### Application Settings

| Variable | Description | Default |
|----------|-------------|---------|
| `APP_NAME` | Application name | `"Apex Backend"` |
| `DEBUG` | Debug mode | `False` |
| `API_V1_PREFIX` | API version 1 prefix | `"/api/v1"` |

### Database Settings

| Variable | Description | Default |
|----------|-------------|---------|
| `DB_ECHO` | Log SQL queries | `False` |
| `DB_POOL_SIZE` | Connection pool size | `5` |
| `DB_MAX_OVERFLOW` | Max overflow connections | `10` |

### JWT/Auth Settings

| Variable | Description | Default |
|----------|-------------|---------|
| `ALGORITHM` | JWT algorithm | `"HS256"` |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | Access token lifetime | `30` |
| `REFRESH_TOKEN_EXPIRE_DAYS` | Refresh token lifetime | `7` |

### CORS Settings

| Variable | Description | Default |
|----------|-------------|---------|
| `CORS_ORIGINS` | Allowed origins (comma-separated) | `"http://localhost:3000"` |
| `CORS_ALLOW_CREDENTIALS` | Allow credentials | `True` |
| `CORS_ALLOW_METHODS` | Allowed HTTP methods | `"GET,POST,PUT,DELETE,PATCH,OPTIONS"` |
| `CORS_ALLOW_HEADERS` | Allowed headers | `"*"` |

### Email Settings (Optional)

| Variable | Description | Example |
|----------|-------------|---------|
| `SMTP_HOST` | SMTP server | `smtp.gmail.com` |
| `SMTP_PORT` | SMTP port | `587` |
| `SMTP_USER` | SMTP username | `your-email@gmail.com` |
| `SMTP_PASSWORD` | SMTP password | Your app password |
| `SMTP_FROM_EMAIL` | From email address | `noreply@yourdomain.com` |
| `SMTP_USE_TLS` | Use TLS | `True` |

### Storage Settings

| Variable | Description | Default |
|----------|-------------|---------|
| `STORAGE_TYPE` | Storage type (`local` or `s3`) | `"local"` |
| `STORAGE_LOCAL_PATH` | Local storage path | `"./uploads"` |
| `AWS_ACCESS_KEY_ID` | AWS access key (if using S3) | - |
| `AWS_SECRET_ACCESS_KEY` | AWS secret key (if using S3) | - |
| `AWS_REGION` | AWS region (if using S3) | `"us-east-1"` |
| `AWS_S3_BUCKET` | S3 bucket name (if using S3) | - |

### Payment Settings (Optional)

| Variable | Description | Example |
|----------|-------------|---------|
| `STRIPE_SECRET_KEY` | Stripe secret key | `sk_test_...` |
| `STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | `pk_test_...` |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook secret | `whsec_...` |

### Multi-tenancy

| Variable | Description | Default |
|----------|-------------|---------|
| `MULTI_TENANT_MODE` | Enable multi-tenancy | `True` |

## CLI Commands

The `apex` command-line tool provides utilities for managing your application:

```bash
# Show package version
apex version

# Check configuration and database connection
apex check

# Initialize database (base implementation - extend in your app)
apex init-db

# Create superuser (base implementation - extend in your app)
apex create-superuser --email admin@example.com --password
```

**Note:** `init-db` and `create-superuser` are base implementations. You should extend these commands in your application to work with your specific User model.

## Extending the Framework

### Custom User Model

```python
class User(BaseUser):
    __tablename__ = "users"
    
    # Add custom fields
    avatar_url: Mapped[str | None] = None
    bio: Mapped[str | None] = None
```

### Custom Service

```python
from apex.domain.services.user import UserService

class CustomUserService(UserService):
    async def get_users_by_organization(self, org_id: str):
        # Custom logic
        pass
```

### Custom Repository

```python
from apex.domain.repositories.base import BaseRepository

class UserRepository(BaseRepository[User]):
    async def get_by_email(self, email: str) -> User | None:
        # Custom query
        pass
```

## Testing

```python
from fastapi.testclient import TestClient
from apex.infrastructure.database import get_db

# Override database dependency
app.dependency_overrides[get_db] = get_test_db

client = TestClient(app)
response = client.post("/api/v1/auth/login", json={
    "email": "test@example.com",
    "password": "password"
})
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT License - see LICENSE file for details.

## Project Files

- `pyproject.toml` - Package configuration and dependencies
- `.env.example` - Template for environment variables (copy to `.env`)
- `.gitignore` - Git ignore patterns
- `.cursor/rules/cursorrules.mdc` - Cursor AI editing rules
- `README.md` - This file

## Support

- 📖 [Documentation](https://github.com/yourusername/apex#readme)
- 🐛 [Issue Tracker](https://github.com/yourusername/apex/issues)
- 💬 [Discussions](https://github.com/yourusername/apex/discussions)

## Roadmap

- [ ] S3 storage adapter implementation
- [ ] Stripe payment integration
- [ ] Audit logging system
- [ ] Module/feature flag system
- [ ] WebSocket support
- [ ] GraphQL support
- [ ] More comprehensive tests
- [ ] Docker deployment examples

---

Built with ❤️ using FastAPI, SQLAlchemy, and modern Python practices.

