Initial commit: site monorepo with API, web, and infra.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
влад
2026-07-16 10:11:54 +03:00
co-authored by Cursor
commit 016910ffb7
447 changed files with 73972 additions and 0 deletions
+155
View File
@@ -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"}