fix: add UTC timezone indicator to all datetime serialization

Database stores times in UTC but serialized without timezone info,
causing frontend to misinterpret as local time. Now all datetime
fields include 'Z' suffix to indicate UTC, enabling proper timezone
conversion in the browser.

- Add UTCDatetimeBaseModel base class for Pydantic schemas
- Update model to_dict() methods to append 'Z' suffix
- Affects: tasks, users, sessions, audit logs, translations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-14 15:48:17 +08:00
parent 7233e9cb7b
commit ee49751c38
9 changed files with 88 additions and 37 deletions

View File

@@ -6,6 +6,8 @@ from typing import Optional
from datetime import datetime
from pydantic import BaseModel, Field
from app.schemas.base import UTCDatetimeBaseModel
class LoginRequest(BaseModel):
"""Login request schema"""
@@ -58,7 +60,7 @@ class TokenData(BaseModel):
session_id: Optional[int] = None
class UserResponse(BaseModel):
class UserResponse(UTCDatetimeBaseModel):
"""User response schema"""
id: int
email: str
@@ -66,9 +68,3 @@ class UserResponse(BaseModel):
created_at: Optional[datetime] = None
last_login: Optional[datetime] = None
is_active: bool = True
class Config:
from_attributes = True
json_encoders = {
datetime: lambda v: v.isoformat() if v else None
}

View File

@@ -0,0 +1,38 @@
"""
Tool_OCR - Base Schema with UTC Datetime Serialization
All datetime fields stored in the database are in UTC (using datetime.utcnow()).
This base class ensures proper serialization with 'Z' suffix to indicate UTC,
allowing frontend to correctly convert to local timezone.
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict
def serialize_datetime_utc(dt: datetime) -> str:
"""Serialize datetime to ISO format with UTC indicator (Z suffix).
Database stores all times in UTC without timezone info (naive datetime).
This serializer adds the 'Z' suffix to indicate UTC, enabling proper
timezone conversion in the frontend.
"""
if dt is None:
return None
# Ensure ISO format with 'Z' suffix for UTC
return dt.isoformat() + 'Z'
class UTCDatetimeBaseModel(BaseModel):
"""Base model that serializes all datetime fields with UTC indicator.
Use this as base class for any response schema containing datetime fields
to ensure consistent timezone handling across the API.
"""
model_config = ConfigDict(
from_attributes=True,
json_encoders={
datetime: serialize_datetime_utc
}
)

View File

@@ -7,6 +7,8 @@ from datetime import datetime
from pydantic import BaseModel, Field
from enum import Enum
from app.schemas.base import UTCDatetimeBaseModel
class TaskStatusEnum(str, Enum):
"""Task status enumeration"""
@@ -146,7 +148,7 @@ class TaskUpdate(BaseModel):
result_pdf_path: Optional[str] = None
class TaskFileResponse(BaseModel):
class TaskFileResponse(UTCDatetimeBaseModel):
"""Task file response schema"""
id: int
original_name: Optional[str] = None
@@ -156,11 +158,8 @@ class TaskFileResponse(BaseModel):
file_hash: Optional[str] = None
created_at: datetime
class Config:
from_attributes = True
class TaskResponse(BaseModel):
class TaskResponse(UTCDatetimeBaseModel):
"""Task response schema"""
id: int
user_id: int
@@ -178,9 +177,6 @@ class TaskResponse(BaseModel):
completed_at: Optional[datetime] = None
file_deleted: bool = False
class Config:
from_attributes = True
class TaskDetailResponse(TaskResponse):
"""Detailed task response with files"""

View File

@@ -9,6 +9,8 @@ from pydantic import BaseModel, Field
from enum import Enum
from dataclasses import dataclass
from app.schemas.base import UTCDatetimeBaseModel
class TranslationStatusEnum(str, Enum):
"""Translation job status enumeration"""
@@ -54,7 +56,7 @@ class TranslationProgress(BaseModel):
percentage: float = Field(default=0.0, description="Progress percentage (0-100)")
class TranslationStatusResponse(BaseModel):
class TranslationStatusResponse(UTCDatetimeBaseModel):
"""Response model for translation status query"""
task_id: str = Field(..., description="Task ID")
status: TranslationStatusEnum = Field(..., description="Current translation status")
@@ -89,7 +91,7 @@ class TranslationStatistics(BaseModel):
total_tokens: int = Field(default=0, description="Total API tokens used")
class TranslationResultResponse(BaseModel):
class TranslationResultResponse(UTCDatetimeBaseModel):
"""Response model for translation result"""
schema_version: str = Field(default="1.0.0", description="Schema version")
source_document: str = Field(..., description="Source document filename")
@@ -104,7 +106,7 @@ class TranslationResultResponse(BaseModel):
)
class TranslationListItem(BaseModel):
class TranslationListItem(UTCDatetimeBaseModel):
"""Item in translation list response"""
target_lang: str = Field(..., description="Target language")
translated_at: datetime = Field(..., description="Translation timestamp")