From 0442d3fc0e3174152a1883c45decdf26b9801106 Mon Sep 17 00:00:00 2001 From: jlimolina Date: Sat, 31 May 2025 11:23:24 +0200 Subject: [PATCH] =?UTF-8?q?Correcci=C3=B3n=20importaci=C3=B3n=20CSV,=20rob?= =?UTF-8?q?ustez=20en=20restore=5Ffeeds=20y=20scripts=20de=20instalaci?= =?UTF-8?q?=C3=B3n=20para=20PostgreSQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 54 +++++++++--------- categorias.sql | 5 +- continentes.sql | 5 +- install.sh | 107 ++++++++++++++++++++++++----------- paises.sql | 5 +- templates/restore_feeds.html | 11 +++- 6 files changed, 119 insertions(+), 68 deletions(-) diff --git a/app.py b/app.py index 751010c..2cb36bd 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,7 @@ # -*- coding: utf-8 -*- """Flask RSS aggregator — versión PostgreSQL -Cambios principales respecto al original (MySQL): -- mysql.connector → psycopg2 -- DB_CONFIG con claves PostgreSQL -- Función auxiliar get_conn() para abrir conexiones -- Reemplazo de INSERT IGNORE / ON DUPLICATE KEY UPDATE por ON CONFLICT +(Copyright tuyo 😉, soporte robusto de importación desde CSV) """ from flask import Flask, render_template, request, redirect, url_for, Response @@ -32,12 +28,10 @@ DB_CONFIG = { "password": "x", } - def get_conn(): """Devuelve una conexión nueva usando psycopg2 y el diccionario DB_CONFIG.""" return psycopg2.connect(**DB_CONFIG) - MAX_FALLOS = 5 # Número máximo de fallos antes de desactivar el feed # ====================================== @@ -109,7 +103,6 @@ def home(): pais_id=int(pais_id) if pais_id else None, ) - # ====================================== # Gestión de feeds en /feeds # ====================================== @@ -152,7 +145,6 @@ def feeds(): paises=paises, ) - # Añadir feed @app.route("/add", methods=["POST"]) def add_feed(): @@ -180,7 +172,6 @@ def add_feed(): conn.close() return redirect(url_for("feeds")) - # Editar feed @app.route("/edit/", methods=["GET", "POST"]) def edit_feed(feed_id): @@ -230,7 +221,6 @@ def edit_feed(feed_id): conn.close() return render_template("edit_feed.html", feed=feed, categorias=categorias, paises=paises) - # Eliminar feed @app.route("/delete/") def delete_feed(feed_id): @@ -247,7 +237,6 @@ def delete_feed(feed_id): conn.close() return redirect(url_for("feeds")) - # Backup de feeds a CSV @app.route("/backup_feeds") def backup_feeds(): @@ -285,8 +274,7 @@ def backup_feeds(): headers={"Content-Disposition": "attachment;filename=feeds_backup.csv"}, ) - -# Restaurar feeds desde CSV +# Restaurar feeds desde CSV (robusto: bool/int/None/códigos) @app.route("/restore_feeds", methods=["GET", "POST"]) def restore_feeds(): msg = "" @@ -301,8 +289,23 @@ def restore_feeds(): conn = get_conn() cursor = conn.cursor() n_ok = 0 - for row in rows: + msg_lines = [] + for i, row in enumerate(rows, 1): try: + # -- robusto para activo (admite True/False/1/0/t/f/yes/no/vacío) + activo_val = str(row.get("activo", "")).strip().lower() + if activo_val in ["1", "true", "t", "yes"]: + activo = True + elif activo_val in ["0", "false", "f", "no"]: + activo = False + else: + activo = True # valor por defecto + + idioma = row.get("idioma", None) + idioma = idioma.strip() if idioma else None + if idioma == "": + idioma = None + cursor.execute( """ INSERT INTO feeds ( @@ -320,35 +323,35 @@ def restore_feeds(): fallos = EXCLUDED.fallos; """, { - "id": row.get("id"), + "id": int(row.get("id")), "nombre": row["nombre"], "descripcion": row.get("descripcion") or "", "url": row["url"], - "categoria_id": row["categoria_id"], - "pais_id": row["pais_id"], - "idioma": row.get("idioma"), - "activo": bool(int(row["activo"])), + "categoria_id": int(row["categoria_id"]) if row["categoria_id"] else None, + "pais_id": int(row["pais_id"]) if row["pais_id"] else None, + "idioma": idioma, + "activo": activo, "fallos": int(row.get("fallos", 0)), }, ) n_ok += 1 except Exception as e: - app.logger.error(f"Error insertando feed {row}: {e}") + app.logger.error(f"Error insertando feed fila {i}: {e}") + msg_lines.append(f"Error en fila {i}: {e}") conn.commit() conn.close() msg = f"Feeds restaurados correctamente: {n_ok}" + if msg_lines: + msg += "
" + "
".join(msg_lines) return render_template("restore_feeds.html", msg=msg) - @app.route("/noticias") def show_noticias(): return home() - # ================================ # Lógica de procesado de feeds con control de fallos # ================================ - def sumar_fallo_feed(cursor, feed_id): cursor.execute("UPDATE feeds SET fallos = fallos + 1 WHERE id = %s", (feed_id,)) cursor.execute("SELECT fallos FROM feeds WHERE id = %s", (feed_id,)) @@ -357,11 +360,9 @@ def sumar_fallo_feed(cursor, feed_id): cursor.execute("UPDATE feeds SET activo = FALSE WHERE id = %s", (feed_id,)) return fallos - def resetear_fallos_feed(cursor, feed_id): cursor.execute("UPDATE feeds SET fallos = 0 WHERE id = %s", (feed_id,)) - def fetch_and_store(): conn = None try: @@ -459,7 +460,6 @@ def fetch_and_store(): conn.close() app.logger.info(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Feeds procesados.") - # --------------------------------------------------------------------------- # Lanzador de la aplicación + scheduler # --------------------------------------------------------------------------- diff --git a/categorias.sql b/categorias.sql index 5241a01..1873ea7 100644 --- a/categorias.sql +++ b/categorias.sql @@ -1,4 +1,4 @@ -INSERT IGNORE INTO categorias_estandar (nombre) VALUES +INSERT INTO categorias (nombre) VALUES ('Ciencia'), ('Cultura'), ('Deportes'), @@ -13,5 +13,6 @@ INSERT IGNORE INTO categorias_estandar (nombre) VALUES ('Salud'), ('Sociedad'), ('Tecnología'), -('Viajes'); +('Viajes') +ON CONFLICT DO NOTHING; diff --git a/continentes.sql b/continentes.sql index 33d8d17..0efec4f 100644 --- a/continentes.sql +++ b/continentes.sql @@ -1,8 +1,9 @@ -INSERT IGNORE INTO continentes (id, nombre) VALUES +INSERT INTO continentes (id, nombre) VALUES (1, 'África'), (2, 'América'), (3, 'Asia'), (4, 'Europa'), (5, 'Oceanía'), -(6, 'Antártida'); +(6, 'Antártida') +ON CONFLICT (id) DO NOTHING; diff --git a/install.sh b/install.sh index 5d30199..6c48f62 100644 --- a/install.sh +++ b/install.sh @@ -1,62 +1,103 @@ #!/bin/bash +set -e + # ========= CONFIGURACIÓN ========= APP_NAME="rss" -USER=$(whoami) -APP_DIR="/home/$USER/rss" +APP_DIR="$(cd "$(dirname "$0")" && pwd)" # SIEMPRE el directorio del script PYTHON_ENV="$APP_DIR/venv" SERVICE_FILE="/etc/systemd/system/$APP_NAME.service" FLASK_FILE="app.py" -DB_NAME="noticiasrss" -MYSQL_USER="root" +DB_NAME="rss" +DB_USER="rss" +DB_PASS="x" -# ========= PEDIR CONTRASEÑA MYSQL ========= -read -s -p "Introduce la contraseña MySQL para '$MYSQL_USER': " MYSQL_PASS -echo +# ========= INSTALAR POSTGRESQL (ÚLTIMA VERSIÓN RECOMENDADA) ========= +echo "🟢 Instalando PostgreSQL (repositorio oficial)..." +sudo apt update +sudo apt install -y wget ca-certificates -# ========= BASE DE DATOS Y TABLAS ========= -echo "🛠️ Verificando/creando base de datos '$DB_NAME'..." -mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "CREATE DATABASE IF NOT EXISTS $DB_NAME DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" +# Añade el repositorio oficial de PostgreSQL (ajusta para tu distro si hace falta) +echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list +wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - +sudo apt update +sudo apt install -y postgresql postgresql-contrib -echo "📐 Verificando tablas necesarias..." -mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" "$DB_NAME" < {% if msg %}
- {{ msg }} + {% if "Error" in msg or "Error en fila" in msg %} +
{{ msg|safe }}
+ {% else %} +
{{ msg|safe }}
+ {% endif %}
{% endif %}

@@ -18,9 +22,12 @@ id, nombre, [descripcion,] url, categoria_id, categoria, pais_id, pais, idioma, activo, fallos
Las columnas descripcion e idioma son opcionales.
- idioma debe ser el código ISO 639-1 de dos letras (ej: es, en, fr...). + activo puede ser: True, False, 1 o 0.
+ idioma debe ser el código ISO 639-1 de dos letras (ej: es, en, fr...).
+ Si falta alguna columna, la restauración puede fallar o ignorar ese campo.

← Volver a feeds {% endblock %} +