diff --git a/modules/connectors/connectorDbJson.py b/modules/connectors/connectorDbJson.py index 9ad73e8c..0b44e6df 100644 --- a/modules/connectors/connectorDbJson.py +++ b/modules/connectors/connectorDbJson.py @@ -7,7 +7,7 @@ from pydantic import BaseModel import threading import time -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp logger = logging.getLogger(__name__) diff --git a/modules/connectors/connectorDbPostgre.py b/modules/connectors/connectorDbPostgre.py index cfc37b73..c9206c8d 100644 --- a/modules/connectors/connectorDbPostgre.py +++ b/modules/connectors/connectorDbPostgre.py @@ -6,7 +6,7 @@ import uuid from pydantic import BaseModel, Field import threading -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp from modules.shared.configuration import APP_CONFIG logger = logging.getLogger(__name__) diff --git a/modules/datamodels/datamodelChat.py b/modules/datamodels/datamodelChat.py index 9acbf464..c748c44a 100644 --- a/modules/datamodels/datamodelChat.py +++ b/modules/datamodels/datamodelChat.py @@ -4,7 +4,7 @@ from typing import List, Dict, Any, Optional from enum import Enum from pydantic import BaseModel, Field from modules.shared.attributeUtils import registerModelLabels -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp import uuid diff --git a/modules/datamodels/datamodelFiles.py b/modules/datamodels/datamodelFiles.py index e1f802b7..32e8d445 100644 --- a/modules/datamodels/datamodelFiles.py +++ b/modules/datamodels/datamodelFiles.py @@ -3,7 +3,7 @@ from typing import Dict, Any, Optional, Union from pydantic import BaseModel, Field from modules.shared.attributeUtils import registerModelLabels -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp import uuid import base64 diff --git a/modules/datamodels/datamodelSecurity.py b/modules/datamodels/datamodelSecurity.py index 42b9a1ad..e5a1e8a4 100644 --- a/modules/datamodels/datamodelSecurity.py +++ b/modules/datamodels/datamodelSecurity.py @@ -3,7 +3,7 @@ from typing import Optional from pydantic import BaseModel, Field from modules.shared.attributeUtils import registerModelLabels -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp from .datamodelUam import AuthAuthority from enum import Enum import uuid diff --git a/modules/datamodels/datamodelUam.py b/modules/datamodels/datamodelUam.py index 0bf71fa9..a889b4ae 100644 --- a/modules/datamodels/datamodelUam.py +++ b/modules/datamodels/datamodelUam.py @@ -5,7 +5,7 @@ from typing import Optional from enum import Enum from pydantic import BaseModel, Field, EmailStr from modules.shared.attributeUtils import registerModelLabels -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp class AuthAuthority(str, Enum): diff --git a/modules/datamodels/datamodelVoice.py b/modules/datamodels/datamodelVoice.py index c6cd5ddd..1ab47f15 100644 --- a/modules/datamodels/datamodelVoice.py +++ b/modules/datamodels/datamodelVoice.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, Field from modules.shared.attributeUtils import registerModelLabels -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp import uuid diff --git a/modules/interfaces/interfaceDbAppObjects.py b/modules/interfaces/interfaceDbAppObjects.py index 18fb34bf..26ded39d 100644 --- a/modules/interfaces/interfaceDbAppObjects.py +++ b/modules/interfaces/interfaceDbAppObjects.py @@ -11,7 +11,7 @@ import uuid from modules.connectors.connectorDbPostgre import DatabaseConnector from modules.shared.configuration import APP_CONFIG -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp, parseTimestamp from modules.interfaces.interfaceDbAppAccess import AppAccess from modules.datamodels.datamodelUam import ( User, @@ -1019,7 +1019,7 @@ class AppObjects: return None # Sort by expiration date and get the latest (most recent expiration) - tokens.sort(key=lambda x: x.get("expiresAt", 0), reverse=True) + tokens.sort(key=lambda x: parseTimestamp(x.get("expiresAt"), default=0), reverse=True) latest_token = Token(**tokens[0]) # No auto-refresh here. Callers should use a higher-level service to refresh when needed. @@ -1170,10 +1170,8 @@ class AppObjects: all_tokens = self.db.getRecordset(Token, recordFilter={}) for token_data in all_tokens: - if ( - token_data.get("expiresAt") - and token_data.get("expiresAt") < current_time - ): + expiresAt = parseTimestamp(token_data.get("expiresAt")) + if expiresAt and expiresAt < current_time: # Token is expired, delete it self.db.recordDelete(Token, token_data["id"]) cleaned_count += 1 diff --git a/modules/interfaces/interfaceDbChatObjects.py b/modules/interfaces/interfaceDbChatObjects.py index 5498524d..8bb86fb1 100644 --- a/modules/interfaces/interfaceDbChatObjects.py +++ b/modules/interfaces/interfaceDbChatObjects.py @@ -27,7 +27,7 @@ from modules.datamodels.datamodelUam import User # DYNAMIC PART: Connectors to the Interface from modules.connectors.connectorDbPostgre import DatabaseConnector -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp, parseTimestamp from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResult # Basic Configurations @@ -995,7 +995,7 @@ class ChatObjects: # Apply default sorting by timestamp if no sort specified if pagination is None or not pagination.sort: - logDicts.sort(key=lambda x: float(x.get("timestamp", 0))) + logDicts.sort(key=lambda x: parseTimestamp(x.get("timestamp"), default=0)) # Apply filtering (if filters provided) if pagination and pagination.filters: @@ -1143,15 +1143,15 @@ class ChatObjects: messages = self.db.getRecordset(ChatMessage, recordFilter={"workflowId": workflowId}) for msg in messages: # Apply timestamp filtering in Python - msg_timestamp = msg.get("publishedAt", getUtcTimestamp()) - if afterTimestamp is not None and msg_timestamp <= afterTimestamp: + msgTimestamp = parseTimestamp(msg.get("publishedAt"), default=getUtcTimestamp()) + if afterTimestamp is not None and msgTimestamp <= afterTimestamp: continue # Load documents for each message documents = self.getDocuments(msg["id"]) # Create ChatMessage object with loaded documents - chat_message = ChatMessage( + chatMessage = ChatMessage( id=msg["id"], workflowId=msg["workflowId"], parentMessageId=msg.get("parentMessageId"), @@ -1176,23 +1176,23 @@ class ChatObjects: # Use publishedAt as the timestamp for chronological ordering items.append({ "type": "message", - "createdAt": msg_timestamp, - "item": chat_message + "createdAt": msgTimestamp, + "item": chatMessage }) # Get logs logs = self.db.getRecordset(ChatLog, recordFilter={"workflowId": workflowId}) for log in logs: # Apply timestamp filtering in Python - log_timestamp = log.get("timestamp", getUtcTimestamp()) - if afterTimestamp is not None and log_timestamp <= afterTimestamp: + logTimestamp = parseTimestamp(log.get("timestamp"), default=getUtcTimestamp()) + if afterTimestamp is not None and logTimestamp <= afterTimestamp: continue - chat_log = ChatLog(**log) + chatLog = ChatLog(**log) items.append({ "type": "log", - "createdAt": log_timestamp, - "item": chat_log + "createdAt": logTimestamp, + "item": chatLog }) # Get stats list @@ -1210,7 +1210,7 @@ class ChatObjects: }) # Sort all items by createdAt timestamp for chronological order - items.sort(key=lambda x: x["createdAt"]) + items.sort(key=lambda x: parseTimestamp(x.get("createdAt"), default=0)) return {"items": items} diff --git a/modules/interfaces/interfaceDbComponentObjects.py b/modules/interfaces/interfaceDbComponentObjects.py index fba237da..10e2d85d 100644 --- a/modules/interfaces/interfaceDbComponentObjects.py +++ b/modules/interfaces/interfaceDbComponentObjects.py @@ -17,7 +17,7 @@ from modules.datamodels.datamodelUtils import Prompt from modules.datamodels.datamodelVoice import VoiceSettings from modules.datamodels.datamodelUam import User, Mandate from modules.shared.configuration import APP_CONFIG -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResult logger = logging.getLogger(__name__) diff --git a/modules/interfaces/interfaceVoiceObjects.py b/modules/interfaces/interfaceVoiceObjects.py index 66b51765..87cb1413 100644 --- a/modules/interfaces/interfaceVoiceObjects.py +++ b/modules/interfaces/interfaceVoiceObjects.py @@ -10,7 +10,7 @@ from typing import Dict, Any, Optional, List from modules.connectors.connectorVoiceGoogle import ConnectorGoogleSpeech from modules.datamodels.datamodelVoice import VoiceSettings from modules.datamodels.datamodelUam import User -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp logger = logging.getLogger(__name__) diff --git a/modules/routes/routeDataConnections.py b/modules/routes/routeDataConnections.py index 684b95d2..c1bbd034 100644 --- a/modules/routes/routeDataConnections.py +++ b/modules/routes/routeDataConnections.py @@ -18,7 +18,7 @@ from modules.datamodels.datamodelUam import User, UserConnection, AuthAuthority, from modules.datamodels.datamodelSecurity import Token from modules.security.auth import getCurrentUser, limiter from modules.interfaces.interfaceDbAppObjects import getInterface -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp, parseTimestamp # Configure logger logger = logging.getLogger(__name__) @@ -51,7 +51,7 @@ def getTokenStatusForConnection(interface, connectionId: str) -> tuple[str, Opti latestCreatedAt = 0 for tokenData in tokens: - createdAt = tokenData.get("createdAt", 0) + createdAt = parseTimestamp(tokenData.get("createdAt"), default=0) if createdAt > latestCreatedAt: latestCreatedAt = createdAt latestToken = tokenData @@ -60,7 +60,7 @@ def getTokenStatusForConnection(interface, connectionId: str) -> tuple[str, Opti return "none", None # Check if token is expired - expiresAt = latestToken.get("expiresAt") + expiresAt = parseTimestamp(latestToken.get("expiresAt")) if not expiresAt: return "none", None diff --git a/modules/routes/routeDataUsers.py b/modules/routes/routeDataUsers.py index b97ed721..2f219b5c 100644 --- a/modules/routes/routeDataUsers.py +++ b/modules/routes/routeDataUsers.py @@ -126,13 +126,24 @@ async def get_user( async def create_user( request: Request, user_data: User = Body(...), + password: Optional[str] = Body(None, embed=True), currentUser: User = Depends(getCurrentUser) ) -> User: """Create a new user""" appInterface = interfaceDbAppObjects.getInterface(currentUser) - # Create user - newUser = appInterface.createUser(user_data) + # Extract fields from User model and call createUser with individual parameters + from modules.datamodels.datamodelUam import AuthAuthority + newUser = appInterface.createUser( + username=user_data.username, + password=password, + email=user_data.email, + fullName=user_data.fullName, + language=user_data.language, + enabled=user_data.enabled, + privilege=user_data.privilege, + authenticationAuthority=user_data.authenticationAuthority + ) return newUser diff --git a/modules/routes/routeSecurityGoogle.py b/modules/routes/routeSecurityGoogle.py index de3c3857..bb8840da 100644 --- a/modules/routes/routeSecurityGoogle.py +++ b/modules/routes/routeSecurityGoogle.py @@ -15,7 +15,7 @@ from modules.interfaces.interfaceDbAppObjects import getInterface, getRootInterf from modules.datamodels.datamodelUam import AuthAuthority, User, ConnectionStatus, UserConnection from modules.security.auth import getCurrentUser, limiter from modules.security.jwtService import createAccessToken, setAccessTokenCookie, createRefreshToken, setRefreshTokenCookie -from modules.shared.timezoneUtils import createExpirationTimestamp, getUtcTimestamp +from modules.shared.timeUtils import createExpirationTimestamp, getUtcTimestamp, parseTimestamp # Configure logger logger = logging.getLogger(__name__) @@ -264,7 +264,7 @@ async def auth_callback(code: str, state: str, request: Request, response: Respo }) if existing_tokens: # Use most recent by createdAt - existing_tokens.sort(key=lambda x: x.get("createdAt", 0), reverse=True) + existing_tokens.sort(key=lambda x: parseTimestamp(x.get("createdAt"), default=0), reverse=True) token_response["refresh_token"] = existing_tokens[0].get("tokenRefresh", "") if not token_response.get("refresh_token") and user_id: existing_access_tokens = rootInterface.db.getRecordset(Token, recordFilter={ @@ -273,7 +273,7 @@ async def auth_callback(code: str, state: str, request: Request, response: Respo "authority": AuthAuthority.GOOGLE }) if existing_access_tokens: - existing_access_tokens.sort(key=lambda x: x.get("createdAt", 0), reverse=True) + existing_access_tokens.sort(key=lambda x: parseTimestamp(x.get("createdAt"), default=0), reverse=True) token_response["refresh_token"] = existing_access_tokens[0].get("tokenRefresh", "") except Exception: # Non-fatal; continue without refresh token @@ -748,19 +748,21 @@ async def refresh_token( raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to refresh token") # Update the connection status and timing - google_connection.expiresAt = float(current_token.expiresAt) if current_token.expiresAt else google_connection.expiresAt + expiresAtValue = parseTimestamp(current_token.expiresAt) + google_connection.expiresAt = expiresAtValue if expiresAtValue else google_connection.expiresAt google_connection.lastChecked = getUtcTimestamp() google_connection.status = ConnectionStatus.ACTIVE appInterface.db.recordModify(UserConnection, google_connection.id, google_connection.model_dump()) # Calculate time until expiration - current_time = getUtcTimestamp() - expires_in = int(current_token.expiresAt - current_time) if current_token.expiresAt else 0 + currentTime = getUtcTimestamp() + expiresAt = parseTimestamp(current_token.expiresAt) + expiresIn = int(expiresAt - currentTime) if expiresAt else 0 return { "message": "Token refreshed successfully", - "expires_at": current_token.expiresAt, - "expires_in_seconds": expires_in + "expires_at": expiresAt, + "expires_in_seconds": expiresIn } except HTTPException: diff --git a/modules/routes/routeSecurityMsft.py b/modules/routes/routeSecurityMsft.py index 3d5aa1fb..b72f4fa3 100644 --- a/modules/routes/routeSecurityMsft.py +++ b/modules/routes/routeSecurityMsft.py @@ -16,7 +16,7 @@ from modules.datamodels.datamodelUam import AuthAuthority, User, ConnectionStatu from modules.datamodels.datamodelSecurity import Token from modules.security.auth import getCurrentUser, limiter from modules.security.jwtService import createAccessToken, setAccessTokenCookie, createRefreshToken, setRefreshTokenCookie -from modules.shared.timezoneUtils import createExpirationTimestamp, getUtcTimestamp +from modules.shared.timeUtils import createExpirationTimestamp, getUtcTimestamp, parseTimestamp # Configure logger logger = logging.getLogger(__name__) @@ -639,7 +639,9 @@ async def refresh_token( appInterface.saveConnectionToken(refreshedToken) # Update the connection's expiration time - msft_connection.expiresAt = float(refreshedToken.expiresAt) + expiresAtValue = parseTimestamp(refreshedToken.expiresAt) + if expiresAtValue: + msft_connection.expiresAt = expiresAtValue msft_connection.lastChecked = getUtcTimestamp() msft_connection.status = ConnectionStatus.ACTIVE @@ -647,12 +649,13 @@ async def refresh_token( appInterface.db.recordModify(UserConnection, msft_connection.id, msft_connection.model_dump()) # Calculate time until expiration - current_time = getUtcTimestamp() - expiresIn = int(refreshedToken.expiresAt - current_time) + currentTime = getUtcTimestamp() + expiresAt = parseTimestamp(refreshedToken.expiresAt) + expiresIn = int(expiresAt - currentTime) if expiresAt else 0 return { "message": "Token refreshed successfully", - "expires_at": refreshedToken.expiresAt, + "expires_at": expiresAt, "expires_in_seconds": expiresIn } else: diff --git a/modules/security/jwtService.py b/modules/security/jwtService.py index ab5a9392..6f0b0c9d 100644 --- a/modules/security/jwtService.py +++ b/modules/security/jwtService.py @@ -9,7 +9,7 @@ from fastapi import Response from jose import jwt from modules.shared.configuration import APP_CONFIG -from modules.shared.timezoneUtils import getUtcNow +from modules.shared.timeUtils import getUtcNow # Config SECRET_KEY = APP_CONFIG.get("APP_JWT_KEY_SECRET") diff --git a/modules/security/tokenManager.py b/modules/security/tokenManager.py index 42c4a7cf..b3c85c53 100644 --- a/modules/security/tokenManager.py +++ b/modules/security/tokenManager.py @@ -10,7 +10,7 @@ from typing import Optional, Dict, Any, Callable from modules.datamodels.datamodelSecurity import Token from modules.datamodels.datamodelUam import AuthAuthority from modules.shared.configuration import APP_CONFIG -from modules.shared.timezoneUtils import getUtcTimestamp, createExpirationTimestamp +from modules.shared.timeUtils import getUtcTimestamp, createExpirationTimestamp, parseTimestamp logger = logging.getLogger(__name__) @@ -167,7 +167,7 @@ class TokenManager: # Only allow a new refresh if at least 10 minutes passed since the token was created/refreshed try: nowTs = getUtcTimestamp() - createdTs = float(oldToken.createdAt) if oldToken.createdAt is not None else 0.0 + createdTs = parseTimestamp(oldToken.createdAt, default=0.0) secondsSinceLastRefresh = nowTs - createdTs if secondsSinceLastRefresh < 10 * 60: logger.info( diff --git a/modules/security/tokenRefreshMiddleware.py b/modules/security/tokenRefreshMiddleware.py index b7131a40..c854d7d4 100644 --- a/modules/security/tokenRefreshMiddleware.py +++ b/modules/security/tokenRefreshMiddleware.py @@ -11,7 +11,7 @@ from starlette.middleware.base import BaseHTTPMiddleware from typing import Callable import asyncio from modules.security.tokenRefreshService import token_refresh_service -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp logger = logging.getLogger(__name__) diff --git a/modules/security/tokenRefreshService.py b/modules/security/tokenRefreshService.py index 24a99e3b..97ff0cd6 100644 --- a/modules/security/tokenRefreshService.py +++ b/modules/security/tokenRefreshService.py @@ -9,7 +9,7 @@ to ensure users don't experience token expiration issues. import logging from typing import Dict, Any from modules.datamodels.datamodelUam import UserConnection, AuthAuthority -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp from modules.shared.auditLogger import audit_logger logger = logging.getLogger(__name__) diff --git a/modules/services/serviceUtils/mainServiceUtils.py b/modules/services/serviceUtils/mainServiceUtils.py index 86a3cdd7..849cc3ef 100644 --- a/modules/services/serviceUtils/mainServiceUtils.py +++ b/modules/services/serviceUtils/mainServiceUtils.py @@ -7,7 +7,7 @@ import logging from typing import Any, Optional, Dict, Callable, List from modules.shared.configuration import APP_CONFIG from modules.shared.eventManagement import eventManager -from modules.shared.timezoneUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp from modules.shared import jsonUtils logger = logging.getLogger(__name__) diff --git a/modules/shared/debugLogger.py b/modules/shared/debugLogger.py index 69e2f39c..08fccc63 100644 --- a/modules/shared/debugLogger.py +++ b/modules/shared/debugLogger.py @@ -120,7 +120,7 @@ def debugLogToFile(message: str, context: str = "DEBUG") -> None: debug_file = os.path.join(debug_dir, "debug_workflow.log") # Format the debug entry - from modules.shared.timezoneUtils import getUtcTimestamp + from modules.shared.timeUtils import getUtcTimestamp timestamp = getUtcTimestamp() debug_entry = f"[{timestamp}] [{context}] {message}\n" diff --git a/modules/shared/timeUtils.py b/modules/shared/timeUtils.py new file mode 100644 index 00000000..743a78b9 --- /dev/null +++ b/modules/shared/timeUtils.py @@ -0,0 +1,104 @@ +""" +Timezone utilities for consistent timestamp handling across the gateway. +Ensures all timestamps are properly handled as UTC. +""" + +from datetime import datetime, timezone +from typing import Optional, Any +import time +import logging + +# Configure logger +logger = logging.getLogger(__name__) + +def getUtcNow() -> datetime: + """ + Get current time in UTC with timezone info. + + Returns: + datetime: Current UTC time with timezone info + """ + return datetime.now(timezone.utc) + +def getUtcTimestamp() -> float: + """ + Get current UTC timestamp (seconds since epoch with millisecond precision). + + Returns: + float: Current UTC timestamp in seconds with millisecond precision + """ + return time.time() + +def createExpirationTimestamp(expiresInSeconds: int) -> float: + """ + Create a new expiration timestamp from seconds until expiration. + + Args: + expiresInSeconds (int): Seconds until expiration + + Returns: + float: UTC timestamp in seconds + """ + return getUtcTimestamp() + expiresInSeconds + +def parseTimestamp(value: Any, default: Optional[float] = None) -> Optional[float]: + """ + Parse a timestamp value from various source formats and return as float. + + Handles conversion from: + - float: Returns as-is + - int: Converts to float + - str: Attempts to parse as numeric string (e.g., "1234567890.123") + - None: Returns default value (or None if no default) + + This function is useful when database connectors (e.g., psycopg2) may return + numeric fields as strings in some environments (e.g., Azure PostgreSQL). + + Args: + value: The timestamp value to parse (can be float, int, str, or None) + default: Optional default value to return if value is None or invalid. + If None and value is invalid, returns None. + + Returns: + float: Parsed timestamp as float, or default value if provided, or None + + Examples: + >>> parseTimestamp(1234567890.123) + 1234567890.123 + >>> parseTimestamp("1234567890.123") + 1234567890.123 + >>> parseTimestamp(None, default=0.0) + 0.0 + >>> parseTimestamp("invalid", default=getUtcTimestamp()) + # Returns current timestamp + """ + if value is None: + return default + + # Already a float + if isinstance(value, float): + return value + + # Integer - convert to float + if isinstance(value, int): + return float(value) + + # String - try to parse as numeric + if isinstance(value, str): + # Empty string + if not value.strip(): + logger.warning(f"parseTimestamp: Received empty string, returning default={default}") + return default + try: + return float(value) + except (ValueError, TypeError) as e: + # Invalid string format + logger.warning(f"parseTimestamp: Failed to parse string '{value}' as float: {type(e).__name__}: {str(e)}, returning default={default}") + return default + + # Unknown type - try to convert anyway + try: + return float(value) + except (ValueError, TypeError) as e: + logger.warning(f"parseTimestamp: Failed to convert value of type {type(value).__name__} '{value}' to float: {type(e).__name__}: {str(e)}, returning default={default}") + return default \ No newline at end of file diff --git a/modules/shared/timezoneUtils.py b/modules/shared/timezoneUtils.py deleted file mode 100644 index 4e2141b7..00000000 --- a/modules/shared/timezoneUtils.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Timezone utilities for consistent timestamp handling across the gateway. -Ensures all timestamps are properly handled as UTC. -""" - -from datetime import datetime, timezone -import time - -def getUtcNow() -> datetime: - """ - Get current time in UTC with timezone info. - - Returns: - datetime: Current UTC time with timezone info - """ - return datetime.now(timezone.utc) - -def getUtcTimestamp() -> float: - """ - Get current UTC timestamp (seconds since epoch with millisecond precision). - - Returns: - float: Current UTC timestamp in seconds with millisecond precision - """ - return time.time() - -def createExpirationTimestamp(expiresInSeconds: int) -> float: - """ - Create a new expiration timestamp from seconds until expiration. - - Args: - expiresInSeconds (int): Seconds until expiration - - Returns: - float: UTC timestamp in seconds - """ - return getUtcTimestamp() + expiresInSeconds \ No newline at end of file diff --git a/modules/workflows/processing/modes/modeActionplan.py b/modules/workflows/processing/modes/modeActionplan.py index 864ccee2..eee563e8 100644 --- a/modules/workflows/processing/modes/modeActionplan.py +++ b/modules/workflows/processing/modes/modeActionplan.py @@ -14,6 +14,7 @@ from modules.datamodels.datamodelChat import ChatWorkflow from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, ProcessingModeEnum, PriorityEnum from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.timeUtils import parseTimestamp from modules.workflows.processing.shared.executionState import TaskExecutionState from modules.workflows.processing.shared.promptGenerationActionsActionplan import ( generateActionDefinitionPrompt, @@ -662,7 +663,7 @@ class ActionplanMode(BaseMode): retryCount=createdAction.get("retryCount", 0), retryMax=createdAction.get("retryMax", 3), processingTime=createdAction.get("processingTime"), - timestamp=float(createdAction.get("timestamp", self.services.utils.timestampGetUtc())), + timestamp=parseTimestamp(createdAction.get("timestamp"), default=self.services.utils.timestampGetUtc()), result=createdAction.get("result"), resultDocuments=createdAction.get("resultDocuments", []), userMessage=createdAction.get("userMessage") diff --git a/modules/workflows/processing/modes/modeAutomation.py b/modules/workflows/processing/modes/modeAutomation.py index 96e379db..43a81db6 100644 --- a/modules/workflows/processing/modes/modeAutomation.py +++ b/modules/workflows/processing/modes/modeAutomation.py @@ -12,6 +12,7 @@ from modules.datamodels.datamodelChat import ( from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.timeUtils import parseTimestamp logger = logging.getLogger(__name__) @@ -342,7 +343,7 @@ class AutomationMode(BaseMode): retryCount=createdAction.get("retryCount", 0), retryMax=createdAction.get("retryMax", 3), processingTime=createdAction.get("processingTime"), - timestamp=float(createdAction.get("timestamp", self.services.utils.timestampGetUtc())), + timestamp=parseTimestamp(createdAction.get("timestamp"), default=self.services.utils.timestampGetUtc()), result=createdAction.get("result"), userMessage=createdAction.get("userMessage") ) diff --git a/modules/workflows/processing/modes/modeDynamic.py b/modules/workflows/processing/modes/modeDynamic.py index 1a1c7e5c..94c04558 100644 --- a/modules/workflows/processing/modes/modeDynamic.py +++ b/modules/workflows/processing/modes/modeDynamic.py @@ -14,6 +14,7 @@ from modules.datamodels.datamodelChat import ( from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.timeUtils import parseTimestamp from modules.workflows.processing.shared.executionState import TaskExecutionState, shouldContinue from modules.workflows.processing.shared.promptGenerationActionsDynamic import ( generateDynamicPlanSelectionPrompt, @@ -867,7 +868,7 @@ Return only the user-friendly message, no technical details.""" retryCount=createdAction.get("retryCount", 0), retryMax=createdAction.get("retryMax", 3), processingTime=createdAction.get("processingTime"), - timestamp=float(createdAction.get("timestamp", self.services.utils.timestampGetUtc())), + timestamp=parseTimestamp(createdAction.get("timestamp"), default=self.services.utils.timestampGetUtc()), result=createdAction.get("result"), resultDocuments=createdAction.get("resultDocuments", []), userMessage=createdAction.get("userMessage") @@ -960,7 +961,7 @@ Return only the user-friendly message, no technical details.""" retryCount=createdAction.get("retryCount", 0), retryMax=createdAction.get("retryMax", 3), processingTime=createdAction.get("processingTime"), - timestamp=float(createdAction.get("timestamp", self.services.utils.timestampGetUtc())), + timestamp=parseTimestamp(createdAction.get("timestamp"), default=self.services.utils.timestampGetUtc()), result=createdAction.get("result"), resultDocuments=createdAction.get("resultDocuments", []), userMessage=createdAction.get("userMessage")