Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/adapters/api/v1/activity_logs.py
2026-04-14 19:34:55 +03:30

321 lines
9.5 KiB
Python
Executable file

from fastapi import APIRouter, Depends, Request, Query, Body
from sqlalchemy.orm import Session
from typing import Optional
from datetime import datetime
from app.core.auth_dependency import get_current_user, AuthContext
from app.core.responses import success_response, ApiError
from app.core.permissions import require_business_access
from adapters.db.session import get_db
from adapters.db.repositories.activity_log_repo import ActivityLogRepository
from adapters.db.models.activity_log import ActivityLog
from app.core.cache import get_cache
router = APIRouter(prefix="/activity-logs", tags=["activity-logs"])
@router.post("/business/{business_id}/table")
@require_business_access("business_id")
def get_business_activity_logs_table(
request: Request,
business_id: int,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
body: dict = Body(...)
) -> dict:
"""دریافت لاگ‌های فعالیت یک کسب و کار برای DataTable (POST)"""
# تبدیل skip/take به page/per_page
skip = body.get("skip", 0)
take = body.get("take", 50)
page = (skip // take) + 1 if take > 0 else 1
per_page = take
# استخراج فیلترها از body
category = body.get("category")
entity_type = body.get("entity_type")
start_date_str = body.get("start_date")
end_date_str = body.get("end_date")
start_date = None
if start_date_str:
try:
start_date = datetime.fromisoformat(start_date_str.replace('Z', '+00:00'))
except:
pass
end_date = None
if end_date_str:
try:
end_date = datetime.fromisoformat(end_date_str.replace('Z', '+00:00'))
except:
pass
# استخراج search و filters
search = body.get("search")
search_fields = body.get("search_fields", [])
filters = body.get("filters", [])
# اعمال فیلترهای اضافی از filters
for filter_item in filters:
if isinstance(filter_item, dict):
prop = filter_item.get("property")
value = filter_item.get("value")
if prop == "category" and value:
category = value
elif prop == "entity_type" and value:
entity_type = value
repo = ActivityLogRepository(db)
offset = (page - 1) * per_page
logs = repo.get_by_business(
business_id=business_id,
category=category,
entity_type=entity_type,
start_date=start_date,
end_date=end_date,
limit=per_page,
offset=offset
)
# شمارش کل
total = repo.count_by_business(
business_id=business_id,
category=category,
entity_type=entity_type,
start_date=start_date,
end_date=end_date
)
# تبدیل به دیکشنری
logs_data = []
for log in logs:
logs_data.append({
"id": log.id,
"user_id": log.user_id,
"user_name": f"{log.user.first_name or ''} {log.user.last_name or ''}".strip() if log.user else None,
"category": log.category,
"action": log.action,
"entity_type": log.entity_type,
"entity_id": log.entity_id,
"description": log.description,
"before_data": log.before_data,
"after_data": log.after_data,
"extra_info": log.extra_info,
"created_at": log.created_at.isoformat()
})
# فیلتر بر اساس search اگر وجود داشته باشد
if search and search_fields:
search_lower = search.lower()
logs_data = [
log for log in logs_data
if any(
search_lower in str(log.get(field, "")).lower()
for field in search_fields
)
]
total = len(logs_data) # در صورت جستجو، total را به‌روزرسانی می‌کنیم
return success_response(
data={
"items": logs_data,
"page": page,
"limit": per_page,
"per_page": per_page, # برای سازگاری
"total": total,
"total_pages": (total + per_page - 1) // per_page if total > 0 else 0
},
request=request
)
@router.get("/business/{business_id}")
@require_business_access("business_id")
def get_business_activity_logs(
request: Request,
business_id: int,
category: Optional[str] = Query(None, description="دسته فعالیت"),
entity_type: Optional[str] = Query(None, description="نوع موجودیت"),
start_date: Optional[datetime] = Query(None, description="تاریخ شروع"),
end_date: Optional[datetime] = Query(None, description="تاریخ پایان"),
page: int = Query(1, ge=1, description="شماره صفحه"),
per_page: int = Query(50, ge=1, le=200, description="تعداد در هر صفحه"),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
"""دریافت لاگ‌های فعالیت یک کسب و کار"""
cache = get_cache()
cache_key = None
if cache.enabled:
import json, hashlib
key_payload = {
"business_id": business_id,
"category": category,
"entity_type": entity_type,
"start_date": start_date.isoformat() if start_date else None,
"end_date": end_date.isoformat() if end_date else None,
"page": page,
"per_page": per_page,
}
key_str = json.dumps(key_payload, sort_keys=True, ensure_ascii=False)
key_hash = hashlib.sha256(key_str.encode("utf-8")).hexdigest()[:16]
cache_key = f"activity_logs:{key_hash}"
cached = cache.get(cache_key)
if cached is not None:
return success_response(data=cached, request=request)
repo = ActivityLogRepository(db)
offset = (page - 1) * per_page
logs = repo.get_by_business(
business_id=business_id,
category=category,
entity_type=entity_type,
start_date=start_date,
end_date=end_date,
limit=per_page,
offset=offset
)
# شمارش کل
total = repo.count_by_business(
business_id=business_id,
category=category,
entity_type=entity_type,
start_date=start_date,
end_date=end_date
)
# تبدیل به دیکشنری
logs_data = []
for log in logs:
logs_data.append({
"id": log.id,
"user_id": log.user_id,
"user_name": f"{log.user.first_name or ''} {log.user.last_name or ''}".strip() if log.user else None,
"category": log.category,
"action": log.action,
"entity_type": log.entity_type,
"entity_id": log.entity_id,
"description": log.description,
"before_data": log.before_data,
"after_data": log.after_data,
"extra_info": log.extra_info,
"created_at": log.created_at.isoformat()
})
response_data = {
"items": logs_data,
"page": page,
"per_page": per_page,
"total": total,
"total_pages": (total + per_page - 1) // per_page if total > 0 else 0
}
if cache.enabled and cache_key:
# لاگ‌ها نسبتا سریع تغییر می‌کنند → TTL کوتاه
cache.set(cache_key, response_data, ttl=30)
return success_response(
data=response_data,
request=request
)
@router.get("/entity/{entity_type}/{entity_id}")
def get_entity_activity_logs(
request: Request,
entity_type: str,
entity_id: str, # تغییر به str برای پشتیبانی از UUID
business_id: Optional[int] = Query(None, description="شناسه کسب و کار (اختیاری)"),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
"""دریافت تاریخچه تغییرات یک موجودیت"""
# اگر business_id داده شده، بررسی دسترسی
if business_id:
if not ctx.can_access_business(business_id):
raise ApiError("FORBIDDEN", f"No access to business {business_id}", http_status=403)
repo = ActivityLogRepository(db)
logs = repo.get_by_entity(
entity_type=entity_type,
entity_id=entity_id, # حالا می‌تواند UUID (str) یا int (به صورت str) باشد
business_id=business_id
)
logs_data = []
for log in logs:
# بررسی دسترسی به business_id لاگ
if log.business_id and not ctx.can_access_business(log.business_id):
continue # skip لاگ‌هایی که دسترسی نداریم
logs_data.append({
"id": log.id,
"user_id": log.user_id,
"user_name": f"{log.user.first_name or ''} {log.user.last_name or ''}".strip() if log.user else None,
"business_id": log.business_id,
"action": log.action,
"description": log.description,
"before_data": log.before_data,
"after_data": log.after_data,
"created_at": log.created_at.isoformat()
})
return success_response(data={"items": logs_data}, request=request)
@router.get("/user/me")
def get_my_activity_logs(
request: Request,
business_id: Optional[int] = Query(None, description="شناسه کسب و کار (اختیاری)"),
page: int = Query(1, ge=1, description="شماره صفحه"),
per_page: int = Query(50, ge=1, le=200, description="تعداد در هر صفحه"),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
"""دریافت لاگ‌های فعالیت کاربر جاری"""
# اگر business_id داده شده، بررسی دسترسی
if business_id:
if not ctx.can_access_business(business_id):
raise ApiError("FORBIDDEN", f"No access to business {business_id}", http_status=403)
repo = ActivityLogRepository(db)
offset = (page - 1) * per_page
logs = repo.get_by_user(
user_id=ctx.user.id,
business_id=business_id,
limit=per_page,
offset=offset
)
logs_data = []
for log in logs:
# بررسی دسترسی به business_id لاگ
if log.business_id and not ctx.can_access_business(log.business_id):
continue # skip لاگ‌هایی که دسترسی نداریم
logs_data.append({
"id": log.id,
"business_id": log.business_id,
"business_name": log.business.name if log.business else None,
"category": log.category,
"action": log.action,
"entity_type": log.entity_type,
"entity_id": log.entity_id,
"description": log.description,
"created_at": log.created_at.isoformat()
})
return success_response(
data={
"items": logs_data,
"page": page,
"per_page": per_page
},
request=request
)