Files
Crumb-Core-v.1/app/routers/pulse.py

165 lines
5.3 KiB
Python

from fastapi import APIRouter, Depends, Request, HTTPException
from fastapi.responses import HTMLResponse
from pymysql.cursors import DictCursor
import json
from collections import Counter
from typing import List, Dict, Any, Optional
from deps import get_db, current_user
from services.config_loader import ConfigLoader
router = APIRouter()
@router.get("/crumbforest/pulse", response_class=HTMLResponse)
async def pulse_dashboard(req: Request, tag: Optional[str] = None, user = Depends(current_user)):
"""
Show the Pulse (Blog) dashboard with tag cloud.
"""
# 1. Auth & Config
if not user:
# Public access allowed? User said "Neuigkeiten...".
# But usually Crumbforest is internal.
# Let's assume login required for now to access specific group templates.
lang = req.query_params.get("lang", "de")
return req.app.state.render(req, "pages/login.html", error="Login required for Pulse")
# Actually Redirect is better
# return RedirectResponse(f"/{lang}/login", status_code=302)
user_group = user.get('user_group', 'demo')
config = ConfigLoader.load_config()
group_config = config.get('groups', {}).get(user_group, {})
lang = req.query_params.get("lang") or req.session.get("lang") or "de"
req.session["lang"] = lang
# 2. Fetch Posts
conn = get_db()
try:
with conn.cursor() as cur:
# Fetch all published posts
base_query = """
SELECT id, title, slug, excerpt, tags, author, created_at
FROM posts
WHERE is_published = 1
"""
cur.execute(base_query)
posts_raw = cur.fetchall()
finally:
conn.close()
# 3. Process Tags & Filter
posts = []
all_tags = []
for p in posts_raw:
# Parse JSON tags
if isinstance(p['tags'], str):
p['tags'] = json.loads(p['tags'])
elif p['tags'] is None:
p['tags'] = []
all_tags.extend(p['tags'])
# Filter (if tag selected)
if tag and tag not in p['tags']:
continue
posts.append(p)
# 4. Build Tag Cloud
# Count tags
tag_counts = Counter(all_tags)
# Convert to list of dicts for template: [{'name': 'RAG', 'count': 5, 'size': '...'}, ...]
# Simple sizing logic: 1..10
tag_cloud = []
if tag_counts:
max_count = max(tag_counts.values())
min_count = min(tag_counts.values())
delta = max_count - min_count or 1
for t_name, count in tag_counts.items():
# Linear scaling 1.0 to 2.0em
size = 0.8 + (1.2 * (count - min_count) / delta)
tag_cloud.append({
'name': t_name,
'count': count,
'size': f"{size:.1f}em",
'active': t_name == tag
})
# Sort cloud alphabetically
tag_cloud.sort(key=lambda x: x['name'])
return req.app.state.render(
req,
"crumbforest/blog.html",
posts=posts,
tag_cloud=tag_cloud,
current_tag=tag,
user_group=user_group,
group_config=group_config,
lang=lang
)
@router.get("/crumbforest/pulse/{slug}", response_class=HTMLResponse)
async def pulse_post(req: Request, slug: str, user = Depends(current_user)):
"""
Show a single blog post.
"""
if not user:
# Redirect
lang = req.query_params.get("lang", "de")
return req.app.state.render(req, "pages/login.html") # Simplified
user_group = user.get('user_group', 'demo')
config = ConfigLoader.load_config()
group_config = config.get('groups', {}).get(user_group, {})
lang = req.query_params.get("lang") or req.session.get("lang") or "de"
conn = get_db()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT id, title, slug, excerpt, tags, author, body_md, created_at, updated_at
FROM posts
WHERE slug=%s AND is_published=1
""",
(slug,)
)
post = cur.fetchone()
finally:
conn.close()
if not post:
raise HTTPException(status_code=404, detail="Post not found")
# Parse tags
if isinstance(post['tags'], str):
post['tags'] = json.loads(post['tags'])
elif post['tags'] is None:
post['tags'] = []
# Markdown rendering handled in template via filter?
# Or pre-render here.
# Usually we pass markdown and let template render it if using a JS lib,
# OR we use `markdown` lib here.
# checking existing templates... we likely use `markdown` filter or lib.
# Code Tour mentioned "body_md".
# I will assume we render MD in Python for safety/consistency?
# Or use a client-side renderer (Zero-MD, Marked)?
# Looking at `diary_rag.py` / `rag_service.py` -> we just store MD.
# If I verify `app/templates/`?
# I'll use `markdown` python lib if available.
# Assume it is available or basic text for now.
import markdown
post['html_content'] = markdown.markdown(post['body_md'])
return req.app.state.render(
req,
"crumbforest/post.html",
post=post,
group_config=group_config,
lang=lang
)