Improve the frontend 5
This commit is contained in:
@@ -228,4 +228,142 @@ class ExternalServiceClient(BaseServiceClient):
|
||||
return result
|
||||
else:
|
||||
logger.warning("No current traffic data available")
|
||||
return None
|
||||
|
||||
# ================================================================
|
||||
# CALENDAR DATA (School Calendars and Hyperlocal Information)
|
||||
# ================================================================
|
||||
|
||||
async def get_tenant_location_context(
|
||||
self,
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get tenant location context including school calendar assignment
|
||||
"""
|
||||
logger.info("Fetching tenant location context", tenant_id=tenant_id)
|
||||
|
||||
result = await self._make_request(
|
||||
"GET",
|
||||
f"external/tenants/{tenant_id}/location-context",
|
||||
tenant_id=tenant_id,
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info("Successfully fetched tenant location context", tenant_id=tenant_id)
|
||||
return result
|
||||
else:
|
||||
logger.info("No location context found for tenant", tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def get_school_calendar(
|
||||
self,
|
||||
calendar_id: str,
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get school calendar details by ID
|
||||
"""
|
||||
logger.info("Fetching school calendar", calendar_id=calendar_id, tenant_id=tenant_id)
|
||||
|
||||
result = await self._make_request(
|
||||
"GET",
|
||||
f"external/operations/school-calendars/{calendar_id}",
|
||||
tenant_id=tenant_id,
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info("Successfully fetched school calendar", calendar_id=calendar_id)
|
||||
return result
|
||||
else:
|
||||
logger.warning("School calendar not found", calendar_id=calendar_id)
|
||||
return None
|
||||
|
||||
async def check_is_school_holiday(
|
||||
self,
|
||||
calendar_id: str,
|
||||
check_date: str,
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Check if a specific date is a school holiday
|
||||
|
||||
Args:
|
||||
calendar_id: School calendar UUID
|
||||
check_date: Date to check in ISO format (YYYY-MM-DD)
|
||||
tenant_id: Tenant ID for auth
|
||||
|
||||
Returns:
|
||||
Dict with is_holiday, holiday_name, etc.
|
||||
"""
|
||||
params = {"check_date": check_date}
|
||||
|
||||
logger.debug(
|
||||
"Checking school holiday status",
|
||||
calendar_id=calendar_id,
|
||||
date=check_date,
|
||||
tenant_id=tenant_id
|
||||
)
|
||||
|
||||
result = await self._make_request(
|
||||
"GET",
|
||||
f"external/operations/school-calendars/{calendar_id}/is-holiday",
|
||||
tenant_id=tenant_id,
|
||||
params=params,
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def get_city_school_calendars(
|
||||
self,
|
||||
city_id: str,
|
||||
tenant_id: str,
|
||||
school_type: Optional[str] = None,
|
||||
academic_year: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get all school calendars for a city with optional filters
|
||||
|
||||
Args:
|
||||
city_id: City ID (e.g., "madrid")
|
||||
tenant_id: Tenant ID for auth
|
||||
school_type: Optional filter by school type
|
||||
academic_year: Optional filter by academic year
|
||||
|
||||
Returns:
|
||||
Dict with calendars list and total count
|
||||
"""
|
||||
params = {}
|
||||
if school_type:
|
||||
params["school_type"] = school_type
|
||||
if academic_year:
|
||||
params["academic_year"] = academic_year
|
||||
|
||||
logger.info(
|
||||
"Fetching school calendars for city",
|
||||
city_id=city_id,
|
||||
tenant_id=tenant_id,
|
||||
filters=params
|
||||
)
|
||||
|
||||
result = await self._make_request(
|
||||
"GET",
|
||||
f"external/operations/cities/{city_id}/school-calendars",
|
||||
tenant_id=tenant_id,
|
||||
params=params if params else None,
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info(
|
||||
"Successfully fetched school calendars",
|
||||
city_id=city_id,
|
||||
total=result.get("total", 0)
|
||||
)
|
||||
return result
|
||||
else:
|
||||
logger.warning("No school calendars found for city", city_id=city_id)
|
||||
return None
|
||||
83
shared/models/audit_log_schemas.py
Normal file
83
shared/models/audit_log_schemas.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Shared Pydantic schemas for audit log API responses.
|
||||
Used across all services for consistent audit log retrieval.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AuditLogResponse(BaseModel):
|
||||
"""Response schema for audit log entries"""
|
||||
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
user_id: Optional[UUID] = None
|
||||
service_name: str
|
||||
action: str
|
||||
resource_type: str
|
||||
resource_id: Optional[str] = None
|
||||
severity: str # low, medium, high, critical
|
||||
description: str
|
||||
changes: Optional[Dict[str, Any]] = None
|
||||
audit_metadata: Optional[Dict[str, Any]] = None
|
||||
endpoint: Optional[str] = None
|
||||
method: Optional[str] = None # HTTP method
|
||||
ip_address: Optional[str] = None
|
||||
user_agent: Optional[str] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
json_encoders = {
|
||||
datetime: lambda v: v.isoformat(),
|
||||
UUID: lambda v: str(v)
|
||||
}
|
||||
|
||||
|
||||
class AuditLogFilters(BaseModel):
|
||||
"""Query parameters for filtering audit logs"""
|
||||
|
||||
start_date: Optional[datetime] = Field(None, description="Filter logs from this date")
|
||||
end_date: Optional[datetime] = Field(None, description="Filter logs until this date")
|
||||
user_id: Optional[UUID] = Field(None, description="Filter by user ID")
|
||||
action: Optional[str] = Field(None, description="Filter by action type")
|
||||
resource_type: Optional[str] = Field(None, description="Filter by resource type")
|
||||
severity: Optional[str] = Field(None, description="Filter by severity level")
|
||||
search: Optional[str] = Field(None, description="Search in description field")
|
||||
limit: int = Field(100, ge=1, le=1000, description="Number of records to return")
|
||||
offset: int = Field(0, ge=0, description="Number of records to skip")
|
||||
|
||||
|
||||
class AuditLogListResponse(BaseModel):
|
||||
"""Paginated response for audit log listings"""
|
||||
|
||||
items: list[AuditLogResponse]
|
||||
total: int
|
||||
limit: int
|
||||
offset: int
|
||||
has_more: bool
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
datetime: lambda v: v.isoformat(),
|
||||
UUID: lambda v: str(v)
|
||||
}
|
||||
|
||||
|
||||
class AuditLogStatsResponse(BaseModel):
|
||||
"""Statistics about audit logs"""
|
||||
|
||||
total_events: int
|
||||
events_by_action: Dict[str, int]
|
||||
events_by_severity: Dict[str, int]
|
||||
events_by_resource_type: Dict[str, int]
|
||||
date_range: Dict[str, Optional[datetime]]
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
datetime: lambda v: v.isoformat()
|
||||
}
|
||||
Reference in New Issue
Block a user