Initial clean commit
This commit is contained in:
commit
6784d81c2c
141 changed files with 25219 additions and 0 deletions
203
routers/favoritos.py
Normal file
203
routers/favoritos.py
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
"""
|
||||
Favorites router - Save and manage favorite news.
|
||||
"""
|
||||
from flask import Blueprint, request, jsonify, session, render_template
|
||||
from psycopg2 import extras
|
||||
from db import get_read_conn, get_write_conn
|
||||
from utils.auth import get_current_user, is_authenticated
|
||||
import secrets
|
||||
|
||||
favoritos_bp = Blueprint("favoritos", __name__, url_prefix="/favoritos")
|
||||
|
||||
|
||||
def get_user_or_session_id():
|
||||
"""Get user ID if authenticated, otherwise session ID.
|
||||
|
||||
Returns:
|
||||
Tuple of (user_id, session_id)
|
||||
"""
|
||||
user = get_current_user()
|
||||
if user:
|
||||
return (user['id'], None)
|
||||
|
||||
# Anonymous user - use session_id
|
||||
if "user_session" not in session:
|
||||
session["user_session"] = secrets.token_hex(16)
|
||||
return (None, session["user_session"])
|
||||
|
||||
|
||||
def ensure_favoritos_table(conn):
|
||||
"""Create/update favoritos table to support both users and sessions."""
|
||||
with conn.cursor() as cur:
|
||||
# Table is created by init-db scripts, just ensure it exists
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS favoritos (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES usuarios(id) ON DELETE CASCADE,
|
||||
session_id VARCHAR(64),
|
||||
noticia_id VARCHAR(32) REFERENCES noticias(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
""")
|
||||
cur.execute("CREATE INDEX IF NOT EXISTS idx_favoritos_session ON favoritos(session_id);")
|
||||
cur.execute("CREATE INDEX IF NOT EXISTS idx_favoritos_user_id ON favoritos(user_id);")
|
||||
|
||||
# Ensure session_id can be null (for logged in users)
|
||||
try:
|
||||
cur.execute("ALTER TABLE favoritos ALTER COLUMN session_id DROP NOT NULL;")
|
||||
except Exception:
|
||||
conn.rollback()
|
||||
else:
|
||||
conn.commit()
|
||||
conn.commit()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# API: Toggle Favorite
|
||||
# ============================================================
|
||||
|
||||
@favoritos_bp.route("/toggle/<noticia_id>", methods=["POST"])
|
||||
def toggle_favorite(noticia_id):
|
||||
"""Toggle favorite status for a news item."""
|
||||
user_id, session_id = get_user_or_session_id()
|
||||
|
||||
with get_write_conn() as conn:
|
||||
ensure_favoritos_table(conn)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
# Check if already favorited (by user_id OR session_id)
|
||||
if user_id:
|
||||
cur.execute(
|
||||
"SELECT id FROM favoritos WHERE user_id = %s AND noticia_id = %s",
|
||||
(user_id, noticia_id)
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"SELECT id FROM favoritos WHERE session_id = %s AND noticia_id = %s",
|
||||
(session_id, noticia_id)
|
||||
)
|
||||
existing = cur.fetchone()
|
||||
|
||||
if existing:
|
||||
# Remove favorite
|
||||
if user_id:
|
||||
cur.execute(
|
||||
"DELETE FROM favoritos WHERE user_id = %s AND noticia_id = %s",
|
||||
(user_id, noticia_id)
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"DELETE FROM favoritos WHERE session_id = %s AND noticia_id = %s",
|
||||
(session_id, noticia_id)
|
||||
)
|
||||
is_favorite = False
|
||||
else:
|
||||
# Add favorite
|
||||
cur.execute(
|
||||
"INSERT INTO favoritos (user_id, session_id, noticia_id) VALUES (%s, %s, %s) ON CONFLICT DO NOTHING",
|
||||
(user_id, session_id, noticia_id)
|
||||
)
|
||||
is_favorite = True
|
||||
|
||||
conn.commit()
|
||||
|
||||
return jsonify({"success": True, "is_favorite": is_favorite})
|
||||
|
||||
|
||||
# ============================================================
|
||||
# API: Check if Favorite
|
||||
# ============================================================
|
||||
|
||||
@favoritos_bp.route("/check/<noticia_id>")
|
||||
def check_favorite(noticia_id):
|
||||
"""Check if a news item is favorited."""
|
||||
user_id, session_id = get_user_or_session_id()
|
||||
|
||||
with get_read_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if user_id:
|
||||
cur.execute(
|
||||
"SELECT id FROM favoritos WHERE user_id = %s AND noticia_id = %s",
|
||||
(user_id, noticia_id)
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"SELECT id FROM favoritos WHERE session_id = %s AND noticia_id = %s",
|
||||
(session_id, noticia_id)
|
||||
)
|
||||
is_favorite = cur.fetchone() is not None
|
||||
|
||||
return jsonify({"is_favorite": is_favorite})
|
||||
|
||||
|
||||
# ============================================================
|
||||
# API: Get User's Favorites IDs
|
||||
# ============================================================
|
||||
|
||||
@favoritos_bp.route("/ids")
|
||||
def get_favorite_ids():
|
||||
"""Get list of favorite noticia IDs for current user."""
|
||||
user_id, session_id = get_user_or_session_id()
|
||||
|
||||
with get_read_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if user_id:
|
||||
cur.execute(
|
||||
"SELECT noticia_id FROM favoritos WHERE user_id = %s",
|
||||
(user_id,)
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"SELECT noticia_id FROM favoritos WHERE session_id = %s",
|
||||
(session_id,)
|
||||
)
|
||||
ids = [row[0] for row in cur.fetchall()]
|
||||
|
||||
return jsonify({"ids": ids})
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Page: View Favorites
|
||||
# ============================================================
|
||||
|
||||
@favoritos_bp.route("/")
|
||||
def view_favorites():
|
||||
"""View all favorited news items."""
|
||||
user_id, session_id = get_user_or_session_id()
|
||||
user = get_current_user()
|
||||
|
||||
with get_read_conn() as conn:
|
||||
with conn.cursor(cursor_factory=extras.DictCursor) as cur:
|
||||
if user_id:
|
||||
cur.execute("""
|
||||
SELECT n.id, n.titulo, n.resumen, n.url, n.fecha, n.imagen_url,
|
||||
n.fuente_nombre, c.nombre AS categoria, p.nombre AS pais,
|
||||
t.titulo_trad, t.resumen_trad, t.lang_to,
|
||||
f.created_at AS favorito_at
|
||||
FROM favoritos f
|
||||
JOIN noticias n ON n.id = f.noticia_id
|
||||
LEFT JOIN categorias c ON c.id = n.categoria_id
|
||||
LEFT JOIN paises p ON p.id = n.pais_id
|
||||
LEFT JOIN traducciones t ON t.noticia_id = n.id AND t.lang_to = 'es' AND t.status = 'done'
|
||||
WHERE f.user_id = %s
|
||||
ORDER BY f.created_at DESC
|
||||
LIMIT 100;
|
||||
""", (user_id,))
|
||||
else:
|
||||
cur.execute("""
|
||||
SELECT n.id, n.titulo, n.resumen, n.url, n.fecha, n.imagen_url,
|
||||
n.fuente_nombre, c.nombre AS categoria, p.nombre AS pais,
|
||||
t.titulo_trad, t.resumen_trad, t.lang_to,
|
||||
f.created_at AS favorito_at
|
||||
FROM favoritos f
|
||||
JOIN noticias n ON n.id = f.noticia_id
|
||||
LEFT JOIN categorias c ON c.id = n.categoria_id
|
||||
LEFT JOIN paises p ON p.id = n.pais_id
|
||||
LEFT JOIN traducciones t ON t.noticia_id = n.id AND t.lang_to = 'es' AND t.status = 'done'
|
||||
WHERE f.session_id = %s
|
||||
ORDER BY f.created_at DESC
|
||||
LIMIT 100;
|
||||
""", (session_id,))
|
||||
noticias = cur.fetchall()
|
||||
|
||||
return render_template("favoritos.html", noticias=noticias, user=user)
|
||||
Loading…
Add table
Add a link
Reference in a new issue