fix: correct typo in user authentication error message for invalid credentials

This commit is contained in:
2026-06-05 03:36:18 +00:00
parent 7c228b684b
commit 50b784b472
16 changed files with 256 additions and 55 deletions
+15
View File
@@ -528,6 +528,21 @@ def get_target_owner_uid(target_type: str, target_uid: str) -> str | None:
return row["user_uid"] if row else None
PAGE_SIZE = 25
def paginate(table, *clauses, before=None, order=None, cursor_field="created_at", **filters):
order = order or ["-" + cursor_field]
clauses = list(clauses)
if before:
clauses.append(table.table.columns[cursor_field] < before)
rows = list(table.find(*clauses, **filters, order_by=order, _limit=PAGE_SIZE + 1))
has_more = len(rows) > PAGE_SIZE
rows = rows[:PAGE_SIZE]
next_cursor = rows[-1][cursor_field] if has_more and rows else None
return rows, next_cursor
def build_pagination(page, total, per_page=25):
total_pages = max(1, __import__("math").ceil(total / per_page))
page = max(1, min(page, total_pages))
+4 -18
View File
@@ -1,7 +1,7 @@
import logging
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, get_site_stats, get_top_authors
from devplacepy.database import get_table, get_daily_topic, get_users_by_uids, get_comment_counts_by_post_uids, get_site_stats, get_top_authors, paginate
from devplacepy.attachments import get_attachments_batch
from devplacepy.content import enrich_items
from devplacepy.templating import templates
@@ -11,8 +11,6 @@ from devplacepy.seo import list_page_seo
logger = logging.getLogger(__name__)
router = APIRouter()
PAGE_SIZE = 25
def get_feed_posts(user, tab: str = "all", topic: str = None, before: str = None):
posts_table = get_table("posts")
@@ -25,22 +23,10 @@ def get_feed_posts(user, tab: str = "all", topic: str = None, before: str = None
following = [f["following_uid"] for f in follows.find(follower_uid=user["uid"])]
if not following:
return [], None
clauses = [posts_table.table.columns.user_uid.in_(following)]
if before:
clauses.append(posts_table.table.columns.created_at < before)
posts = list(posts_table.find(*clauses, order_by=order, _limit=PAGE_SIZE + 1))
posts, next_cursor = paginate(posts_table, posts_table.table.columns.user_uid.in_(following), before=before, order=order)
else:
filters = {}
if topic:
filters["topic"] = topic
if before:
filters["created_at"] = {"<": before}
posts = list(posts_table.find(**filters, order_by=order, _limit=PAGE_SIZE + 1))
has_more = len(posts) > PAGE_SIZE
posts = posts[:PAGE_SIZE]
next_cursor = posts[-1]["created_at"] if has_more and posts else None
filters = {"topic": topic} if topic else {}
posts, next_cursor = paginate(posts_table, before=before, order=order, **filters)
if not posts:
return [], next_cursor
+11 -10
View File
@@ -3,7 +3,7 @@ from typing import Annotated
from fastapi import APIRouter, Request, Form
from devplacepy.models import GistForm, GistEditForm
from fastapi.responses import HTMLResponse, RedirectResponse
from devplacepy.database import get_table, get_users_by_uids, get_gist_languages
from devplacepy.database import get_table, get_users_by_uids, get_gist_languages, paginate
from devplacepy.content import load_detail, edit_content_item, delete_content_item, enrich_items, create_content_item, detail_context
from devplacepy.templating import templates
from devplacepy.utils import get_current_user, require_user, not_found, XP_GIST
@@ -23,7 +23,7 @@ LANGUAGES = [
]
def get_gists_list(user_uid=None, language=None, viewer=None):
def get_gists_list(user_uid=None, language=None, before=None, viewer=None):
gists_table = get_table("gists")
filters = {}
if user_uid:
@@ -31,20 +31,20 @@ def get_gists_list(user_uid=None, language=None, viewer=None):
if language:
filters["language"] = language
all_gists = list(gists_table.find(**filters, order_by=["-created_at"]))
total = gists_table.count(**filters)
gists, next_cursor = paginate(gists_table, before=before, **filters)
if not all_gists:
return []
if not gists:
return [], next_cursor, total
users_map = get_users_by_uids([g["user_uid"] for g in all_gists])
return enrich_items(all_gists, "gist", users_map, user=viewer)
users_map = get_users_by_uids([g["user_uid"] for g in gists])
return enrich_items(gists, "gist", users_map, user=viewer), next_cursor, total
@router.get("", response_class=HTMLResponse)
async def gists_page(request: Request, language: str = None, user_uid: str = None):
async def gists_page(request: Request, language: str = None, user_uid: str = None, before: str = None):
user = get_current_user(request)
gists_data = get_gists_list(user_uid, language, viewer=user)
total_count = len(gists_data)
gists_data, next_cursor, total_count = get_gists_list(user_uid, language, before, viewer=user)
seo_ctx = list_page_seo(
request,
title="Gists",
@@ -60,6 +60,7 @@ async def gists_page(request: Request, language: str = None, user_uid: str = Non
"user": user,
"gists": gists_data,
"total_count": total_count,
"next_cursor": next_cursor,
"current_language": language,
"languages": LANGUAGES,
"gist_language_codes": get_gist_languages(),
+9 -5
View File
@@ -2,7 +2,7 @@ import logging
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, get_news_images_by_uids
from devplacepy.database import get_table, db, load_comments, resolve_by_slug, get_news_images_by_uids, paginate
from devplacepy.templating import templates
from devplacepy.utils import get_current_user, time_ago, not_found
from devplacepy.seo import base_seo_context, website_schema, site_url, news_article_schema, list_page_seo
@@ -14,16 +14,19 @@ NEWS_MAX_AGE_DAYS = 4
@router.get("", response_class=HTMLResponse)
async def news_page(request: Request):
async def news_page(request: Request, before: str = None):
user = get_current_user(request)
cutoff = (datetime.now(timezone.utc) - timedelta(days=NEWS_MAX_AGE_DAYS)).isoformat()
news_table = get_table("news")
articles = list(news_table.find(
articles, next_cursor = paginate(
news_table,
before=before,
order=["-grade", "-synced_at"],
cursor_field="synced_at",
status="published",
synced_at={">=": cutoff},
order_by=["-grade", "-synced_at"],
))
)
article_uids = [a["uid"] for a in articles]
images_by_news = get_news_images_by_uids(article_uids)
@@ -49,6 +52,7 @@ async def news_page(request: Request):
"request": request,
"user": user,
"articles": enriched,
"next_cursor": next_cursor,
})
+14 -11
View File
@@ -4,7 +4,7 @@ from sqlalchemy import or_
from fastapi import APIRouter, Request, Form
from devplacepy.models import ProjectForm
from fastapi.responses import HTMLResponse, RedirectResponse
from devplacepy.database import get_table, get_users_by_uids, get_site_stats, get_user_votes
from devplacepy.database import get_table, get_users_by_uids, get_site_stats, get_user_votes, paginate
from devplacepy.content import load_detail, delete_content_item, create_content_item, detail_context
from devplacepy.templating import templates
from devplacepy.utils import get_current_user, require_user, not_found, XP_PROJECT
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
router = APIRouter()
def get_projects_list(tab: str = "recent", search: str = "", user_uid: str = None, project_type: str = None, viewer: dict = None):
def get_projects_list(tab: str = "recent", search: str = "", user_uid: str = None, project_type: str = None, before: str = None, viewer: dict = None):
projects = get_table("projects")
filters = {}
@@ -31,17 +31,18 @@ def get_projects_list(tab: str = "recent", search: str = "", user_uid: str = Non
clauses.append(or_(projects.table.columns.title.ilike(like), projects.table.columns.description.ilike(like)))
order = ["-stars", "-created_at"] if tab == "popular" else ["-created_at"]
all_projects = list(projects.find(*clauses, **filters, order_by=order))
total = projects.count(*clauses, **filters)
page, next_cursor = paginate(projects, *clauses, before=before, order=order, **filters)
if all_projects:
users_map = get_users_by_uids([p["user_uid"] for p in all_projects])
my_votes = get_user_votes(viewer["uid"], [p["uid"] for p in all_projects]) if viewer else {}
for p in all_projects:
if page:
users_map = get_users_by_uids([p["user_uid"] for p in page])
my_votes = get_user_votes(viewer["uid"], [p["uid"] for p in page]) if viewer else {}
for p in page:
author = users_map.get(p["user_uid"])
p["author_name"] = author["username"] if author else "Unknown"
p["my_vote"] = my_votes.get(p["uid"], 0)
return all_projects
return page, next_cursor, total
@router.get("", response_class=HTMLResponse)
@@ -51,15 +52,16 @@ async def projects_page(
search: str = "",
user_uid: str = None,
project_type: str = None,
before: str = None,
):
user = get_current_user(request)
projects = get_projects_list(tab, search, user_uid, project_type, viewer=user)
projects, next_cursor, total_count = get_projects_list(tab, search, user_uid, project_type, before, viewer=user)
total_members = get_site_stats()["total_members"]
seo_ctx = list_page_seo(
request,
title="Projects",
description=f"Explore {len(projects)} developer projects on DevPlace. Games, software, mobile apps and more.",
description=f"Explore {total_count} developer projects on DevPlace. Games, software, mobile apps and more.",
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "Projects", "url": "/projects"},
@@ -73,7 +75,8 @@ async def projects_page(
"current_tab": tab,
"search": search,
"project_type": project_type,
"total_count": len(projects),
"total_count": total_count,
"next_cursor": next_cursor,
"total_members": total_members,
})
+5
View File
@@ -0,0 +1,5 @@
{% if next_cursor %}
<div class="load-more-wrap">
<a href="{{ request.url.path }}?{{ request.url.include_query_params(before=next_cursor).query }}" class="btn btn-secondary btn-sm"><span class="icon">&#x1F4C4;</span>Load More</a>
</div>
{% endif %}
+1 -5
View File
@@ -61,11 +61,7 @@
{% endfor %}
</div>
{% if next_cursor %}
<div class="load-more-wrap">
<a href="/feed?before={{ next_cursor }}{% if current_tab %}&tab={{ current_tab }}{% endif %}{% if current_topic %}&topic={{ current_topic }}{% endif %}" class="btn btn-secondary btn-sm"><span class="icon">&#x1F4C4;</span>Load More</a>
</div>
{% endif %}
{% include "_load_more.html" %}
</div>
<aside class="feed-right">
+2
View File
@@ -79,6 +79,8 @@
<div class="empty-state empty-state-full">No gists found. Create one!</div>
{% endfor %}
</div>
{% include "_load_more.html" %}
</div>
</div>
+2
View File
@@ -43,6 +43,8 @@
</article>
{% endfor %}
</div>
{% include "_load_more.html" %}
{% else %}
<div class="news-empty">
<div class="news-empty-icon">&#x1F4F0;</div>
+3 -1
View File
@@ -40,7 +40,7 @@
<h1>Projects</h1>
<span class="subtitle">Discover amazing projects from the DevPlace community</span>
</div>
<div class="projects-count">Showing {{ total_count }} of {{ total_count }} projects</div>
<div class="projects-count">Showing {{ projects|length }} of {{ total_count }} projects</div>
</div>
<div class="projects-tabs">
@@ -90,6 +90,8 @@
<div class="empty-state empty-state-full">No projects found. Create one!</div>
{% endfor %}
</div>
{% include "_load_more.html" %}
</div>
</div>