130 lines
5.1 KiB
Python
Raw Normal View History

2026-05-10 09:08:12 +02:00
import logging
2026-05-23 05:44:04 +02:00
from typing import Annotated
from fastapi import APIRouter, Request, Form
from devplacepy.models import ProfileForm
2026-05-12 13:08:38 +02:00
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from devplacepy.database import get_table, db
2026-05-10 09:08:12 +02:00
from devplacepy.templating import templates
from devplacepy.utils import get_current_user, require_user, time_ago
2026-05-11 05:30:51 +02:00
from devplacepy.seo import base_seo_context, site_url, website_schema, breadcrumb_schema, profile_page_schema, combine
2026-05-10 09:08:12 +02:00
logger = logging.getLogger(__name__)
router = APIRouter()
2026-05-23 05:44:04 +02:00
@router.get("/search")
async def search_users(request: Request, q: str = ""):
require_user(request)
if not q or len(q) < 1:
return JSONResponse({"results": []})
if "users" in db.tables:
rows = db.query(
"SELECT uid, username FROM users WHERE username LIKE :q LIMIT 10",
q=f"%{q}%",
)
results = [{"uid": r["uid"], "username": r["username"]} for r in rows]
else:
results = []
return JSONResponse({"results": results})
2026-05-10 09:08:12 +02:00
@router.get("/{username}", response_class=HTMLResponse)
async def profile_page(request: Request, username: str, tab: str = "posts"):
current_user = get_current_user(request)
users = get_table("users")
profile_user = users.find_one(username=username)
if not profile_user:
return RedirectResponse(url="/feed", status_code=302)
posts = []
if tab == "posts":
posts_table = get_table("posts")
2026-05-11 03:14:43 +02:00
raw_posts = list(posts_table.find(user_uid=profile_user["uid"], order_by=["-created_at"]))
if raw_posts:
from devplacepy.database import get_comment_counts_by_post_uids
counts = get_comment_counts_by_post_uids([p["uid"] for p in raw_posts])
else:
counts = {}
2026-05-10 09:08:12 +02:00
for p in raw_posts:
posts.append({
"post": p,
"time_ago": time_ago(p["created_at"]),
2026-05-11 03:14:43 +02:00
"comment_count": counts.get(p["uid"], 0),
2026-05-10 09:08:12 +02:00
})
badges = list(get_table("badges").find(user_uid=profile_user["uid"]))
projects = list(get_table("projects").find(user_uid=profile_user["uid"]))
2026-05-13 21:17:57 +02:00
gists_raw = list(get_table("gists").find(user_uid=profile_user["uid"]))
gists = []
for g in gists_raw:
gists.append({"gist": g, "time_ago": time_ago(g["created_at"])})
2026-05-11 03:14:43 +02:00
posts_count = len(posts) or len(list(get_table("posts").find(user_uid=profile_user["uid"])))
2026-05-11 00:41:41 +02:00
activities = []
if tab == "activity":
posts_table = get_table("posts")
for p in posts_table.find(user_uid=profile_user["uid"], order_by=["-created_at"], _limit=10):
2026-05-11 03:14:43 +02:00
activities.append({"type": "post", "content": p.get("title") or p["content"][:80], "time_ago": time_ago(p["created_at"]), "created_at": p["created_at"], "uid": p["uid"]})
2026-05-11 00:41:41 +02:00
comments_table = get_table("comments")
for c in comments_table.find(user_uid=profile_user["uid"], order_by=["-created_at"], _limit=10):
2026-05-13 21:17:57 +02:00
target_uid = c.get("target_uid", c.get("post_uid", ""))
target_type = c.get("target_type", "post")
activities.append({"type": "comment", "content": c["content"][:80], "time_ago": time_ago(c["created_at"]), "created_at": c["created_at"], "uid": target_uid, "target_type": target_type})
2026-05-11 03:14:43 +02:00
activities.sort(key=lambda a: a["created_at"], reverse=True)
2026-05-11 00:41:41 +02:00
is_following = False
if current_user:
follows = get_table("follows")
is_following = bool(follows.find_one(follower_uid=current_user["uid"], following_uid=profile_user["uid"]))
2026-05-10 09:08:12 +02:00
2026-05-11 05:30:51 +02:00
base = site_url(request)
robots = "noindex,follow" if posts_count < 2 else "index,follow"
bio = profile_user.get("bio", "")
desc = bio or f"View {profile_user['username']}'s profile on DevPlace. {posts_count} posts."
seo_ctx = base_seo_context(
request,
title=f"{profile_user['username']} (@{profile_user['username']})",
description=desc,
robots=robots,
2026-05-23 03:21:55 +02:00
og_type="profile",
2026-05-11 05:30:51 +02:00
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": profile_user['username'], "url": f"/profile/{profile_user['username']}"},
],
schemas=[
website_schema(base),
profile_page_schema(profile_user, posts_count, base),
],
)
2026-05-13 23:03:06 +02:00
return templates.TemplateResponse(request, "profile.html", {
2026-05-11 05:30:51 +02:00
**seo_ctx,
2026-05-10 09:08:12 +02:00
"request": request,
"user": current_user,
"profile_user": profile_user,
"posts": posts,
"badges": badges,
"projects": projects,
2026-05-12 15:07:34 +02:00
"gists": gists,
2026-05-10 09:08:12 +02:00
"current_tab": tab,
2026-05-11 00:41:41 +02:00
"posts_count": posts_count,
"is_following": is_following,
"activities": activities,
2026-05-10 09:08:12 +02:00
})
@router.post("/update")
2026-05-23 05:44:04 +02:00
async def update_profile(request: Request, data: Annotated[ProfileForm, Form()]):
2026-05-10 09:08:12 +02:00
user = require_user(request)
users = get_table("users")
2026-05-11 03:14:43 +02:00
users.update({
2026-05-10 09:08:12 +02:00
"uid": user["uid"],
2026-05-23 05:44:04 +02:00
"bio": data.bio.strip(),
"location": data.location.strip(),
"git_link": data.git_link.strip(),
"website": data.website.strip(),
2026-05-11 03:14:43 +02:00
}, ["uid"])
2026-05-10 09:08:12 +02:00
logger.info(f"Profile updated for {user['username']}")
return RedirectResponse(url=f"/profile/{user['username']}", status_code=302)