2026-02-10 12:05:22 +09:00
|
|
|
import logging
|
|
|
|
|
import os
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
from typing import List
|
|
|
|
|
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
load_dotenv()
|
|
|
|
|
|
|
|
|
|
from fastapi import FastAPI, Depends, HTTPException, Path
|
|
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
from src.database.config import get_db, init_db
|
|
|
|
|
from src.auth.router import router as auth_router, admin_router as auth_admin_router
|
|
|
|
|
from src.auth.dependencies import require_auth, require_superadmin
|
|
|
|
|
from src.auth.models import TokenData
|
|
|
|
|
from src.auth.password import hash_password
|
|
|
|
|
from src.tenant import manager as tenant_manager
|
|
|
|
|
from src.tenant.manager import TenantNotFoundError, InvalidTenantIdError
|
|
|
|
|
from src.api.machines import router as machines_router
|
|
|
|
|
from src.api.equipment_parts import router as equipment_parts_router
|
2026-02-10 13:24:30 +09:00
|
|
|
from src.api.templates import router as templates_router
|
2026-02-10 13:46:23 +09:00
|
|
|
from src.api.inspections import router as inspections_router
|
2026-02-10 14:39:03 +09:00
|
|
|
from src.api.alarms import router as alarms_router
|
2026-02-10 12:05:22 +09:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
await init_db()
|
|
|
|
|
logger.info("Database initialized")
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI(title="FactoryOps v2 API", lifespan=lifespan)
|
|
|
|
|
|
|
|
|
|
CORS_ORIGINS = (
|
|
|
|
|
os.getenv("CORS_ORIGINS", "").split(",") if os.getenv("CORS_ORIGINS") else []
|
|
|
|
|
)
|
|
|
|
|
if not CORS_ORIGINS:
|
2026-02-10 12:21:07 +09:00
|
|
|
CORS_ORIGINS = ["http://localhost:3100", "http://127.0.0.1:3100"]
|
2026-02-10 12:05:22 +09:00
|
|
|
app.add_middleware(
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
allow_origins=CORS_ORIGINS,
|
|
|
|
|
allow_credentials=True,
|
|
|
|
|
allow_methods=["*"],
|
|
|
|
|
allow_headers=["*"],
|
|
|
|
|
)
|
|
|
|
|
|
2026-02-10 13:24:30 +09:00
|
|
|
app.include_router(auth_router)
|
|
|
|
|
app.include_router(auth_admin_router)
|
|
|
|
|
app.include_router(machines_router)
|
|
|
|
|
app.include_router(equipment_parts_router)
|
|
|
|
|
app.include_router(templates_router)
|
2026-02-10 13:46:23 +09:00
|
|
|
app.include_router(inspections_router)
|
2026-02-10 14:39:03 +09:00
|
|
|
app.include_router(alarms_router)
|
2026-02-10 13:24:30 +09:00
|
|
|
|
2026-02-10 12:05:22 +09:00
|
|
|
|
|
|
|
|
@app.get("/api/health")
|
|
|
|
|
async def health_check():
|
|
|
|
|
return {"status": "ok", "version": "2.0.0"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TenantCreate(BaseModel):
|
|
|
|
|
id: str
|
|
|
|
|
name: str
|
|
|
|
|
industry_type: str = "general"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/tenants")
|
|
|
|
|
async def list_accessible_tenants(
|
|
|
|
|
current_user: TokenData = Depends(require_auth),
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
all_tenants = await tenant_manager.list_tenants(db)
|
|
|
|
|
|
|
|
|
|
if current_user.role == "superadmin":
|
|
|
|
|
return {"tenants": [t for t in all_tenants if t.get("is_active", True)]}
|
|
|
|
|
|
|
|
|
|
if current_user.tenant_id:
|
|
|
|
|
return {
|
|
|
|
|
"tenants": [
|
|
|
|
|
t
|
|
|
|
|
for t in all_tenants
|
|
|
|
|
if t["id"] == current_user.tenant_id and t.get("is_active", True)
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {"tenants": []}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/admin/tenants")
|
|
|
|
|
async def list_all_tenants(
|
|
|
|
|
current_user: TokenData = Depends(require_superadmin),
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
return {"tenants": await tenant_manager.list_tenants(db)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/admin/tenants")
|
|
|
|
|
async def create_tenant(
|
|
|
|
|
tenant: TenantCreate,
|
|
|
|
|
current_user: TokenData = Depends(require_superadmin),
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
try:
|
|
|
|
|
result = await tenant_manager.create_tenant(
|
|
|
|
|
db, tenant.id, tenant.name, tenant.industry_type
|
|
|
|
|
)
|
|
|
|
|
return {"status": "success", "tenant": result}
|
|
|
|
|
except InvalidTenantIdError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=409, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get(
|
|
|
|
|
"/api/admin/tenants/{tenant_id}",
|
|
|
|
|
)
|
|
|
|
|
async def get_tenant(
|
|
|
|
|
tenant_id: str = Path(..., pattern=r"^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$"),
|
|
|
|
|
current_user: TokenData = Depends(require_superadmin),
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
try:
|
|
|
|
|
return await tenant_manager.get_tenant(db, tenant_id)
|
|
|
|
|
except TenantNotFoundError as e:
|
|
|
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import uvicorn
|
|
|
|
|
|
|
|
|
|
uvicorn.run(
|
|
|
|
|
"main:app",
|
|
|
|
|
host=os.getenv("HOST", "0.0.0.0"),
|
|
|
|
|
port=int(os.getenv("PORT", "8000")),
|
|
|
|
|
reload=True,
|
|
|
|
|
)
|