diff --git a/backend/app/database/dependencies.py b/backend/app/database/dependencies.py index 3d81ff4..339d11b 100644 --- a/backend/app/database/dependencies.py +++ b/backend/app/database/dependencies.py @@ -40,7 +40,7 @@ async def create_generation_session( generation_id = str(uuid.uuid4()) db.set(COL_GENERATION_SESSIONS, generation_id, { "status": "pending", - "created_at": db.server_timestamp(), + "created_at": datetime.now(UTC), **data.dict() }) return {"id": generation_id} diff --git a/backend/app/database/firestore.py b/backend/app/database/firestore.py index 6c36ce1..9b29d2b 100644 --- a/backend/app/database/firestore.py +++ b/backend/app/database/firestore.py @@ -234,10 +234,6 @@ def list_subcollection( out.append(row) return out - def server_timestamp(self) -> Any: - """Get server timestamp sentinel.""" - return firestore.SERVER_TIMESTAMP - def get_api_key_by_uid(self, key_uid: str) -> Optional[Dict[str, Any]]: """Return the api_keys document whose key_uid field matches.""" results = self.query("api_keys", filters=[("key_uid", "==", key_uid)]) diff --git a/backend/app/database/interface.py b/backend/app/database/interface.py index 3125c22..76641b8 100644 --- a/backend/app/database/interface.py +++ b/backend/app/database/interface.py @@ -127,7 +127,6 @@ class IDatabase(ABC): - CRUD operations (get, set, update, delete) - Queries with filters and ordering - Atomic transactions - - Server timestamps - Array operations """ @@ -162,7 +161,7 @@ def set(self, collection: str, doc_id: str, data: Dict[str, Any]) -> None: Example: >>> db.set("generation_sessions", "est-123", { ... "status": "pending", - ... "created_at": db.server_timestamp() + ... "created_at": datetime.now(UTC) ... }) """ pass @@ -279,7 +278,7 @@ def run_transaction(self, callback: Callable[[ITransactionContext], T]) -> T: ... # 2. Then perform all writes ... tx.update("workspaces", ws_id, { ... "status": "allocated", - ... "locked_at": db.server_timestamp() + ... "locked_at": datetime.now(UTC) ... }) ... ... return ws_id @@ -322,27 +321,6 @@ def list_subcollection( Each returned dict includes ``_id`` with the child document id. """ - @abstractmethod - def server_timestamp(self) -> Any: - """ - Get a server timestamp value for use in set/update operations. - - When this value is written to the database, it will be replaced with - the actual server timestamp. This ensures consistent timestamps across - all clients regardless of clock skew. - - Returns: - A sentinel value representing server timestamp - - Example: - >>> db.set("generation_sessions", "est-123", { - ... "created_at": db.server_timestamp(), - ... "status": "pending" - ... }) - """ - pass - - @abstractmethod def get_api_key_by_uid(self, key_uid: str) -> Optional[Dict[str, Any]]: """ diff --git a/backend/app/database/memory.py b/backend/app/database/memory.py index 014e53f..75d96c3 100644 --- a/backend/app/database/memory.py +++ b/backend/app/database/memory.py @@ -6,7 +6,6 @@ """ import threading -from datetime import UTC, datetime from typing import Any, Callable, Dict, List, Optional, TypeVar from app.database.interface import ( @@ -43,12 +42,6 @@ def _apply_dot_notation_update(doc: Dict[str, Any], key: str, value: Any) -> Non target[parts[-1]] = value -class _ServerTimestamp: - """Sentinel class for server timestamps in memory database.""" - - pass - - class InMemoryTransactionContext(ITransactionContext): """Transaction context for in-memory database.""" @@ -156,12 +149,10 @@ def _apply_writes(self) -> None: self._data[collection].pop(doc_id, None) def _process_timestamps(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Replace server timestamp sentinels with actual timestamps.""" + """Deep-copy the payload, recursing into nested maps.""" result = {} for key, value in data.items(): - if isinstance(value, _ServerTimestamp): - result[key] = datetime.now(UTC).replace(tzinfo=None) - elif isinstance(value, dict): + if isinstance(value, dict): result[key] = self._process_timestamps(value) else: result[key] = value @@ -351,10 +342,6 @@ def list_subcollection( out.append(row) return out - def server_timestamp(self) -> Any: - """Get server timestamp sentinel.""" - return _ServerTimestamp() - def get_api_key_by_uid(self, key_uid: str) -> Optional[Dict[str, Any]]: """Return the api_keys document whose key_uid field matches.""" with self._lock: @@ -366,12 +353,10 @@ def get_api_key_by_uid(self, key_uid: str) -> Optional[Dict[str, Any]]: return None def _process_timestamps(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Replace server timestamp sentinels with actual timestamps.""" + """Deep-copy the payload, recursing into nested maps.""" result = {} for key, value in data.items(): - if isinstance(value, _ServerTimestamp): - result[key] = datetime.now(UTC).replace(tzinfo=None) - elif isinstance(value, dict): + if isinstance(value, dict): result[key] = self._process_timestamps(value) else: result[key] = value diff --git a/backend/test/database/test_firestore_db.py b/backend/test/database/test_firestore_db.py index 41a83a0..3a50c1d 100644 --- a/backend/test/database/test_firestore_db.py +++ b/backend/test/database/test_firestore_db.py @@ -15,7 +15,6 @@ import os import pytest -from datetime import datetime from app.database.emulator import EmulatorDatabase from app.database.interface import DocumentNotFoundError @@ -215,31 +214,6 @@ def test_array_union_no_duplicates(self, db): assert "vue" in user["tags"] -class TestServerTimestamp: - """Test server timestamp functionality against Firestore emulator.""" - - def test_server_timestamp_on_set(self, db): - """Test server timestamp is replaced with actual time.""" - db.set("users", "user-1", { - "name": "Alice", - "created_at": db.server_timestamp() - }) - - user = db.get("users", "user-1") - assert "created_at" in user - # Firestore returns datetime objects - assert isinstance(user["created_at"], datetime) - - def test_server_timestamp_on_update(self, db): - """Test server timestamp in update operation.""" - db.set("users", "user-1", {"name": "Alice"}) - db.update("users", "user-1", {"updated_at": db.server_timestamp()}) - - user = db.get("users", "user-1") - assert "updated_at" in user - assert isinstance(user["updated_at"], datetime) - - class TestEmulatorConnection: """Test emulator-specific functionality.""" diff --git a/backend/test/database/test_memory_db.py b/backend/test/database/test_memory_db.py index 32b5f8a..bfacb01 100644 --- a/backend/test/database/test_memory_db.py +++ b/backend/test/database/test_memory_db.py @@ -5,7 +5,6 @@ """ import pytest -from datetime import datetime from app.database.memory import InMemoryDatabase from app.database.interface import DocumentNotFoundError @@ -345,43 +344,6 @@ def test_array_union_nonexistent_doc(self, db): db.array_union("users", "nonexistent", "tags", ["python"]) -class TestServerTimestamp: - """Test server timestamp functionality.""" - - def test_server_timestamp_on_set(self, db): - """Test server timestamp is replaced with actual time.""" - db.set("users", "user-1", { - "name": "Alice", - "created_at": db.server_timestamp() - }) - - user = db.get("users", "user-1") - assert "created_at" in user - assert isinstance(user["created_at"], datetime) - - def test_server_timestamp_on_update(self, db): - """Test server timestamp in update operation.""" - db.set("users", "user-1", {"name": "Alice"}) - db.update("users", "user-1", {"updated_at": db.server_timestamp()}) - - user = db.get("users", "user-1") - assert "updated_at" in user - assert isinstance(user["updated_at"], datetime) - - def test_server_timestamp_in_transaction(self, db): - """Test server timestamp in transaction.""" - def create_with_timestamp(tx): - tx.set("users", "user-1", { - "name": "Alice", - "created_at": db.server_timestamp() - }) - - db.run_transaction(create_with_timestamp) - - user = db.get("users", "user-1") - assert isinstance(user["created_at"], datetime) - - class TestIsolation: """Test data isolation and cleanup."""