fix: replace deprecated datetime.utcnow with timezone-aware datetime.now in all modules and fix comment count query column name
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
@@ -74,7 +74,7 @@ def store_attachment(file_bytes, original_filename, user_uid):
|
||||
"directory": directory, "file_size": len(file_bytes), "mime_type": mime,
|
||||
"image_width": img_w, "image_height": img_h,
|
||||
"has_thumbnail": 1 if thumb else 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
return {"uid": uid, "original_filename": original_filename, "file_size": len(file_bytes), "mime_type": mime, "url": f"/static/uploads/attachments/{directory}/{stored_name}", "thumbnail_url": f"/static/uploads/attachments/{directory}/{thumb}" if thumb else None, "has_thumbnail": thumb is not None, "is_image": is_img}
|
||||
|
||||
|
||||
@@ -143,8 +143,8 @@ def get_comment_counts_by_post_uids(post_uids):
|
||||
return {}
|
||||
placeholders = ", ".join(f":p{i}" for i in range(len(post_uids)))
|
||||
params = {f"p{i}": u for i, u in enumerate(post_uids)}
|
||||
rows = db.query(f"SELECT post_uid, COUNT(*) as c FROM comments WHERE post_uid IN ({placeholders}) GROUP BY post_uid", **params)
|
||||
return {r["post_uid"]: r["c"] for r in rows}
|
||||
rows = db.query(f"SELECT target_uid, COUNT(*) as c FROM comments WHERE target_type='post' AND target_uid IN ({placeholders}) GROUP BY target_uid", **params)
|
||||
return {r["target_uid"]: r["c"] for r in rows}
|
||||
|
||||
|
||||
def get_vote_counts(target_uids):
|
||||
|
||||
@@ -37,6 +37,7 @@ async def not_found(request: Request, exc):
|
||||
|
||||
@app.exception_handler(500)
|
||||
async def server_error(request: Request, exc):
|
||||
logger.exception("500 error on %s %s", request.method, request.url.path)
|
||||
seo_ctx = base_seo_context(request, title="Server Error — DevPlace", description="Something went wrong.", robots="noindex")
|
||||
return templates.TemplateResponse(request, "error.html", {**seo_ctx, "request": request, "error_code": 500, "error_message": "Internal server error"}, status_code=500)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import hashlib
|
||||
import secrets
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
from devplacepy.database import get_table
|
||||
@@ -90,7 +90,7 @@ async def signup(request: Request):
|
||||
"level": 1,
|
||||
"xp": 0,
|
||||
"stars": 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
badges = get_table("badges")
|
||||
@@ -98,7 +98,7 @@ async def signup(request: Request):
|
||||
"uid": generate_uid(),
|
||||
"user_uid": uid,
|
||||
"badge_name": "Member",
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
token = create_session(uid)
|
||||
@@ -174,9 +174,9 @@ async def forgot_password(request: Request):
|
||||
"uid": generate_uid(),
|
||||
"user_uid": user["uid"],
|
||||
"token": token_hash,
|
||||
"expires_at": (datetime.utcnow() + timedelta(hours=1)).isoformat(),
|
||||
"expires_at": (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
|
||||
"used": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
logger.info(f"Password reset requested for {email}")
|
||||
|
||||
@@ -220,7 +220,15 @@ async def reset_password(request: Request, token: str):
|
||||
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
||||
matched = resets.find_one(token=token_hash, used=False)
|
||||
|
||||
if not matched or datetime.fromisoformat(matched["expires_at"]) < datetime.utcnow():
|
||||
if not matched:
|
||||
errors.append("Invalid or expired reset token")
|
||||
return templates.TemplateResponse(request, "reset_password.html", {
|
||||
"request": request, "token": token, "errors": errors,
|
||||
})
|
||||
expires = datetime.fromisoformat(matched["expires_at"])
|
||||
if expires.tzinfo is None:
|
||||
expires = expires.replace(tzinfo=timezone.utc)
|
||||
if expires < datetime.now(timezone.utc):
|
||||
errors.append("Invalid or expired reset token")
|
||||
return templates.TemplateResponse(request, "reset_password.html", {
|
||||
"request": request, "token": token, "errors": errors,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table, load_comments
|
||||
@@ -69,7 +69,7 @@ async def create_bug(request: Request):
|
||||
"title": title,
|
||||
"description": description,
|
||||
"status": "open",
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from devplacepy.database import get_table, resolve_by_slug
|
||||
@@ -56,7 +56,7 @@ async def create_comment(request: Request):
|
||||
"user_uid": user["uid"],
|
||||
"content": content,
|
||||
"parent_uid": parent_uid or None,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
if target_type == "post":
|
||||
insert["post_uid"] = target_uid
|
||||
@@ -73,7 +73,7 @@ async def create_comment(request: Request):
|
||||
"uid": generate_uid(),
|
||||
"user_uid": user["uid"],
|
||||
"badge_name": "First Comment",
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
if target_type == "post":
|
||||
@@ -89,7 +89,7 @@ async def create_comment(request: Request):
|
||||
"message": f"{user['username']} replied to your comment",
|
||||
"related_uid": user["uid"],
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
else:
|
||||
posts = get_table("posts")
|
||||
@@ -105,7 +105,7 @@ async def create_comment(request: Request):
|
||||
"message": f"{user['username']} commented on your post",
|
||||
"related_uid": user["uid"],
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
create_mention_notifications(content, user["uid"], redirect_url)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from devplacepy.database import get_table, get_daily_topic, get_users_by_uids, get_comment_counts_by_post_uids
|
||||
@@ -72,7 +72,7 @@ async def feed_page(request: Request, tab: str = "all", topic: str = None, befor
|
||||
users_table = get_table("users")
|
||||
total_members = len(list(users_table.all()))
|
||||
posts_table = get_table("posts")
|
||||
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
|
||||
today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
|
||||
posts_today = len(list(posts_table.find(created_at={">=": today_start})))
|
||||
total_projects = len(list(get_table("projects").all()))
|
||||
total_gists = len(list(get_table("gists").all()))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
@@ -26,7 +26,7 @@ async def follow_user(request: Request, username: str):
|
||||
"uid": generate_uid(),
|
||||
"follower_uid": user["uid"],
|
||||
"following_uid": target["uid"],
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
notifications = get_table("notifications")
|
||||
@@ -37,7 +37,7 @@ async def follow_user(request: Request, username: str):
|
||||
"message": f"{user['username']} started following you",
|
||||
"related_uid": user["uid"],
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
logger.info(f"{user['username']} followed {username}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table, load_comments, get_vote_counts, resolve_by_slug, db
|
||||
@@ -161,7 +161,7 @@ async def create_gist(request: Request):
|
||||
"source_code": source_code,
|
||||
"language": language,
|
||||
"stars": 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
|
||||
from devplacepy.database import get_table, db
|
||||
@@ -160,7 +160,7 @@ async def send_message(request: Request):
|
||||
"receiver_uid": receiver_uid,
|
||||
"content": content,
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
|
||||
@@ -177,7 +177,7 @@ async def send_message(request: Request):
|
||||
"message": f"{user['username']} sent you a message",
|
||||
"related_uid": user["uid"],
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
create_mention_notifications(content, user["uid"], f"/messages?with_uid={receiver_uid}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from devplacepy.database import get_table, db, load_comments, resolve_by_slug
|
||||
@@ -16,7 +16,7 @@ NEWS_MAX_AGE_DAYS = 4
|
||||
@router.get("", response_class=HTMLResponse)
|
||||
async def news_page(request: Request):
|
||||
user = get_current_user(request)
|
||||
cutoff = (datetime.utcnow() - timedelta(days=NEWS_MAX_AGE_DAYS)).isoformat()
|
||||
cutoff = (datetime.now(timezone.utc) - timedelta(days=NEWS_MAX_AGE_DAYS)).isoformat()
|
||||
|
||||
news_table = get_table("news")
|
||||
articles = list(news_table.find(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
@@ -14,7 +14,7 @@ router = APIRouter()
|
||||
def _group_label(created_at: str) -> str:
|
||||
try:
|
||||
dt = datetime.fromisoformat(created_at)
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(timezone.utc)
|
||||
today = now.date()
|
||||
date = dt.date()
|
||||
if date == today:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
import aiofiles
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
@@ -76,7 +76,7 @@ async def create_post(request: Request):
|
||||
"project_uid": project_uid or None,
|
||||
"image": image_filename,
|
||||
"stars": 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
badges = get_table("badges")
|
||||
@@ -86,7 +86,7 @@ async def create_post(request: Request):
|
||||
"uid": generate_uid(),
|
||||
"user_uid": user["uid"],
|
||||
"badge_name": "First Post",
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
attachment_uids = form.getlist("attachment_uids")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table, get_vote_counts, load_comments, resolve_by_slug
|
||||
@@ -176,7 +176,7 @@ async def create_project(request: Request):
|
||||
"platforms": platforms,
|
||||
"status": status,
|
||||
"stars": 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
attachment_uids = form.getlist("attachment_uids") if hasattr(form, "getlist") else []
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
@@ -33,7 +33,7 @@ async def vote(request: Request, target_type: str, target_uid: str):
|
||||
"target_uid": target_uid,
|
||||
"target_type": target_type,
|
||||
"value": value,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
up_count = len(list(votes.find(target_uid=target_uid, value=1)))
|
||||
@@ -77,7 +77,7 @@ async def vote(request: Request, target_type: str, target_uid: str):
|
||||
"message": f"{user['username']} ++'d your {label}",
|
||||
"related_uid": user["uid"],
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
referer = request.headers.get("Referer", "/feed")
|
||||
|
||||
@@ -2,7 +2,7 @@ import asyncio
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -34,11 +34,11 @@ class BaseService(ABC):
|
||||
def uptime(self) -> str | None:
|
||||
if self._started_at is None:
|
||||
return None
|
||||
delta = datetime.utcnow() - self._started_at
|
||||
delta = datetime.now(timezone.utc) - self._started_at
|
||||
return str(delta).split(".")[0]
|
||||
|
||||
def log(self, message: str) -> None:
|
||||
stamp = datetime.utcnow().strftime("%H:%M:%S")
|
||||
stamp = datetime.now(timezone.utc).strftime("%H:%M:%S")
|
||||
entry = f"[{stamp}] {message}"
|
||||
self.log_buffer.append(entry)
|
||||
logger.info(f"[{self.name}] {message}")
|
||||
@@ -49,11 +49,11 @@ class BaseService(ABC):
|
||||
|
||||
async def _run_loop(self) -> None:
|
||||
self._running = True
|
||||
self._started_at = datetime.utcnow()
|
||||
self._started_at = datetime.now(timezone.utc)
|
||||
self.log(f"Service started (interval={self.interval_seconds}s)")
|
||||
while self._running:
|
||||
try:
|
||||
self._last_run = datetime.utcnow().isoformat()
|
||||
self._last_run = datetime.now(timezone.utc).isoformat()
|
||||
self._next_run = None
|
||||
await self.run_once()
|
||||
except asyncio.CancelledError:
|
||||
@@ -63,7 +63,7 @@ class BaseService(ABC):
|
||||
self.log(f"Error in run_once: {e}")
|
||||
if not self._running:
|
||||
break
|
||||
self._next_run = datetime.utcnow().isoformat()
|
||||
self._next_run = datetime.now(timezone.utc).isoformat()
|
||||
self.log(f"Sleeping for {self.interval_seconds}s")
|
||||
try:
|
||||
await asyncio.sleep(self.interval_seconds)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -119,7 +119,7 @@ class NewsService(BaseService):
|
||||
continue
|
||||
|
||||
grade = await self._grade_article(article, ai_url, ai_model)
|
||||
now = datetime.utcnow().isoformat()
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
if grade is None:
|
||||
grade_val = 0
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
{% if _user %}
|
||||
<a href="/profile/{{ _user['username'] }}" class="user-avatar-link">
|
||||
<img src="{{ avatar_url('multiavatar', _user['username'], _size) }}" class="avatar-img avatar-{{ _size_class }}" alt="{{ _user['username'] }}" loading="lazy">
|
||||
</a>
|
||||
</a>
|
||||
{% endif %}
|
||||
+10
-6
@@ -2,7 +2,7 @@ import re
|
||||
import secrets
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from passlib.hash import pbkdf2_sha256
|
||||
from fastapi import Request, HTTPException, status
|
||||
from devplacepy.database import get_table
|
||||
@@ -21,12 +21,12 @@ def verify_password(password: str, hashed: str) -> bool:
|
||||
|
||||
def create_session(user_uid: str) -> str:
|
||||
token = secrets.token_hex(32)
|
||||
expires_at = datetime.utcnow() + timedelta(seconds=SESSION_MAX_AGE)
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(seconds=SESSION_MAX_AGE)
|
||||
sessions = get_table("sessions")
|
||||
sessions.insert({
|
||||
"session_token": token,
|
||||
"user_uid": user_uid,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
"expires_at": expires_at.isoformat(),
|
||||
})
|
||||
return token
|
||||
@@ -53,7 +53,9 @@ def get_current_user(request: Request):
|
||||
if not session:
|
||||
return None
|
||||
expires = datetime.fromisoformat(session["expires_at"])
|
||||
if expires < datetime.utcnow():
|
||||
if expires.tzinfo is None:
|
||||
expires = expires.replace(tzinfo=timezone.utc)
|
||||
if expires < datetime.now(timezone.utc):
|
||||
sessions.delete(id=session["id"])
|
||||
return None
|
||||
users = get_table("users")
|
||||
@@ -104,7 +106,9 @@ def make_combined_slug(text: str, uid: str) -> str:
|
||||
|
||||
def time_ago(dt_str: str) -> str:
|
||||
dt = datetime.fromisoformat(dt_str)
|
||||
now = datetime.utcnow()
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
now = datetime.now(timezone.utc)
|
||||
diff = now - dt
|
||||
days = diff.days
|
||||
if days > 30:
|
||||
@@ -147,7 +151,7 @@ def create_mention_notifications(content: str, actor_uid: str, target_url: str)
|
||||
"related_uid": actor_uid,
|
||||
"target_url": target_url,
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user