Files
factoryOps-v2/main.py
Johngreen 278cd9d551
All checks were successful
Deploy to Production / deploy (push) Successful in 1m37s
feat: bidirectional equipment sync with digital-twin
Add import, sync, and push capabilities between factoryOps and the
digital-twin (BaSyx AAS) backend. Includes:

- Equipment sync service with field mapping and LWW conflict resolution
- Import preview modal with already-imported detection
- Bidirectional sync (pull updates + push local changes)
- Sync history tracking via equipment_sync_history table
- Machine detail page shows sync status and change history
- Docker networking for container-to-container communication
- UI fixes: responsive layout (375px), touch targets, section spacing
- 30 test cases for sync service
2026-02-12 12:27:21 +09:00

145 lines
4.1 KiB
Python

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_sync import router as equipment_sync_router
from src.api.equipment_parts import router as equipment_parts_router
from src.api.templates import router as templates_router
from src.api.inspections import router as inspections_router
from src.api.alarms import router as alarms_router
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:
CORS_ORIGINS = ["http://localhost:3100", "http://127.0.0.1:3100"]
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth_router)
app.include_router(auth_admin_router)
app.include_router(machines_router)
app.include_router(equipment_sync_router)
app.include_router(equipment_parts_router)
app.include_router(templates_router)
app.include_router(inspections_router)
app.include_router(alarms_router)
@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,
)