# retoor import logging from typing import Annotated from fastapi import Depends, APIRouter, Request from devplacepy.models import GistForm, GistEditForm from fastapi.responses import HTMLResponse from devplacepy.database import ( get_table, get_users_by_uids, get_gist_languages, get_recent_comments_by_target_uids, paginate, text_search_clause, resolve_object_url, mark_notifications_read_by_target, ) from devplacepy.content import ( load_detail, edit_content_item, delete_content_item, enrich_items, create_content_item, detail_context, canonical_redirect, first_image_url, ) from devplacepy.utils import get_current_user, require_user, not_found, XP_GIST from devplacepy.seo import ( base_seo_context, site_url, website_schema, software_source_code_schema, list_page_seo, next_page_url, ) from devplacepy.responses import respond, action_result from devplacepy.schemas import GistsOut, GistDetailOut from devplacepy.dependencies import json_or_form logger = logging.getLogger(__name__) router = APIRouter() LANGUAGES = [ ("python", "Python"), ("javascript", "JavaScript"), ("typescript", "TypeScript"), ("html", "HTML"), ("css", "CSS"), ("c", "C"), ("cpp", "C++"), ("java", "Java"), ("go", "Go"), ("rust", "Rust"), ("sql", "SQL"), ("bash", "Bash"), ("yaml", "YAML"), ("json", "JSON"), ("markdown", "Markdown"), ("markdown_rendered", "Markdown Rendered"), ("swift", "Swift"), ("php", "PHP"), ("ruby", "Ruby"), ("kotlin", "Kotlin"), ("lua", "Lua"), ("perl", "Perl"), ("haskell", "Haskell"), ("elixir", "Elixir"), ("r", "R"), ("dart", "Dart"), ("scala", "Scala"), ("plaintext", "Plain Text"), ] def get_gists_list(user_uid=None, language=None, search="", before=None, viewer=None): gists_table = get_table("gists") filters = {} if user_uid: filters["user_uid"] = user_uid if language: filters["language"] = language search_match = text_search_clause( gists_table, search, ("title", "description"), author_field="user_uid" ) clauses = [search_match] if search_match is not None else [] total = gists_table.count(*clauses, deleted_at=None, **filters) gists, next_cursor = paginate( gists_table, *clauses, before=before, viewer_uid=viewer["uid"] if viewer else None, **filters, ) if not gists: return [], next_cursor, total users_map = get_users_by_uids([g["user_uid"] for g in gists]) enriched = enrich_items(gists, "gist", users_map, user=viewer) recent_comments = get_recent_comments_by_target_uids( "gist", [g["uid"] for g in gists], 3, viewer ) for item in enriched: item["recent_comments"] = recent_comments.get(item["gist"]["uid"], []) return enriched, next_cursor, total @router.get("", response_class=HTMLResponse) async def gists_page( request: Request, language: str = None, user_uid: str = None, search: str = "", before: str = None, ): user = get_current_user(request) gists_list, next_cursor, total_count = get_gists_list( user_uid, language, search, before, viewer=user ) seo_ctx = list_page_seo( request, title="Gists", description=f"Browse {total_count} code snippets on DevPlace.", breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "Gists", "url": "/gists"}, ], next_url=next_page_url(request, next_cursor), ) return respond( request, "gists.html", { **seo_ctx, "request": request, "user": user, "gists": gists_list, "total_count": total_count, "next_cursor": next_cursor, "current_language": language, "search": search, "languages": LANGUAGES, "gist_language_codes": get_gist_languages(), }, model=GistsOut, ) @router.get("/{gist_slug}", response_class=HTMLResponse) async def gist_detail(request: Request, gist_slug: str): user = get_current_user(request) detail = load_detail("gists", "gist", gist_slug, user) if not detail: raise not_found("Gist not found") gist = detail["item"] redirect = canonical_redirect("gists", gist, gist_slug) if redirect: return redirect if user: mark_notifications_read_by_target( user["uid"], resolve_object_url("gist", gist["uid"]) ) base = site_url(request) seo_ctx = base_seo_context( request, title=gist.get("title", "Gist"), description=gist.get("description") or "", seo_target=("gist", gist["uid"]), og_type="article", og_image=first_image_url(gist, detail["attachments"]), breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "Gists", "url": "/gists"}, { "name": gist.get("title", "Gist"), "url": f"/gists/{gist['slug'] or gist['uid']}", }, ], schemas=[website_schema(base), software_source_code_schema(gist, base)], ) return respond( request, "gist_detail.html", detail_context( request, user, detail, "gist", seo_ctx, { "languages": LANGUAGES, }, ), model=GistDetailOut, ) @router.post("/create") async def create_gist(request: Request, data: Annotated[GistForm, Depends(json_or_form(GistForm))]): user = require_user(request) title = data.title.strip() description = data.description.strip() source_code = data.source_code.strip() language = data.language valid_languages = {lang[0] for lang in LANGUAGES} if language not in valid_languages: language = "plaintext" uid, gist_slug = create_content_item( "gists", "gist", user, { "title": title, "description": description or None, "source_code": source_code, "language": language, }, title, XP_GIST, "First Gist", description or "", data.attachment_uids, request, ) url = f"/gists/{gist_slug}" return action_result(request, url, data={"uid": uid, "slug": gist_slug, "url": url}) @router.post("/edit/{gist_slug}") async def edit_gist( request: Request, gist_slug: str, data: Annotated[GistEditForm, Depends(json_or_form(GistEditForm))] ): user = require_user(request) language = data.language if language not in {lang[0] for lang in LANGUAGES}: language = "plaintext" return edit_content_item( request, "gists", user, gist_slug, { "title": data.title.strip(), "description": data.description.strip() or None, "source_code": data.source_code.strip(), "language": language, }, "/gists", target_type="gist", ) @router.post("/delete/{gist_slug}") async def delete_gist(request: Request, gist_slug: str): user = require_user(request) return delete_content_item(request, "gists", "gist", user, gist_slug, "/gists")