144 lines
4.3 KiB
Markdown
144 lines
4.3 KiB
Markdown
|
|
# CRM System v1.0
|
|||
|
|
|
|||
|
|
> Self-hosted CRM for small sales teams (5–25 sales reps).
|
|||
|
|
> Stack: FastAPI + SQLAlchemy (async) + Alembic + Pydantic v2 + SQLite/PostgreSQL + Alpine.js + Tailwind + Docker + Coolify
|
|||
|
|
|
|||
|
|
## Quick Start (Development)
|
|||
|
|
|
|||
|
|
### 1. Clone and Setup
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git clone <repo-url> crm-system
|
|||
|
|
cd crm-system
|
|||
|
|
|
|||
|
|
# Create virtual environment
|
|||
|
|
python3 -m venv .venv
|
|||
|
|
source .venv/bin/activate
|
|||
|
|
|
|||
|
|
# Install dependencies
|
|||
|
|
pip install -r requirements.txt -r requirements-dev.txt
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Configure Environment
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Copy template
|
|||
|
|
cp .env.example .env
|
|||
|
|
|
|||
|
|
# Generate a secure AUTH_SECRET (min 32 chars)
|
|||
|
|
python3 -c "import secrets; print('AUTH_SECRET=' + secrets.token_urlsafe(48))" >> .env
|
|||
|
|
|
|||
|
|
# Edit .env and set AUTH_SECRET (remove the placeholder line first)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Initialize Database
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Apply migrations
|
|||
|
|
alembic upgrade head
|
|||
|
|
|
|||
|
|
# (Optional) Create migration after model changes
|
|||
|
|
# alembic revision --autogenerate -m "description"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Run Server
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Development with auto-reload
|
|||
|
|
uvicorn app.main:app --reload --port 8000
|
|||
|
|
|
|||
|
|
# Production-like
|
|||
|
|
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 2
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Open:
|
|||
|
|
- API: http://localhost:8000
|
|||
|
|
- Swagger UI: http://localhost:8000/docs
|
|||
|
|
- ReDoc: http://localhost:8000/redoc
|
|||
|
|
- Health: http://localhost:8000/health
|
|||
|
|
|
|||
|
|
### 5. Bootstrap First User
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X POST http://localhost:8000/api/v1/auth/register \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d '{
|
|||
|
|
"email": "admin@example.com",
|
|||
|
|
"password": "secure-password-123",
|
|||
|
|
"name": "First Admin"
|
|||
|
|
}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This creates the first user + a default org. After that, registration is disabled (use admin invite flow in v1.1).
|
|||
|
|
|
|||
|
|
## Project Structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
crm-system/
|
|||
|
|
├── app/ # Application package
|
|||
|
|
│ ├── main.py # FastAPI entry point
|
|||
|
|
│ ├── core/ # Core modules (config, db, security, deps)
|
|||
|
|
│ ├── models/ # SQLAlchemy models
|
|||
|
|
│ ├── schemas/ # Pydantic schemas (request/response)
|
|||
|
|
│ ├── services/ # Business logic layer
|
|||
|
|
│ ├── api/v1/ # API routers (versioned)
|
|||
|
|
│ └── webui/ # Static frontend (Phase 4c)
|
|||
|
|
├── alembic/ # Database migrations
|
|||
|
|
│ ├── env.py # Async migration environment
|
|||
|
|
│ └── versions/ # Migration scripts
|
|||
|
|
├── tests/ # Test suite (pytest + pytest-asyncio)
|
|||
|
|
├── requirements.txt # Production dependencies
|
|||
|
|
├── requirements-dev.txt # Test/lint dependencies
|
|||
|
|
├── pyproject.toml # Tool configuration
|
|||
|
|
├── alembic.ini # Alembic configuration
|
|||
|
|
├── .env.example # Environment template
|
|||
|
|
└── README.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Testing
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Run all tests
|
|||
|
|
pytest -v --tb=short
|
|||
|
|
|
|||
|
|
# Run with coverage
|
|||
|
|
pytest --cov=app --cov-report=term-missing
|
|||
|
|
|
|||
|
|
# Run specific test file
|
|||
|
|
pytest tests/test_auth.py -v
|
|||
|
|
|
|||
|
|
# Stop on first failure (for debugging)
|
|||
|
|
pytest -x
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Environment Variables
|
|||
|
|
|
|||
|
|
| Variable | Required | Default | Description |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `AUTH_SECRET` | ✅ | – | JWT signing secret (≥32 chars). Hard-fail if missing. |
|
|||
|
|
| `DATABASE_URL` | ❌ | `sqlite+aiosqlite:///./dev.db` | Async DB URL (aiosqlite or asyncpg) |
|
|||
|
|
| `JWT_ALGORITHM` | ❌ | `HS256` | JWT algorithm |
|
|||
|
|
| `JWT_EXPIRY_HOURS` | ❌ | `24` | Token lifetime |
|
|||
|
|
| `BCRYPT_ROUNDS` | ❌ | `12` | Password hashing cost |
|
|||
|
|
| `CORS_ORIGINS` | ❌ | `http://localhost:5500,http://localhost:8000` | Allowed origins (comma-separated, NO wildcards) |
|
|||
|
|
| `ENVIRONMENT` | ❌ | `development` | `development` or `production` |
|
|||
|
|
| `LOG_LEVEL` | ❌ | `INFO` | Python log level |
|
|||
|
|
|
|||
|
|
## Architecture Decisions (ADR)
|
|||
|
|
|
|||
|
|
- **JWT Library**: `python-jose[cryptography]==3.3.0` (pattern reuse from wochenplaner)
|
|||
|
|
- **Database**: SQLite (aiosqlite) for dev, PostgreSQL (asyncpg) for prod
|
|||
|
|
- **Auth**: Stateless JWT in localStorage, bcrypt password hashing (12 rounds)
|
|||
|
|
- **Security**: CORS whitelist (no wildcard), CSP middleware, no default admin user
|
|||
|
|
- **Async**: All routers/services/DB operations are async (SQLAlchemy 2.0 + aiosqlite)
|
|||
|
|
|
|||
|
|
See `/a0/.a0/02-architecture.md` Section 13 for full lockdown decisions.
|
|||
|
|
|
|||
|
|
## Deployment
|
|||
|
|
|
|||
|
|
See `/a0/.a0/03-task-graph.json` Phase 4d for Docker + Coolify setup (out of scope for Phase 4a).
|
|||
|
|
|
|||
|
|
## License
|
|||
|
|
|
|||
|
|
Internal project – proprietary.
|