fix: correct typo in user authentication error message for invalid credentials
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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">📄</span>Load More</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -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">📄</span>Load More</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "_load_more.html" %}
|
||||
</div>
|
||||
|
||||
<aside class="feed-right">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% include "_load_more.html" %}
|
||||
{% else %}
|
||||
<div class="news-empty">
|
||||
<div class="news-empty-icon">📰</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user