feat: implement pydantic multi env setup and removed node based version
This commit is contained in:
@@ -20,9 +20,10 @@ A multi-tenant SaaS backend built with FastAPI, PostgreSQL, and SQLAlchemy.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.12+
|
||||
- PostgreSQL 12+
|
||||
- Python 3.10+
|
||||
- PostgreSQL 12+
|
||||
- Node.js (for npm scripts)
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -44,7 +45,7 @@ python -m venv venv
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
npm install # For cross-env support in npm scripts
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 4. Environment Configuration
|
||||
@@ -112,11 +113,11 @@ alembic revision --autogenerate -m "Initial schema"
|
||||
#### Run Migrations
|
||||
|
||||
```bash
|
||||
# Using npm scripts (recommended - handles APP_ENV automatically)
|
||||
npm run migrate:local
|
||||
npm run migrate:dev
|
||||
npm run migrate:prod
|
||||
npm run migrate:test
|
||||
# Using manage.py (recommended - handles APP_ENV automatically)
|
||||
python manage.py migrate --env local
|
||||
python manage.py migrate --env development
|
||||
python manage.py migrate --env production
|
||||
python manage.py migrate --env testing
|
||||
|
||||
# Or using alembic directly
|
||||
$env:APP_ENV="local" # Set environment first
|
||||
@@ -148,20 +149,20 @@ After running migrations, seed the database with initial data:
|
||||
|
||||
```bash
|
||||
# Seed super admin user
|
||||
npm run seed:superadmin:local
|
||||
python manage.py seed superadmin --env local
|
||||
|
||||
# Seed default color palettes
|
||||
npm run seed:palettes:local
|
||||
python manage.py seed palettes --env local
|
||||
```
|
||||
|
||||
### 7. Run the Application
|
||||
|
||||
```bash
|
||||
# Using npm scripts (recommended)
|
||||
npm run local # Local environment
|
||||
npm run dev # Development environment
|
||||
npm run prod # Production environment
|
||||
npm run test # Testing environment
|
||||
# Using manage.py (recommended)
|
||||
python manage.py run --env local # Local environment
|
||||
python manage.py run --env development # Development environment
|
||||
python manage.py run --env production # Production environment
|
||||
python manage.py run --env testing # Testing environment
|
||||
|
||||
# Or using Python directly
|
||||
python run.py
|
||||
@@ -209,7 +210,8 @@ backend/
|
||||
│ ├── seed_palettes.py
|
||||
│ └── seed_superadmin.py
|
||||
├── alembic.ini # Alembic configuration
|
||||
├── package.json # NPM scripts
|
||||
├── alembic.ini # Alembic configuration
|
||||
├── manage.py # Management CLI script
|
||||
├── requirements.txt # Python dependencies
|
||||
└── run.py # Application entry point
|
||||
```
|
||||
|
||||
+6
-12
@@ -5,21 +5,21 @@ import logging
|
||||
from sqlalchemy import text
|
||||
from app.config.settings import settings
|
||||
from app.config.database import engine
|
||||
|
||||
# Import models for Alembic
|
||||
import app.models.auth.user_model
|
||||
import app.models.auth.role_model
|
||||
import app.models.auth.tenant_model
|
||||
import app.models.theme.color_palette_model
|
||||
|
||||
# New Module Integration Models
|
||||
import app.models.auth.module_model
|
||||
import app.models.auth.module_environment_model
|
||||
import app.models.auth.tenant_module_model
|
||||
import app.models.auth.sso_grant_model
|
||||
import app.models.auth.access_model # Re-import to ensure updates are picked up
|
||||
import app.models.auth.access_model
|
||||
import app.models.system.event_log_model
|
||||
|
||||
import asyncio
|
||||
from app.services.auth.event_service import EventService
|
||||
from app.config.database import SessionLocal
|
||||
from app.core.redis import redis_client, sync_redis_client
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
@@ -141,11 +141,6 @@ def create_app() -> FastAPI:
|
||||
f"{settings.PROJECT_NAME} v{settings.VERSION} started ({settings.APP_ENV})"
|
||||
)
|
||||
|
||||
import asyncio
|
||||
from app.services.auth.event_service import EventService
|
||||
from app.config.database import SessionLocal
|
||||
from app.core.redis import redis_client, sync_redis_client
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
|
||||
async def redis_event_consumer():
|
||||
logger.info("Redis Event Consumer STARTED")
|
||||
@@ -185,7 +180,6 @@ def create_app() -> FastAPI:
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
logger.info("Shutting down...")
|
||||
from app.core.redis import redis_client
|
||||
await redis_client.close()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import HTTPException, status, BackgroundTasks
|
||||
from fastapi import HTTPException, status
|
||||
from typing import List, Optional
|
||||
import uuid
|
||||
from app.models.auth.user_model import User
|
||||
|
||||
@@ -9,7 +9,6 @@ from app.schemas.theme.color_palette_schema import (
|
||||
)
|
||||
from app.services.theme.color_palette_service import PaletteService
|
||||
|
||||
|
||||
class PaletteController:
|
||||
@staticmethod
|
||||
def get_all_palettes(db: Session, current_user: User) -> List[ColorPalette]:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .auth import router
|
||||
from .tenant import router
|
||||
from .role import router
|
||||
from .user import router
|
||||
@@ -0,0 +1 @@
|
||||
from .color_palette import router
|
||||
@@ -14,14 +14,12 @@ from app.models.auth.user_model import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/get", response_model=List[ColorPaletteResponse])
|
||||
def get_all_palettes(
|
||||
db: Session = Depends(get_db), current_user: User = Depends(get_current_user)
|
||||
):
|
||||
return PaletteController.get_all_palettes(db, current_user)
|
||||
|
||||
|
||||
@router.get("/get/{palette_id}", response_model=ColorPaletteResponse)
|
||||
def get_palette(
|
||||
palette_id: UUID,
|
||||
@@ -30,7 +28,6 @@ def get_palette(
|
||||
):
|
||||
return PaletteController.get_palette(db, palette_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/create", response_model=ColorPaletteResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
@@ -42,12 +39,10 @@ def create_palette(
|
||||
):
|
||||
return PaletteController.create_palette(db, data, current_user)
|
||||
|
||||
|
||||
@router.put("/update/users/me/preferences", deprecated=True)
|
||||
def update_user_preference():
|
||||
pass
|
||||
|
||||
|
||||
@router.put("/update/{palette_id}", response_model=ColorPaletteResponse)
|
||||
def update_palette(
|
||||
palette_id: UUID,
|
||||
@@ -58,7 +53,6 @@ def update_palette(
|
||||
):
|
||||
return PaletteController.update_palette(db, palette_id, data)
|
||||
|
||||
|
||||
@router.delete("/delete/{palette_id}", status_code=status.HTTP_200_OK)
|
||||
def delete_palette(
|
||||
palette_id: UUID,
|
||||
|
||||
@@ -173,7 +173,7 @@ class TenantService:
|
||||
if event_targets:
|
||||
payload = {
|
||||
"tenant_id": str(tenant.id),
|
||||
"tenant_name": tenant.tenant_name, # Note: using current name (might be old if not updated yet, but usually distinct requests)
|
||||
"tenant_name": tenant.tenant_name,
|
||||
"provisioning_id": provisioning_id,
|
||||
"targets": event_targets
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import typer
|
||||
from typing import Optional
|
||||
|
||||
app = typer.Typer(help="Management script for SaaS Backend")
|
||||
seed_app = typer.Typer(help="Seeding commands")
|
||||
app.add_typer(seed_app, name="seed")
|
||||
|
||||
def run_command(args: list[str], env_name: str):
|
||||
"""
|
||||
Helper to run commands with a specific APP_ENV.
|
||||
Replaces "python" with sys.executable to ensure the same interpreter is used.
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
env["APP_ENV"] = env_name
|
||||
|
||||
if args[0] == "python":
|
||||
args[0] = sys.executable
|
||||
|
||||
try:
|
||||
subprocess.run(args, env=env, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
sys.exit(e.returncode)
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
|
||||
@app.command()
|
||||
def run(
|
||||
env: str = typer.Option("local", "--env", "-e", help="Environment to run in (local, development, production, testing)")
|
||||
):
|
||||
"""Start the FastAPI server."""
|
||||
run_command(["python", "run.py"], env)
|
||||
|
||||
@app.command()
|
||||
def migrate(
|
||||
env: str = typer.Option("local", "--env", "-e", help="Environment to run in (local, development, production, testing)")
|
||||
):
|
||||
"""Run Alembic migrations (upgrade head)."""
|
||||
run_command(["python", "-m", "alembic", "upgrade", "head"], env)
|
||||
|
||||
@seed_app.command("palettes")
|
||||
def seed_palettes(
|
||||
env: str = typer.Option("local", "--env", "-e", help="Environment to run in (local, development, production, testing)")
|
||||
):
|
||||
"""Seed palettes database."""
|
||||
run_command(["python", "scripts/seed_palettes.py"], env)
|
||||
|
||||
@seed_app.command("superadmin")
|
||||
def seed_superadmin(
|
||||
env: str = typer.Option("local", "--env", "-e", help="Environment to run in (local, development, production, testing)")
|
||||
):
|
||||
"""Seed superadmin user."""
|
||||
run_command(["python", "scripts/seed_superadmin.py"], env)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
Generated
-105
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"name": "saas-backend",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "saas-backend",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "saas-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend for SaaS architecture",
|
||||
"main": "run.py",
|
||||
"scripts": {
|
||||
"start": "python run.py",
|
||||
"local": "cross-env APP_ENV=local python run.py",
|
||||
"dev": "cross-env APP_ENV=development python run.py",
|
||||
"prod": "cross-env APP_ENV=production python run.py",
|
||||
"test": "cross-env APP_ENV=testing python run.py",
|
||||
"migrate:local": "cross-env APP_ENV=local python -m alembic upgrade head",
|
||||
"migrate:dev": "cross-env APP_ENV=development python -m alembic upgrade head",
|
||||
"migrate:prod": "cross-env APP_ENV=production python -m alembic upgrade head",
|
||||
"migrate:test": "cross-env APP_ENV=testing python -m alembic upgrade head",
|
||||
"seed:palettes:local": "cross-env APP_ENV=local python scripts/seed_palettes.py",
|
||||
"seed:palettes:dev": "cross-env APP_ENV=development python scripts/seed_palettes.py",
|
||||
"seed:palettes:prod": "cross-env APP_ENV=production python scripts/seed_palettes.py",
|
||||
"seed:palettes:test": "cross-env APP_ENV=testing python scripts/seed_palettes.py",
|
||||
"seed:superadmin:local": "cross-env APP_ENV=local python scripts/seed_superadmin.py",
|
||||
"seed:superadmin:dev": "cross-env APP_ENV=development python scripts/seed_superadmin.py",
|
||||
"seed:superadmin:prod": "cross-env APP_ENV=production python scripts/seed_superadmin.py",
|
||||
"seed:superadmin:test": "cross-env APP_ENV=testing python scripts/seed_superadmin.py"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
@@ -10,3 +10,4 @@ pyjwt>=2.8.0
|
||||
email-validator>=2.1.0
|
||||
redis==7.1.0
|
||||
requests==2.32.5
|
||||
typer==0.21.1
|
||||
Reference in New Issue
Block a user