Initial commit: site monorepo with API, web, and infra.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
|
||||
from app.core.crypto import create_access_token
|
||||
from app.core.dependencies import get_current_user, require_superuser
|
||||
from app.modules.sync import repository as sync_repo
|
||||
from app.modules.sync.schemas import EnterpriseOut, FarmHubOut
|
||||
from app.modules.users.models import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/enterprises")
|
||||
def list_enterprises(user: User = Depends(get_current_user)):
|
||||
# Superuser sees all; regular users see memberships only (simplified v1)
|
||||
if user.is_superuser:
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.database import session_scope
|
||||
from app.modules.sync.models import Enterprise
|
||||
|
||||
with session_scope() as db:
|
||||
rows = list(db.scalars(select(Enterprise).order_by(Enterprise.name)))
|
||||
return [
|
||||
EnterpriseOut(id=r.id, name=r.name, slug=r.slug, status=r.status).model_dump()
|
||||
for r in rows
|
||||
]
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.database import session_scope
|
||||
from app.modules.sync.models import Enterprise, EnterpriseMember
|
||||
|
||||
with session_scope() as db:
|
||||
rows = list(
|
||||
db.scalars(
|
||||
select(Enterprise)
|
||||
.join(EnterpriseMember, EnterpriseMember.enterprise_id == Enterprise.id)
|
||||
.where(EnterpriseMember.user_id == user.id)
|
||||
)
|
||||
)
|
||||
return [EnterpriseOut(id=r.id, name=r.name, slug=r.slug, status=r.status).model_dump() for r in rows]
|
||||
|
||||
|
||||
@router.post("/enterprises")
|
||||
def create_enterprise(body: dict, _: User = Depends(require_superuser)):
|
||||
ent = sync_repo.create_enterprise(body["name"], body["slug"])
|
||||
return EnterpriseOut(id=ent.id, name=ent.name, slug=ent.slug, status=ent.status).model_dump()
|
||||
|
||||
|
||||
@router.get("/enterprises/{enterprise_id}/farms")
|
||||
def list_farms(enterprise_id: str, user: User = Depends(get_current_user)):
|
||||
member = sync_repo.get_member(user.id, enterprise_id)
|
||||
if not member and not user.is_superuser:
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_FORBIDDEN")
|
||||
hubs = sync_repo.list_farm_hubs(enterprise_id)
|
||||
return [
|
||||
FarmHubOut(
|
||||
id=h.id,
|
||||
enterprise_id=h.enterprise_id,
|
||||
name=h.name,
|
||||
hub_site_id=h.hub_site_id,
|
||||
url=h.url,
|
||||
status=h.status,
|
||||
last_seen=h.last_seen,
|
||||
wesp_version=h.wesp_version,
|
||||
).model_dump()
|
||||
for h in hubs
|
||||
]
|
||||
|
||||
|
||||
@router.post("/enterprises/{enterprise_id}/session")
|
||||
def enterprise_session(enterprise_id: str, user: User = Depends(get_current_user)):
|
||||
member = sync_repo.get_member(user.id, enterprise_id)
|
||||
if not member and not user.is_superuser:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_FORBIDDEN")
|
||||
role = member.role if member else "admin"
|
||||
farm_ids = None if role == "admin" else sync_repo.list_farm_access(user.id, enterprise_id)
|
||||
token = create_access_token(
|
||||
user.id,
|
||||
user.role,
|
||||
user.is_superuser,
|
||||
enterprise_id=enterprise_id,
|
||||
enterprise_role=role,
|
||||
farm_ids=farm_ids,
|
||||
)
|
||||
return {
|
||||
"access_token": token,
|
||||
"enterprise_id": enterprise_id,
|
||||
"enterprise_role": role,
|
||||
"farm_ids": farm_ids,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/enterprises/{enterprise_id}/sync-status")
|
||||
def enterprise_sync_status(enterprise_id: str, user: User = Depends(get_current_user)):
|
||||
member = sync_repo.get_member(user.id, enterprise_id)
|
||||
if not member and not user.is_superuser:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_FORBIDDEN")
|
||||
from app.modules.sync.service import get_sync_metrics
|
||||
|
||||
return get_sync_metrics(enterprise_id)
|
||||
|
||||
|
||||
@router.get("/enterprises/{enterprise_id}/members")
|
||||
def list_members(enterprise_id: str, user: User = Depends(get_current_user)):
|
||||
member = sync_repo.get_member(user.id, enterprise_id)
|
||||
if not member or member.role != "admin":
|
||||
if not user.is_superuser:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_ADMIN_ONLY")
|
||||
from app.modules.users.repository import get_user_by_id
|
||||
|
||||
rows = sync_repo.list_members(enterprise_id)
|
||||
return [
|
||||
{
|
||||
"user_id": m.user_id,
|
||||
"role": m.role,
|
||||
"email": (get_user_by_id(m.user_id).email if get_user_by_id(m.user_id) else None),
|
||||
"farm_ids": sync_repo.list_farm_access(m.user_id, enterprise_id),
|
||||
}
|
||||
for m in rows
|
||||
]
|
||||
|
||||
|
||||
@router.post("/enterprises/{enterprise_id}/members")
|
||||
def add_enterprise_member(enterprise_id: str, body: dict, user: User = Depends(get_current_user)):
|
||||
member = sync_repo.get_member(user.id, enterprise_id)
|
||||
if not member or member.role != "admin":
|
||||
if not user.is_superuser:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_ADMIN_ONLY")
|
||||
from app.modules.users.repository import get_user_by_email
|
||||
|
||||
target = get_user_by_email(body["email"].strip().lower())
|
||||
if not target:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="USER_NOT_FOUND")
|
||||
existing = sync_repo.get_member(target.id, enterprise_id)
|
||||
if existing:
|
||||
sync_repo.set_member_role(target.id, enterprise_id, body.get("role", existing.role))
|
||||
else:
|
||||
sync_repo.add_member(target.id, enterprise_id, body.get("role", "viewer"))
|
||||
for farm_id in body.get("farm_ids") or []:
|
||||
sync_repo.grant_farm_access(target.id, farm_id)
|
||||
return {"status": "ok", "user_id": target.id}
|
||||
|
||||
|
||||
@router.post("/enterprises/{enterprise_id}/farm-access")
|
||||
def grant_farm_access(enterprise_id: str, body: dict, user: User = Depends(get_current_user)):
|
||||
member = sync_repo.get_member(user.id, enterprise_id)
|
||||
if not member or member.role != "admin":
|
||||
if not user.is_superuser:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_ADMIN_ONLY")
|
||||
sync_repo.grant_farm_access(body["user_id"], body["farm_hub_id"])
|
||||
return {"status": "ok"}
|
||||
Reference in New Issue
Block a user