160 lines
5.8 KiB
Python
160 lines
5.8 KiB
Python
import re
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
|
|
from app.core.deps import current_principal, current_user, require_client_roles, Principal
|
|
from app.models import Organization, OrgMember, Plan, Subscription, StorageUsage, AuditLog
|
|
from app.models.enums import ClientRole
|
|
from app.schemas.common import OrgCreate
|
|
|
|
router = APIRouter(tags=["organization"])
|
|
|
|
|
|
@router.get("/me")
|
|
async def me(p: Principal = Depends(current_principal)):
|
|
"""Who am I — used by the frontend to route admins vs clients."""
|
|
return {
|
|
"id": str(p.user.id),
|
|
"name": p.user.name,
|
|
"email": p.user.email,
|
|
"email_verified": p.user.email_verified,
|
|
"is_product_admin": p.user.is_product_admin,
|
|
"product_admin_role": p.admin_role,
|
|
"client_role": p.client_role,
|
|
"has_org": p.org is not None,
|
|
"org_name": p.org.name if p.org else None,
|
|
|
|
}
|
|
|
|
|
|
def _slug(name: str) -> str:
|
|
base = re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
|
|
return base or "org"
|
|
|
|
|
|
@router.post("/organization", status_code=201)
|
|
async def create_org(body: OrgCreate, user=Depends(current_user)):
|
|
existing = await OrgMember.find_one(OrgMember.user_id == str(user.id))
|
|
if existing:
|
|
raise HTTPException(status.HTTP_409_CONFLICT, "User already belongs to an organization")
|
|
free = await Plan.find_one(Plan.name == "Free")
|
|
slug = _slug(body.name)
|
|
if await Organization.find_one(Organization.slug == slug):
|
|
slug = f"{slug}-{str(user.id)[-4:]}"
|
|
org = Organization(name=body.name, slug=slug, gstin=body.gstin, pan=body.pan,
|
|
contact_email=body.contact_email or user.email,address=body.address,
|
|
phone=body.phone, website=body.website,
|
|
plan_id=str(free.id) if free else None)
|
|
await org.insert()
|
|
await OrgMember(org_id=str(org.id), user_id=str(user.id),
|
|
role=ClientRole.admin, status="active").insert()
|
|
await StorageUsage(org_id=str(org.id),
|
|
total_bytes=(free.storage_gb if free else 5) * 1024 ** 3).insert()
|
|
if free:
|
|
await Subscription(org_id=str(org.id), plan_id=str(free.id),
|
|
plan_name=free.name).insert()
|
|
await AuditLog(org_id=str(org.id), user_id=str(user.id), user_name=user.name,
|
|
action="org.create", module="organization",
|
|
details=f"Created org {org.name}").insert()
|
|
return {"id": str(org.id), "name": org.name, "slug": org.slug}
|
|
|
|
|
|
@router.get("/organization")
|
|
async def get_org(p: Principal = Depends(current_principal)):
|
|
if not p.org:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "No organization")
|
|
plan = await Plan.get(p.org.plan_id) if p.org.plan_id else None
|
|
return {
|
|
"id": str(p.org.id), "name": p.org.name, "slug": p.org.slug,
|
|
"logo_url": p.org.logo_url, "gstin": p.org.gstin, "pan": p.org.pan,
|
|
"timezone": p.org.timezone, "language": p.org.language,
|
|
"subscription_status": p.org.subscription_status,"address": p.org.address,
|
|
"phone": p.org.phone,
|
|
"website": p.org.website,
|
|
"contact_email": p.org.contact_email,
|
|
"address":p.org.address,"country": p.org.country,
|
|
"product_count": p.org.product_count, "user_count": p.org.user_count,
|
|
"plan": {"name": plan.name, "product_limit": plan.product_limit,
|
|
"user_limit": plan.user_limit, "price": plan.price} if plan else None,
|
|
"your_role": p.client_role,
|
|
}
|
|
|
|
|
|
@router.put("/organization")
|
|
async def update_org(body: dict, p: Principal = Depends(require_client_roles(ClientRole.admin))):
|
|
allowed = {"name", "logo_url", "gstin", "pan", "address", "contact_email","country",
|
|
"phone", "website", "timezone", "language"}
|
|
for k, v in body.items():
|
|
if k in allowed:
|
|
setattr(p.org, k, v)
|
|
await p.org.save()
|
|
await AuditLog(
|
|
org_id=str(p.org.id),
|
|
user_id=str(p.user.id),
|
|
user_name=p.user.name,
|
|
user_role=p.client_role,
|
|
action="organization.update",
|
|
module="organization",
|
|
details=f"Updated organization {p.org.name}",
|
|
).insert()
|
|
return {"message": "updated"}
|
|
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
@router.get("/audit-logs")
|
|
async def org_audit_logs(
|
|
p: Principal = Depends(require_client_roles(ClientRole.admin)),
|
|
limit: int = 50,
|
|
module: Optional[str] = None,
|
|
user: Optional[str] = None,
|
|
from_date: Optional[str] = None,
|
|
to_date: Optional[str] = None,
|
|
):
|
|
query = AuditLog.find(AuditLog.org_id == str(p.org.id))
|
|
|
|
if module:
|
|
query = query.find(AuditLog.module == module)
|
|
|
|
if user:
|
|
query = query.find(AuditLog.user_name == user)
|
|
|
|
if from_date:
|
|
query = query.find(
|
|
AuditLog.created_at >= datetime.fromisoformat(from_date)
|
|
)
|
|
|
|
if to_date:
|
|
query = query.find(
|
|
AuditLog.created_at <= datetime.fromisoformat(to_date + "T23:59:59")
|
|
)
|
|
|
|
logs = await query.sort(-AuditLog.created_at).limit(limit).to_list()
|
|
|
|
return [
|
|
{
|
|
"at": l.created_at,
|
|
"user": l.user_name,
|
|
"role": l.user_role,
|
|
"action": l.action,
|
|
"module": l.module,
|
|
"details": l.details,
|
|
"ip": l.ip,
|
|
}
|
|
for l in logs
|
|
]
|
|
|
|
|
|
@router.get("/roles")
|
|
async def list_roles():
|
|
return [
|
|
{"role": "admin", "label": "Admin",
|
|
"description": "Full access within the organization: products, QRs, batches, compliance, users, billing."},
|
|
{"role": "finance", "label": "Finance",
|
|
"description": "Billing, invoices, payment methods, subscription within the org only."},
|
|
{"role": "user", "label": "User",
|
|
"description": "Create products, generate QRs, manage batches, view analytics. No billing/invites."},
|
|
]
|
|
|