Use timezone-aware timestamps and update tests

This commit is contained in:
Alexa Amundson
2025-11-16 06:41:33 -06:00
parent 44f928d88e
commit a0f26b8ebc
29 changed files with 110 additions and 71 deletions

View File

@@ -11,6 +11,7 @@ from sqlalchemy import select
from app.config import settings from app.config import settings
from app.database import get_db from app.database import get_db
from app.models.user import User from app.models.user import User
from app.utils import utc_now
# Password hashing # Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@@ -33,9 +34,9 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -
"""Create a JWT access token""" """Create a JWT access token"""
to_encode = data.copy() to_encode = data.copy()
if expires_delta: if expires_delta:
expire = datetime.utcnow() + expires_delta expire = utc_now() + expires_delta
else: else:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) expire = utc_now() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire, "type": "access"}) to_encode.update({"exp": expire, "type": "access"})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
@@ -45,7 +46,7 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -
def create_refresh_token(data: dict) -> str: def create_refresh_token(data: dict) -> str:
"""Create a JWT refresh token""" """Create a JWT refresh token"""
to_encode = data.copy() to_encode = data.copy()
expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS) expire = utc_now() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
to_encode.update({"exp": expire, "type": "refresh"}) to_encode.update({"exp": expire, "type": "refresh"})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt return encoded_jwt

View File

@@ -4,6 +4,7 @@ from typing import Optional
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, JSON, ForeignKey, Text from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, JSON, ForeignKey, Text
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.database import Base from app.database import Base
from app.utils import utc_now
class Device(Base): class Device(Base):
@@ -59,8 +60,8 @@ class Device(Base):
owner = relationship("User", back_populates="devices") owner = relationship("User", back_populates="devices")
# Timestamps # Timestamps
created_at = Column(DateTime, default=datetime.utcnow) created_at = Column(DateTime, default=utc_now)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = Column(DateTime, default=utc_now, onupdate=utc_now)
# Relations # Relations
metrics = relationship("DeviceMetric", back_populates="device", cascade="all, delete-orphan") metrics = relationship("DeviceMetric", back_populates="device", cascade="all, delete-orphan")
@@ -76,7 +77,7 @@ class DeviceMetric(Base):
device_id = Column(Integer, ForeignKey("devices.id", ondelete="CASCADE"), nullable=False) device_id = Column(Integer, ForeignKey("devices.id", ondelete="CASCADE"), nullable=False)
# Metric data # Metric data
timestamp = Column(DateTime, default=datetime.utcnow, index=True) timestamp = Column(DateTime, default=utc_now, index=True)
cpu_usage = Column(Float) cpu_usage = Column(Float)
ram_usage = Column(Float) ram_usage = Column(Float)
disk_usage = Column(Float) disk_usage = Column(Float)
@@ -100,7 +101,7 @@ class DeviceLog(Base):
device_id = Column(Integer, ForeignKey("devices.id", ondelete="CASCADE"), nullable=False) device_id = Column(Integer, ForeignKey("devices.id", ondelete="CASCADE"), nullable=False)
# Log data # Log data
timestamp = Column(DateTime, default=datetime.utcnow, index=True) timestamp = Column(DateTime, default=utc_now, index=True)
level = Column(String(20), nullable=False) # info, warning, error, critical level = Column(String(20), nullable=False) # info, warning, error, critical
category = Column(String(50)) # system, network, service, hardware category = Column(String(50)) # system, network, service, hardware
message = Column(Text, nullable=False) message = Column(Text, nullable=False)

View File

@@ -10,6 +10,7 @@ from app.database import get_db
from app.models.user import User from app.models.user import User
from app.models.ai_chat import Conversation, Message, MessageRole from app.models.ai_chat import Conversation, Message, MessageRole
from app.auth import get_current_active_user from app.auth import get_current_active_user
from app.utils import utc_now
router = APIRouter(prefix="/api/ai-chat", tags=["AI Chat"]) router = APIRouter(prefix="/api/ai-chat", tags=["AI Chat"])
@@ -188,7 +189,7 @@ async def send_message(
# Update conversation # Update conversation
conversation.message_count += 2 conversation.message_count += 2
conversation.updated_at = datetime.utcnow() conversation.updated_at = utc_now()
if not conversation.title or conversation.title == "New Conversation": if not conversation.title or conversation.title == "New Conversation":
# Auto-generate title from first message # Auto-generate title from first message

View File

@@ -13,6 +13,8 @@ import asyncio
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/health", tags=["health"]) router = APIRouter(prefix="/api/health", tags=["health"])
@@ -47,7 +49,7 @@ async def check_api_status(name: str, check_func) -> Dict[str, Any]:
"name": name, "name": name,
"status": "connected" if result.get("connected") else "not_configured", "status": "connected" if result.get("connected") else "not_configured",
"message": result.get("message", ""), "message": result.get("message", ""),
"last_checked": datetime.utcnow().isoformat(), "last_checked": utc_now().isoformat(),
"configuration": { "configuration": {
k: v for k, v in result.items() k: v for k, v in result.items()
if k.endswith("_configured") or k == "connected" if k.endswith("_configured") or k == "connected"
@@ -60,7 +62,7 @@ async def check_api_status(name: str, check_func) -> Dict[str, Any]:
"name": name, "name": name,
"status": "error", "status": "error",
"message": f"Health check failed: {str(e)}", "message": f"Health check failed: {str(e)}",
"last_checked": datetime.utcnow().isoformat(), "last_checked": utc_now().isoformat(),
"configuration": {}, "configuration": {},
"error": str(e) "error": str(e)
} }
@@ -154,7 +156,7 @@ async def check_all_apis():
return SystemHealthStatus( return SystemHealthStatus(
status=overall_status, status=overall_status,
timestamp=datetime.utcnow().isoformat(), timestamp=utc_now().isoformat(),
total_apis=total_apis, total_apis=total_apis,
connected_apis=connected_count, connected_apis=connected_count,
not_configured_apis=not_configured_count, not_configured_apis=not_configured_count,

View File

@@ -17,7 +17,7 @@ from app.auth import (
) )
from app.services.blockchain import BlockchainService from app.services.blockchain import BlockchainService
from app.services.crypto import wallet_crypto, WalletKeyEncryptionError from app.services.crypto import wallet_crypto, WalletKeyEncryptionError
from datetime import datetime from app.utils import utc_now
router = APIRouter(prefix="/api/auth", tags=["Authentication"]) router = APIRouter(prefix="/api/auth", tags=["Authentication"])
@@ -82,7 +82,7 @@ async def register(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
wallet_address=wallet_address, wallet_address=wallet_address,
wallet_private_key=encrypted_private_key, wallet_private_key=encrypted_private_key,
balance=100.0, # Starting bonus balance=100.0, # Starting bonus
created_at=datetime.utcnow() created_at=utc_now()
) )
db.add(user) db.add(user)
@@ -131,7 +131,7 @@ async def login(
) )
# Update last login # Update last login
user.last_login = datetime.utcnow() user.last_login = utc_now()
await db.commit() await db.commit()
# Create tokens # Create tokens

View File

@@ -12,7 +12,6 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select
from typing import List, Optional from typing import List, Optional
from datetime import datetime
import httpx import httpx
from urllib.parse import urlparse, quote from urllib.parse import urlparse, quote
import hashlib import hashlib
@@ -21,6 +20,7 @@ from ..database import get_db
from ..auth import get_current_user from ..auth import get_current_user
from ..models import User from ..models import User
from pydantic import BaseModel, HttpUrl from pydantic import BaseModel, HttpUrl
from ..utils import utc_now
router = APIRouter(prefix="/api/browser", tags=["browser"]) router = APIRouter(prefix="/api/browser", tags=["browser"])
@@ -142,7 +142,7 @@ async def add_bookmark(
"title": bookmark.title, "title": bookmark.title,
"url": bookmark.url, "url": bookmark.url,
"folder": bookmark.folder, "folder": bookmark.folder,
"created_at": datetime.utcnow().isoformat() "created_at": utc_now().isoformat()
} }
return { return {
@@ -208,10 +208,10 @@ async def add_history_entry(
): ):
"""Add a history entry""" """Add a history entry"""
new_entry = { new_entry = {
"id": hashlib.md5(f"{entry.url}{datetime.utcnow()}".encode()).hexdigest()[:8], "id": hashlib.md5(f"{entry.url}{utc_now()}".encode()).hexdigest()[:8],
"url": entry.url, "url": entry.url,
"title": entry.title, "title": entry.title,
"visited_at": datetime.utcnow().isoformat() "visited_at": utc_now().isoformat()
} }
return { return {
@@ -232,7 +232,7 @@ async def clear_history(
@router.get("/search") @router.get("/search")
async def web_search( async def web_search(
q: str = Query(..., min_length=1, description="Search query"), q: str = Query(..., min_length=1, description="Search query"),
engine: str = Query("duckduckgo", regex="^(duckduckgo|google|bing)$"), engine: str = Query("duckduckgo", pattern="^(duckduckgo|google|bing)$"),
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
): ):
""" """
@@ -397,7 +397,7 @@ async def open_new_tab(
"""Open a new tab""" """Open a new tab"""
return { return {
"tab": { "tab": {
"id": hashlib.md5(f"{url}{datetime.utcnow()}".encode()).hexdigest()[:8], "id": hashlib.md5(f"{url}{utc_now()}".encode()).hexdigest()[:8],
"url": url, "url": url,
"title": "Loading...", "title": "Loading...",
"active": True "active": True

View File

@@ -19,6 +19,7 @@ from ..database import get_db
from ..auth import get_current_user from ..auth import get_current_user
from ..models import User, Device, Email, Post, Video, File, Conversation, Block, Transaction from ..models import User, Device, Email, Post, Video, File, Conversation, Block, Transaction
from pydantic import BaseModel from pydantic import BaseModel
from ..utils import utc_now
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"]) router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
@@ -201,7 +202,7 @@ async def get_dashboard_overview(
}, },
"services": services, "services": services,
"system_health": system_health, "system_health": system_health,
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }
@@ -356,7 +357,7 @@ async def get_recent_activity(
"icon": "📧", "icon": "📧",
"action": "Received new email", "action": "Received new email",
"description": "Meeting reminder from Sarah", "description": "Meeting reminder from Sarah",
"timestamp": (datetime.utcnow() - timedelta(minutes=5)).isoformat() "timestamp": (utc_now() - timedelta(minutes=5)).isoformat()
}, },
{ {
"id": 2, "id": 2,
@@ -364,7 +365,7 @@ async def get_recent_activity(
"icon": "⛓️", "icon": "⛓️",
"action": "Transaction completed", "action": "Transaction completed",
"description": "Sent 10 RoadCoins to wallet abc123", "description": "Sent 10 RoadCoins to wallet abc123",
"timestamp": (datetime.utcnow() - timedelta(minutes=15)).isoformat() "timestamp": (utc_now() - timedelta(minutes=15)).isoformat()
}, },
{ {
"id": 3, "id": 3,
@@ -372,7 +373,7 @@ async def get_recent_activity(
"icon": "⛏️", "icon": "⛏️",
"action": "Block mined", "action": "Block mined",
"description": "Mined block #1234, earned 50 RoadCoins", "description": "Mined block #1234, earned 50 RoadCoins",
"timestamp": (datetime.utcnow() - timedelta(hours=1)).isoformat() "timestamp": (utc_now() - timedelta(hours=1)).isoformat()
}, },
{ {
"id": 4, "id": 4,
@@ -380,7 +381,7 @@ async def get_recent_activity(
"icon": "🥧", "icon": "🥧",
"action": "Device connected", "action": "Device connected",
"description": "Raspberry Pi 4 - Living Room came online", "description": "Raspberry Pi 4 - Living Room came online",
"timestamp": (datetime.utcnow() - timedelta(hours=2)).isoformat() "timestamp": (utc_now() - timedelta(hours=2)).isoformat()
}, },
{ {
"id": 5, "id": 5,
@@ -388,7 +389,7 @@ async def get_recent_activity(
"icon": "🌐", "icon": "🌐",
"action": "New like", "action": "New like",
"description": "Mike liked your post", "description": "Mike liked your post",
"timestamp": (datetime.utcnow() - timedelta(hours=3)).isoformat() "timestamp": (utc_now() - timedelta(hours=3)).isoformat()
} }
] ]

View File

@@ -10,6 +10,7 @@ from app.database import get_db
from app.models.device import Device, DeviceMetric, DeviceLog from app.models.device import Device, DeviceMetric, DeviceLog
from app.models.user import User from app.models.user import User
from app.routers.auth import get_current_user from app.routers.auth import get_current_user
from app.utils import utc_now
router = APIRouter(prefix="/api/devices", tags=["devices"]) router = APIRouter(prefix="/api/devices", tags=["devices"])
@@ -239,7 +240,7 @@ async def update_device(
if device_data.description is not None: if device_data.description is not None:
device.description = device_data.description device.description = device_data.description
device.updated_at = datetime.utcnow() device.updated_at = utc_now()
await db.commit() await db.commit()
await db.refresh(device) await db.refresh(device)
@@ -265,7 +266,7 @@ async def device_heartbeat(
# Update device status # Update device status
device.is_online = True device.is_online = True
device.status = "online" device.status = "online"
device.last_seen = datetime.utcnow() device.last_seen = utc_now()
# Update system info if provided # Update system info if provided
if heartbeat_data.ip_address: if heartbeat_data.ip_address:
@@ -308,7 +309,7 @@ async def device_heartbeat(
# Save metric snapshot # Save metric snapshot
metric = DeviceMetric( metric = DeviceMetric(
device_id=device.id, device_id=device.id,
timestamp=datetime.utcnow(), timestamp=utc_now(),
cpu_usage=heartbeat_data.cpu_usage_percent, cpu_usage=heartbeat_data.cpu_usage_percent,
ram_usage=heartbeat_data.ram_usage_percent, ram_usage=heartbeat_data.ram_usage_percent,
disk_usage=heartbeat_data.disk_usage_percent, disk_usage=heartbeat_data.disk_usage_percent,
@@ -392,7 +393,7 @@ async def ssh_connect(
"device_id": device_id, "device_id": device_id,
"ip_address": device.ip_address, "ip_address": device.ip_address,
"hostname": device.hostname, "hostname": device.hostname,
"connection_token": f"ssh_token_{device_id}_{datetime.utcnow().timestamp()}", "connection_token": f"ssh_token_{device_id}_{utc_now().timestamp()}",
"status": "connected", "status": "connected",
"message": f"SSH connection established to {device.hostname or device.ip_address}" "message": f"SSH connection established to {device.hostname or device.ip_address}"
} }
@@ -448,7 +449,7 @@ async def ssh_execute_command(
"command": command_data.command, "command": command_data.command,
"output": output, "output": output,
"exit_code": 0, "exit_code": 0,
"executed_at": datetime.utcnow().isoformat() "executed_at": utc_now().isoformat()
} }
@@ -511,7 +512,7 @@ async def deploy_to_device(
"deploy_path": deploy_config.deploy_path, "deploy_path": deploy_config.deploy_path,
"steps": deployment_steps, "steps": deployment_steps,
"status": "success", "status": "success",
"deployed_at": datetime.utcnow().isoformat(), "deployed_at": utc_now().isoformat(),
"message": "Deployment completed successfully" "message": "Deployment completed successfully"
} }

View File

@@ -12,6 +12,8 @@ import httpx
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/discord", tags=["discord"]) router = APIRouter(prefix="/api/discord", tags=["discord"])
@@ -304,5 +306,5 @@ async def discord_health_check():
"service": "discord", "service": "discord",
"status": "operational" if DISCORD_BOT_TOKEN else "not_configured", "status": "operational" if DISCORD_BOT_TOKEN else "not_configured",
"webhook_status": "operational" if DISCORD_WEBHOOK_URL else "not_configured", "webhook_status": "operational" if DISCORD_WEBHOOK_URL else "not_configured",
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }

View File

@@ -10,6 +10,7 @@ from app.database import get_db
from app.models.user import User from app.models.user import User
from app.models.email import Email, EmailFolder, EmailFolderType from app.models.email import Email, EmailFolder, EmailFolderType
from app.auth import get_current_active_user from app.auth import get_current_active_user
from app.utils import utc_now
router = APIRouter(prefix="/api/email", tags=["Email"]) router = APIRouter(prefix="/api/email", tags=["Email"])
@@ -154,7 +155,7 @@ async def send_email(
bcc=",".join(email_data.bcc) if email_data.bcc else None, bcc=",".join(email_data.bcc) if email_data.bcc else None,
is_read=False, is_read=False,
is_draft=False, is_draft=False,
sent_at=datetime.utcnow() sent_at=utc_now()
) )
db.add(email) db.add(email)
@@ -193,7 +194,7 @@ async def get_email(
# Mark as read if recipient is viewing # Mark as read if recipient is viewing
if email.recipient_id == current_user.id and not email.is_read: if email.recipient_id == current_user.id and not email.is_read:
email.is_read = True email.is_read = True
email.read_at = datetime.utcnow() email.read_at = utc_now()
await db.commit() await db.commit()
return email return email

View File

@@ -11,6 +11,7 @@ from app.database import get_db
from app.models.user import User from app.models.user import User
from app.models.file import File, Folder from app.models.file import File, Folder
from app.auth import get_current_active_user from app.auth import get_current_active_user
from app.utils import utc_now
router = APIRouter(prefix="/api/files", tags=["Files"]) router = APIRouter(prefix="/api/files", tags=["Files"])
@@ -217,7 +218,7 @@ async def get_file(
) )
# Update last accessed # Update last accessed
file.last_accessed = datetime.utcnow() file.last_accessed = utc_now()
await db.commit() await db.commit()
return file return file

View File

@@ -19,6 +19,7 @@ import random
from ..database import get_db from ..database import get_db
from ..auth import get_current_user from ..auth import get_current_user
from ..models import User from ..models import User
from ..utils import utc_now
router = APIRouter(prefix="/api/games", tags=["games"]) router = APIRouter(prefix="/api/games", tags=["games"])
@@ -62,7 +63,7 @@ async def list_cities(
"money": 45000, "money": 45000,
"level": 5, "level": 5,
"created_at": "2024-01-01T00:00:00Z", "created_at": "2024-01-01T00:00:00Z",
"updated_at": datetime.utcnow().isoformat() "updated_at": utc_now().isoformat()
} }
] ]
} }

View File

@@ -186,7 +186,7 @@ async def list_commits(
async def list_pull_requests( async def list_pull_requests(
owner: str, owner: str,
repo: str, repo: str,
state: str = Query("open", regex="^(open|closed|all)$"), state: str = Query("open", pattern="^(open|closed|all)$"),
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100), per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
@@ -220,7 +220,7 @@ async def list_pull_requests(
async def list_issues( async def list_issues(
owner: str, owner: str,
repo: str, repo: str,
state: str = Query("open", regex="^(open|closed|all)$"), state: str = Query("open", pattern="^(open|closed|all)$"),
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100), per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)

View File

@@ -37,7 +37,7 @@ class InferenceRequest(BaseModel):
async def list_models( async def list_models(
search: Optional[str] = Query(None), search: Optional[str] = Query(None),
filter_task: Optional[str] = Query(None, alias="task"), filter_task: Optional[str] = Query(None, alias="task"),
sort: str = Query("downloads", regex="^(downloads|likes|trending)$"), sort: str = Query("downloads", pattern="^(downloads|likes|trending)$"),
limit: int = Query(20, ge=1, le=100), limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
): ):
@@ -146,7 +146,7 @@ async def run_inference(
@router.get("/datasets") @router.get("/datasets")
async def list_datasets( async def list_datasets(
search: Optional[str] = Query(None), search: Optional[str] = Query(None),
sort: str = Query("downloads", regex="^(downloads|likes|trending)$"), sort: str = Query("downloads", pattern="^(downloads|likes|trending)$"),
limit: int = Query(20, ge=1, le=100), limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
): ):
@@ -194,7 +194,7 @@ async def get_dataset_info(
@router.get("/spaces") @router.get("/spaces")
async def list_spaces( async def list_spaces(
search: Optional[str] = Query(None), search: Optional[str] = Query(None),
sort: str = Query("likes", regex="^(likes|trending)$"), sort: str = Query("likes", pattern="^(likes|trending)$"),
limit: int = Query(20, ge=1, le=100), limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
): ):

View File

@@ -13,6 +13,7 @@ from app.database import get_db
from app.models.blockchain import Block, Wallet from app.models.blockchain import Block, Wallet
from app.models.user import User from app.models.user import User
from app.routers.auth import get_current_user from app.routers.auth import get_current_user
from app.utils import utc_now
router = APIRouter(prefix="/api/miner", tags=["miner"]) router = APIRouter(prefix="/api/miner", tags=["miner"])
@@ -97,7 +98,7 @@ async def get_miner_status(
"""Get current miner status and performance metrics.""" """Get current miner status and performance metrics."""
uptime_seconds = 0 uptime_seconds = 0
if miner_state.started_at: if miner_state.started_at:
uptime_seconds = int((datetime.utcnow() - miner_state.started_at).total_seconds()) uptime_seconds = int((utc_now() - miner_state.started_at).total_seconds())
# Simulate some variance in hashrate # Simulate some variance in hashrate
current_hashrate = miner_state.hashrate_mhs current_hashrate = miner_state.hashrate_mhs
@@ -242,7 +243,7 @@ async def control_miner(
raise HTTPException(status_code=400, detail="Miner is already running") raise HTTPException(status_code=400, detail="Miner is already running")
miner_state.is_mining = True miner_state.is_mining = True
miner_state.started_at = datetime.utcnow() miner_state.started_at = utc_now()
miner_state.hashrate_mhs = random.uniform(38.0, 45.0) # Simulate hashrate miner_state.hashrate_mhs = random.uniform(38.0, 45.0) # Simulate hashrate
if control.pool_url: if control.pool_url:
@@ -268,7 +269,7 @@ async def control_miner(
miner_state.is_mining = False miner_state.is_mining = False
await asyncio.sleep(1) await asyncio.sleep(1)
miner_state.is_mining = True miner_state.is_mining = True
miner_state.started_at = datetime.utcnow() miner_state.started_at = utc_now()
miner_state.hashrate_mhs = random.uniform(38.0, 45.0) miner_state.hashrate_mhs = random.uniform(38.0, 45.0)
background_tasks.add_task(simulate_mining) background_tasks.add_task(simulate_mining)
@@ -314,5 +315,5 @@ async def get_pool_info(
"pool_fee": "1%", "pool_fee": "1%",
"min_payout": 10.0, "min_payout": 10.0,
"payment_interval_hours": 24, "payment_interval_hours": 24,
"last_block_found": (datetime.utcnow() - timedelta(minutes=random.randint(5, 120))).isoformat(), "last_block_found": (utc_now() - timedelta(minutes=random.randint(5, 120))).isoformat(),
} }

View File

@@ -13,6 +13,8 @@ import httpx
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/railway", tags=["railway"]) router = APIRouter(prefix="/api/railway", tags=["railway"])
@@ -389,5 +391,5 @@ async def railway_health_check():
return { return {
"service": "railway", "service": "railway",
"status": "operational" if RAILWAY_TOKEN else "not_configured", "status": "operational" if RAILWAY_TOKEN else "not_configured",
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }

View File

@@ -13,6 +13,8 @@ import httpx
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/sentry", tags=["sentry"]) router = APIRouter(prefix="/api/sentry", tags=["sentry"])
@@ -376,5 +378,5 @@ async def sentry_health_check():
return { return {
"service": "sentry", "service": "sentry",
"status": "operational" if SENTRY_AUTH_TOKEN else "not_configured", "status": "operational" if SENTRY_AUTH_TOKEN else "not_configured",
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }

View File

@@ -7,11 +7,12 @@ Provides endpoints for sending messages, managing channels, and interacting with
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional, Dict, Any from typing import List, Optional, Dict, Any
from datetime import datetime
import httpx import httpx
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/slack", tags=["slack"]) router = APIRouter(prefix="/api/slack", tags=["slack"])
@@ -277,5 +278,5 @@ async def slack_health_check():
"service": "slack", "service": "slack",
"status": "operational" if SLACK_BOT_TOKEN else "not_configured", "status": "operational" if SLACK_BOT_TOKEN else "not_configured",
"webhook_status": "operational" if SLACK_WEBHOOK_URL else "not_configured", "webhook_status": "operational" if SLACK_WEBHOOK_URL else "not_configured",
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }

View File

@@ -8,11 +8,12 @@ Stripe is a payment processing platform for online businesses.
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr
from typing import List, Optional, Dict, Any from typing import List, Optional, Dict, Any
from datetime import datetime
import httpx import httpx
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/stripe", tags=["stripe"]) router = APIRouter(prefix="/api/stripe", tags=["stripe"])
@@ -324,5 +325,5 @@ async def stripe_health_check():
return { return {
"service": "stripe", "service": "stripe",
"status": "operational" if STRIPE_SECRET_KEY else "not_configured", "status": "operational" if STRIPE_SECRET_KEY else "not_configured",
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }

View File

@@ -14,6 +14,8 @@ import base64
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/twilio", tags=["twilio"]) router = APIRouter(prefix="/api/twilio", tags=["twilio"])
@@ -255,5 +257,5 @@ async def twilio_health_check():
return { return {
"service": "twilio", "service": "twilio",
"status": "operational" if (TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN) else "not_configured", "status": "operational" if (TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN) else "not_configured",
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }

View File

@@ -13,6 +13,8 @@ import httpx
import os import os
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/vercel", tags=["vercel"]) router = APIRouter(prefix="/api/vercel", tags=["vercel"])
@@ -420,5 +422,5 @@ async def vercel_health_check():
return { return {
"service": "vercel", "service": "vercel",
"status": "operational" if VERCEL_TOKEN else "not_configured", "status": "operational" if VERCEL_TOKEN else "not_configured",
"timestamp": datetime.utcnow().isoformat() "timestamp": utc_now().isoformat()
} }

View File

@@ -10,6 +10,7 @@ from app.database import get_db
from app.models.user import User from app.models.user import User
from app.models.video import Video, VideoView, VideoLike from app.models.video import Video, VideoView, VideoLike
from app.auth import get_current_active_user from app.auth import get_current_active_user
from app.utils import utc_now
router = APIRouter(prefix="/api/videos", tags=["Videos"]) router = APIRouter(prefix="/api/videos", tags=["Videos"])
@@ -117,7 +118,7 @@ async def upload_video(
category=video_data.category, category=video_data.category,
tags=video_data.tags, tags=video_data.tags,
is_public=True, is_public=True,
published_at=datetime.utcnow() published_at=utc_now()
) )
db.add(video) db.add(video)

View File

@@ -12,11 +12,11 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select
from typing import List, Optional from typing import List, Optional
from datetime import datetime
from ..database import get_db from ..database import get_db
from ..auth import get_current_user from ..auth import get_current_user
from ..models import User, File, Folder from ..models import User, File, Folder
from ..utils import utc_now
from pydantic import BaseModel from pydantic import BaseModel
router = APIRouter(prefix="/api/vscode", tags=["vscode"]) router = APIRouter(prefix="/api/vscode", tags=["vscode"])
@@ -120,7 +120,7 @@ async def update_file_content(
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found")
# In a real implementation, save to S3 or file system # In a real implementation, save to S3 or file system
file.updated_at = datetime.utcnow() file.updated_at = utc_now()
file.size = len(content.encode('utf-8')) file.size = len(content.encode('utf-8'))
await db.commit() await db.commit()

View File

@@ -12,6 +12,8 @@ import httpx
from enum import Enum from enum import Enum
import logging import logging
from app.utils import utc_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -93,23 +95,23 @@ class APIClient:
if response.status_code < 500: if response.status_code < 500:
self.status = APIStatus.CONNECTED self.status = APIStatus.CONNECTED
self.error_message = None self.error_message = None
self.last_check = datetime.utcnow() self.last_check = utc_now()
return True return True
else: else:
self.status = APIStatus.ERROR self.status = APIStatus.ERROR
self.error_message = f"Server error: {response.status_code}" self.error_message = f"Server error: {response.status_code}"
self.last_check = datetime.utcnow() self.last_check = utc_now()
return False return False
except httpx.TimeoutException: except httpx.TimeoutException:
self.status = APIStatus.ERROR self.status = APIStatus.ERROR
self.error_message = "Connection timeout" self.error_message = "Connection timeout"
self.last_check = datetime.utcnow() self.last_check = utc_now()
return False return False
except Exception as e: except Exception as e:
self.status = APIStatus.ERROR self.status = APIStatus.ERROR
self.error_message = str(e) self.error_message = str(e)
self.last_check = datetime.utcnow() self.last_check = utc_now()
logger.error(f"Health check failed for {self.name}: {e}") logger.error(f"Health check failed for {self.name}: {e}")
return False return False

View File

@@ -1,7 +1,6 @@
"""Blockchain service""" """Blockchain service"""
import hashlib import hashlib
import json import json
from datetime import datetime
from typing import List, Optional from typing import List, Optional
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc from sqlalchemy import select, desc
@@ -9,6 +8,7 @@ from app.models.blockchain import Block, Transaction, Wallet
from app.models.user import User from app.models.user import User
from app.config import settings from app.config import settings
from app.services.crypto import wallet_crypto, WalletKeyDecryptionError from app.services.crypto import wallet_crypto, WalletKeyDecryptionError
from app.utils import utc_now
import secrets import secrets
@@ -31,7 +31,7 @@ class BlockchainService:
if existing: if existing:
return existing return existing
timestamp = datetime.utcnow() timestamp = utc_now()
genesis_hash = BlockchainService.calculate_hash(0, str(timestamp), "0", [], 0) genesis_hash = BlockchainService.calculate_hash(0, str(timestamp), "0", [], 0)
genesis_block = Block( genesis_block = Block(
@@ -69,7 +69,7 @@ class BlockchainService:
latest_block = await BlockchainService.create_genesis_block(db) latest_block = await BlockchainService.create_genesis_block(db)
new_index = latest_block.index + 1 new_index = latest_block.index + 1
timestamp = datetime.utcnow() timestamp = utc_now()
previous_hash = latest_block.hash previous_hash = latest_block.hash
difficulty = settings.BLOCKCHAIN_DIFFICULTY difficulty = settings.BLOCKCHAIN_DIFFICULTY
@@ -117,7 +117,7 @@ class BlockchainService:
tx.block_index = new_block.index tx.block_index = new_block.index
tx.is_confirmed = True tx.is_confirmed = True
tx.confirmations = 1 tx.confirmations = 1
tx.confirmed_at = datetime.utcnow() tx.confirmed_at = utc_now()
# Reward miner # Reward miner
user.balance += settings.MINING_REWARD user.balance += settings.MINING_REWARD
@@ -152,7 +152,7 @@ class BlockchainService:
) from exc ) from exc
# Generate transaction hash # Generate transaction hash
tx_data = f"{from_address}{to_address}{amount}{datetime.utcnow()}" tx_data = f"{from_address}{to_address}{amount}{utc_now()}"
transaction_hash = hashlib.sha256(tx_data.encode()).hexdigest() transaction_hash = hashlib.sha256(tx_data.encode()).hexdigest()
# Sign transaction (simplified) # Sign transaction (simplified)

View File

@@ -0,0 +1,4 @@
"""Utility helpers for the BlackRoad backend."""
from .datetime import utc_now # noqa: F401

View File

@@ -0,0 +1,9 @@
"""Datetime utilities."""
from datetime import datetime, timezone
def utc_now() -> datetime:
"""Return a timezone-aware UTC datetime."""
return datetime.now(timezone.utc)

View File

@@ -1,5 +1,5 @@
"""Miner integration tests""" """Miner integration tests"""
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
import pytest import pytest
from httpx import AsyncClient from httpx import AsyncClient
@@ -44,7 +44,7 @@ async def test_miner_stats_respects_wallet(
"""Ensure /api/miner/stats reports only the authenticated user's blocks.""" """Ensure /api/miner/stats reports only the authenticated user's blocks."""
wallet_address = test_user["wallet_address"] wallet_address = test_user["wallet_address"]
user_id = test_user["id"] user_id = test_user["id"]
now = datetime.utcnow() now = datetime.now(timezone.utc)
await _create_block( await _create_block(
db_session, db_session,
@@ -91,7 +91,7 @@ async def test_miner_blocks_endpoint_returns_only_user_blocks(
"""Ensure /api/miner/blocks only returns the authenticated user's blocks.""" """Ensure /api/miner/blocks only returns the authenticated user's blocks."""
wallet_address = test_user["wallet_address"] wallet_address = test_user["wallet_address"]
user_id = test_user["id"] user_id = test_user["id"]
now = datetime.utcnow() now = datetime.now(timezone.utc)
await _create_block( await _create_block(
db_session, db_session,

View File

@@ -1,5 +1,5 @@
"""Tests for VS Code integration endpoints""" """Tests for VS Code integration endpoints"""
from datetime import datetime from datetime import datetime, timezone
from typing import Optional from typing import Optional
import pytest import pytest
@@ -11,7 +11,7 @@ from app.models import File
async def _create_file(db: AsyncSession, user_id: int, name: str, file_type: Optional[str]) -> File: async def _create_file(db: AsyncSession, user_id: int, name: str, file_type: Optional[str]) -> File:
"""Helper to create a file record for tests.""" """Helper to create a file record for tests."""
now = datetime.utcnow() now = datetime.now(timezone.utc)
file = File( file = File(
user_id=user_id, user_id=user_id,
name=name, name=name,