diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..957fc8d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +# Dockerfile para contenerizar la aplicación Flask de Pokédex +# Usa Python 3.10 slim como base +FROM python:3.10-slim + +# Variables de entorno +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# Instala dependencias del sistema +RUN apt-get update && apt-get install -y \ + build-essential \ + default-libmysqlclient-dev \ + && rm -rf /var/lib/apt/lists/* + +# Crea el directorio de la app +WORKDIR /app + +# Copia los archivos de la aplicación +COPY . /app + +# Instala las dependencias de Python +RUN pip install --upgrade pip && pip install -r requirements.txt + +# Expone el puerto de Flask +EXPOSE 5000 + +# Comando para iniciar la app +CMD ["python", "poke/run.py"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..be4782a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3.8' + +services: + db: + image: mysql:8.0 + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpass + MYSQL_DATABASE: poke + MYSQL_USER: poke_user + MYSQL_PASSWORD: poke_pass + volumes: + - poke_db_data:/var/lib/mysql + ports: + - "3307:3306" + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root --password=rootpass"] + interval: 10s + timeout: 5s + retries: 5 + + web: + build: . + working_dir: /app + volumes: + - .:/app + ports: + - "5000:5000" + env_file: poke/.env + depends_on: + db: + condition: service_healthy + command: python poke/run.py + +volumes: + poke_db_data: diff --git a/docker-entrypoint-initdb.d/init.sql b/docker-entrypoint-initdb.d/init.sql new file mode 100644 index 0000000..eaa85e7 --- /dev/null +++ b/docker-entrypoint-initdb.d/init.sql @@ -0,0 +1,26 @@ + +CREATE TABLE IF NOT EXISTS users ( + id_user INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(85) NOT NULL, + last_name VARCHAR(36) NOT NULL, + email VARCHAR(90) UNIQUE NOT NULL, + password VARCHAR(350) NOT NULL, + imagen LONGBLOB +); + +CREATE TABLE IF NOT EXISTS user_post ( + id INT AUTO_INCREMENT PRIMARY KEY, + message TEXT NOT NULL, + fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + users_id_post INT, + FOREIGN KEY (users_id_post) REFERENCES users(id_user) +); + +CREATE TABLE IF NOT EXISTS notification ( + id INT AUTO_INCREMENT PRIMARY KEY, + receptor_id INT, + sender_id INT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (receptor_id) REFERENCES users(id_user), + FOREIGN KEY (sender_id) REFERENCES users(id_user) +); diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..5ff3932 --- /dev/null +++ b/init.sql @@ -0,0 +1,28 @@ + +CREATE TABLE IF NOT EXISTS users ( + id_user INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(85) NOT NULL, + last_name VARCHAR(36) NOT NULL, + email VARCHAR(90) UNIQUE NOT NULL, + password VARCHAR(350) NOT NULL, + imagen LONGBLOB +); + + +CREATE TABLE IF NOT EXISTS user_post ( + id INT AUTO_INCREMENT PRIMARY KEY, + message TEXT NOT NULL, + fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + users_id_post INT, + FOREIGN KEY (users_id_post) REFERENCES users(id_user) +); + + +CREATE TABLE IF NOT EXISTS notification ( + id INT AUTO_INCREMENT PRIMARY KEY, + receptor_id INT, + sender_id INT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (receptor_id) REFERENCES users(id_user), + FOREIGN KEY (sender_id) REFERENCES users(id_user) +); diff --git a/init_db.sh b/init_db.sh new file mode 100644 index 0000000..0bc8f4d --- /dev/null +++ b/init_db.sh @@ -0,0 +1,7 @@ +# Entrypoint para inicializar la base de datos si es necesario +# Este script se puede usar en el contenedor de la base de datos + +#!/bin/bash +set -e + +mysql -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" < /app/init.sql diff --git a/poke/.env b/poke/.env new file mode 100644 index 0000000..dc020a5 --- /dev/null +++ b/poke/.env @@ -0,0 +1,7 @@ +FLASK_DATABASE_HOST=yamabiko.proxy.rlwy.net +FLASK_DATABASE_USER=root +FLASK_DATABASE_PASSWORD=EgUprnPxtcwpiQjCGYrJuKVkGoPDTapw +FLASK_DATABASE=railway +FLASK_DATABASE_PORT=48098 +SECRET_KEY=mikey +FLASK_DEBUG=1 diff --git a/poke/.gitignore b/poke/.gitignore index 133027a..aaa5e82 100644 --- a/poke/.gitignore +++ b/poke/.gitignore @@ -1,3 +1,4 @@ -.env -app/__pycache__ -venv \ No newline at end of file +.env/ +uploads/ +/app/__pycache__ +../a \ No newline at end of file diff --git a/poke/app/__init__.py b/poke/app/__init__.py index 5c3ce38..13b7903 100644 --- a/poke/app/__init__.py +++ b/poke/app/__init__.py @@ -1,28 +1,37 @@ -from flask import Flask +from flask import Flask, current_app import os +from dotenv import load_dotenv +load_dotenv() + def create_app(): app = Flask(__name__) - + app.config['UPLOAD_FOLDER'] = 'static' app.config.from_mapping( FROM_EMAIL=os.environ.get('FROM_EMAIL'), SECRET_KEY=os.environ.get('SECRET_KEY'), DATABASE_HOST=os.environ.get('FLASK_DATABASE_HOST'), DATABASE_USER=os.environ.get('FLASK_DATABASE_USER'), DATABASE_PASSWORD=os.environ.get('FLASK_DATABASE_PASSWORD'), - DATABASE=os.environ.get('FLASK_DATABASE') + DATABASE=os.environ.get('FLASK_DATABASE'), + DATABASE_PORT=os.environ.get('FLASK_DATABASE_PORT') ) - from . import db + + from . import db db.init_app(app) from . import poke - app.register_blueprint(poke.bp) + + from . import pokedex - app.register_blueprint(pokedex.bp, name='pokedex_blueprint') + app.register_blueprint(pokedex.bppoke, name='pokedex_blueprint') + + from . import perfil + app.register_blueprint(perfil.bpp) return app \ No newline at end of file diff --git a/poke/app/__pycache__/__init__.cpython-310.pyc b/poke/app/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..0996c81 Binary files /dev/null and b/poke/app/__pycache__/__init__.cpython-310.pyc differ diff --git a/poke/app/__pycache__/__init__.cpython-39.pyc b/poke/app/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..8410a73 Binary files /dev/null and b/poke/app/__pycache__/__init__.cpython-39.pyc differ diff --git a/poke/app/__pycache__/db.cpython-310.pyc b/poke/app/__pycache__/db.cpython-310.pyc new file mode 100644 index 0000000..79a70cf Binary files /dev/null and b/poke/app/__pycache__/db.cpython-310.pyc differ diff --git a/poke/app/__pycache__/db.cpython-39.pyc b/poke/app/__pycache__/db.cpython-39.pyc new file mode 100644 index 0000000..1bd5b5d Binary files /dev/null and b/poke/app/__pycache__/db.cpython-39.pyc differ diff --git a/poke/app/__pycache__/perfil.cpython-39.pyc b/poke/app/__pycache__/perfil.cpython-39.pyc new file mode 100644 index 0000000..37039b6 Binary files /dev/null and b/poke/app/__pycache__/perfil.cpython-39.pyc differ diff --git a/poke/app/__pycache__/poke.cpython-310.pyc b/poke/app/__pycache__/poke.cpython-310.pyc new file mode 100644 index 0000000..001b935 Binary files /dev/null and b/poke/app/__pycache__/poke.cpython-310.pyc differ diff --git a/poke/app/__pycache__/poke.cpython-39.pyc b/poke/app/__pycache__/poke.cpython-39.pyc new file mode 100644 index 0000000..5f854a1 Binary files /dev/null and b/poke/app/__pycache__/poke.cpython-39.pyc differ diff --git a/poke/app/__pycache__/pokedex.cpython-310.pyc b/poke/app/__pycache__/pokedex.cpython-310.pyc new file mode 100644 index 0000000..a625fe1 Binary files /dev/null and b/poke/app/__pycache__/pokedex.cpython-310.pyc differ diff --git a/poke/app/__pycache__/pokedex.cpython-39.pyc b/poke/app/__pycache__/pokedex.cpython-39.pyc new file mode 100644 index 0000000..5200083 Binary files /dev/null and b/poke/app/__pycache__/pokedex.cpython-39.pyc differ diff --git a/poke/app/db.py b/poke/app/db.py index 7e5c391..f525a4c 100644 --- a/poke/app/db.py +++ b/poke/app/db.py @@ -6,20 +6,21 @@ def get_db(): if 'db' not in g: g.db = mysql.connector.connect( - host = current_app.config['DATABASE_HOST'], - user = current_app.config['DATABASE_USER'], - password = current_app.config['DATABASE_PASSWORD'], - database = current_app.config['DATABASE'] + host=current_app.config['DATABASE_HOST'], # ✅ sin FLASK_ + user=current_app.config['DATABASE_USER'], + password=current_app.config['DATABASE_PASSWORD'], + database=current_app.config['DATABASE'], + port=int(current_app.config['DATABASE_PORT']) ) + g.c = g.db.cursor(dictionary=True) return g.db, g.c - + def close_db(e=None): db = g.pop('db', None) - if db is not None: db.close() def init_app(app): - app.teardown_appcontext(close_db) \ No newline at end of file + app.teardown_appcontext(close_db) diff --git a/poke/app/perfil.py b/poke/app/perfil.py new file mode 100644 index 0000000..7f50c73 --- /dev/null +++ b/poke/app/perfil.py @@ -0,0 +1,159 @@ +from flask import ( + Blueprint, flash, g, redirect, render_template, request, url_for, current_app +) + +import os +from flask import Flask, render_template, request, flash, redirect, url_for +from werkzeug.utils import secure_filename +import mysql.connector + +from werkzeug.exceptions import abort +from app.poke import login_required +from app.db import get_db +from werkzeug.utils import secure_filename + +import base64 + +import re +import functools + +import base64 +from flask import send_file + +from flask import Flask, render_template, request +from werkzeug.utils import secure_filename +from os import path +import os + + +UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads') +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} + +# Verificar si la carpeta de carga existe, si no, crearla +if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER) + +bpp = Blueprint('perfil', __name__, url_prefix='/perfil', static_folder='static') + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +@bpp.route('/', methods=["GET", "POST"]) +@login_required +def perfil(): + db, c = get_db() + error = None + #Obtener el nombre del usuario de la base de datos + user_id = g.user['id_user'] + # Obtener el ID del usuario actual desde la sesión + + # Consulta para obtener las solicitudes de amistad recibidas por el usuario actual + c.execute(""" + SELECT n.id, u.name AS sender_name, u.last_name AS sender_last_name, n.created_at + FROM notification n + JOIN users u ON n.sender_id = u.id_user + WHERE n.receptor_id = %s + ORDER BY n.created_at DESC + """, (user_id,)) + + received_requests = c.fetchall() + + + c.execute('SELECT name FROM users WHERE id_user = %s', (user_id,)) + user = c.fetchone() + nombre_usuario = user['name'] if user else None + + c.execute('SELECT last_name FROM users WHERE id_user = %s', (user_id,)) + last = c.fetchone() + apellido_usuario = last['last_name'] if last else None + + c.execute("SELECT imagen FROM users WHERE id_user = %s", (user_id,)) + image_data = c.fetchone() + imagen_path = image_data['imagen'] if image_data else None + +#Obtener el nombre del usuario de la base de datos//// + # Antes del bloque if request.method == 'POST' + imagen_base64 = base64.b64encode(imagen_path).decode('utf-8') if imagen_path else None + +# Obtener el Nombre, Apellido y foto de perfil del usuario de la base de datos + if request.method == 'POST': + new_name = request.form['new_name'] + new_lastname = request.form['new_lastname'] + + if not error: + c.execute("UPDATE users SET name = %s, last_name = %s WHERE id_user = %s", (new_name, new_lastname, user_id)) + db.commit() + + + flash('Se ha actualizado el nombre correctamente') + + return redirect(url_for('perfil.perfil')) + + # Convertir los datos de la imagen a una cadena base64 + imagen_base64 = base64.b64encode(imagen_path).decode('utf-8') if imagen_path else None + + return render_template('perfil/perfil.html', nombre_usuario=nombre_usuario, apellido_usuario=apellido_usuario, imagen_base64=imagen_base64, error=error, received_requests=received_requests) + + +# ... + +@bpp.route('/upload', methods=['GET', 'POST']) +@login_required +def upload(): + db, c = get_db() + error = None + #Obtener el nombre del usuario de la base de datos + user_id = g.user['id_user'] + c.execute('SELECT name FROM users WHERE id_user = %s', (user_id,)) + user = c.fetchone() + nombre_usuario = user['name'] if user else None + + c.execute('SELECT last_name FROM users WHERE id_user = %s', (user_id,)) + last = c.fetchone() + apellido_usuario = last['last_name'] if last else None + + c.execute("SELECT imagen FROM users WHERE id_user = %s", (user_id,)) + image_data = c.fetchone() + imagen_path = image_data['imagen'] if image_data else None + +#Obtener el nombre del usuario de la base de datos//// + # Antes del bloque if request.method == 'POST' + imagen_base64 = base64.b64encode(imagen_path).decode('utf-8') if imagen_path else None + if request.method == 'POST': + file = request.files['file'] + user_id = g.user['id_user'] + + if not file: + error = 'File es requerido' + return render_template('perfil/perfil.html', nombre_usuario=nombre_usuario, apellido_usuario=apellido_usuario, imagen_base64=imagen_base64, error=error) + + elif file and allowed_file(file.filename): + image_data = file.read() + # Resto del código... + + # Guardar la imagen en la base de datos + cursor = db.cursor() + cursor.execute("UPDATE users SET imagen = %s WHERE id_user = %s", (image_data, user_id)) + db.commit() + + flash('Se ha actualizado el nombre correctamente') + + return redirect(url_for('perfil.perfil')) + + # Convertir los datos de la imagen a una cadena base64 + + return render_template('perfil/perfil.html') + +@bpp.route('/imagen') +@login_required +def mostrar_imagen(): + user_id = g.user['id_user'] + db, c = get_db() + c.execute("SELECT imagen FROM users WHERE id_user = %s", (user_id,)) + image_data = c.fetchone() + imagen_path = image_data['imagen'] if image_data else None + + if imagen_path: + return send_file(imagen_path) + + return abort(404) diff --git a/poke/app/poke.py b/poke/app/poke.py index f332f7e..82a6767 100644 --- a/poke/app/poke.py +++ b/poke/app/poke.py @@ -1,14 +1,22 @@ -from flask import Blueprint, render_template, url_for, request, redirect, flash, session, g +from flask import Blueprint, render_template, url_for, request, redirect, flash, session, g, abort from werkzeug.security import check_password_hash, generate_password_hash from app.db import get_db import re import functools +import base64 +from flask import send_file + + +from flask import Flask, render_template, request +from werkzeug.utils import secure_filename +from os import path +import os """ ////// -session para crear session del usuario y tenga cosas personalizadas +seccion para crear session del usuario y tenga cosas personalizadas Check_password_hash es para encriptar la contraseña @@ -18,11 +26,50 @@ """ +UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads') +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} + +# Verificar si la carpeta de carga existe, si no, crearla +if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER) + bp = Blueprint('pokedex', __name__, url_prefix='/') +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + @bp.route('/', methods=['POST', 'GET']) def index(): - return render_template('menu/index.html') + db, c = get_db() + + if 'user' in g and g.user is not None: + user_id = g.user['id_user'] # Obtener el ID del usuario actual desde la sesión + + # Consulta para obtener las solicitudes de amistad recibidas por el usuario actual + c.execute(""" + SELECT n.id, u.name AS sender_name, u.last_name AS sender_last_name, n.created_at + FROM notification n + JOIN users u ON n.sender_id = u.id_user + WHERE n.receptor_id = %s + ORDER BY n.created_at DESC + """, (user_id,)) + + received_requests = c.fetchall() + + # Obtener el nombre del usuario de la base de datos + if 'user' in g and g.user is not None: + user_id = g.user['id_user'] + c.execute("SELECT imagen FROM users WHERE id_user = %s", (user_id,)) + image_data = c.fetchone() + imagen_path = image_data['imagen'] if image_data else None + else: + imagen_path = None + + # Antes del bloque if request.method == 'POST': + imagen_base64 = base64.b64encode(imagen_path).decode('utf-8') if imagen_path else None + + return render_template('indexContent/index.html', imagen_base64=imagen_base64 if 'user' in g and g.user else None, received_requests=received_requests if 'user' in g and g.user else None) """ @@ -31,16 +78,21 @@ def index(): """ @bp.route('/signup', methods=['POST', 'GET']) -def signup():#REQUEST DE LOS INPUTS START///////////////////////////////////////////////////////// +def signup(): + db, c = get_db() error = None + #////////////REQUEST DE LOS INPUTS START//////// + if request.method == 'POST': name = request.form['name'] lastname = request.form['lastname'] email = request.form['email'] password = request.form['password'] - db, c = get_db() + confirm_password = request.form['confirm_password'] + #////////////REQUEST DE LOS INPUTS START////////ENDS + #funcion de campos requeridos if not name: error = 'name es requerido' return render_template('registros/signup.html', error=error) @@ -56,53 +108,54 @@ def signup():#REQUEST DE LOS INPUTS START/////////////////////////////////////// if not password: error = 'password es requerido' return render_template('registros/signup.html', error=error) -#REQUEST DE LOS INPUTS END///////////////////////////////////////////////////////// - -# Validar que name y lastname no contengan números ni caracteres especiales - if not re.match("^[a-zA-Z\s]+$", name): - error = 'name solo debe contener letras' + if not confirm_password: + error = 'confirm password es requerido' return render_template('registros/signup.html', error=error) - - elif not re.match("^[a-zA-Z\s]+$", lastname): - error = 'lastname solo debe contener letras' + + if password != confirm_password: + error = 'La contraseña no es igual' return render_template('registros/signup.html', error=error) -#////////////////////////////////////////////////////////////////////////////// - #VALIDACION DE RESGISTROS, PARA QUE NO SE TREPITAN - #START - c.execute('SELECT id_user FROM users WHERE name = %s', (name,)) - user = c.fetchone() + #funcion de campos requeridos ENDS - # Leer y descartar los resultados de la consulta anterior - c.fetchall() + #////////NO ACEPTAR CARECTERES ESPECIALES///////////// + if not re.match("^[a-zA-Z\s]+$", name): + error = 'name solo debe contener letras.' + return redirect(url_for('pokedex.signup')) + + elif not re.match("^[a-zA-Z\s]+$", lastname): + error = 'lastname solo debe contener letras.' + return redirect(url_for('pokedex.signup')) + + elif not re.match(r'^[^\'"=]*$', password): + error = 'Password no puede contener: Guiones, comullas y signo de igual.' + return redirect(url_for('pokedex.signup')) + #////////NO ACEPTAR CARECTERES ESPECIALES/////////////ENDS + #////Esto revisa si el correo ya esta registrado///////// c.execute('SELECT id_user FROM users WHERE email = %s', (email,)) email_user = c.fetchone() - - if user is not None: - error = 'El usuario {} ya está registrado.'.format(name) - elif email_user is not None: + if email_user is not None: error = 'El correo electrónico {} ya está registrado.'.format(email) + #////Esto revisa si el correo ya esta registrado/////////EMDS - #VALIDACION DE REGISTRO END -#//////////////////////////////////////////////////////////////////////////////////// - -#INSERTAR TADOS A LA BASE DE DATOS START/////////////////////////////////////////////////// + #INSERTAR TADOS A LA BASE DE DATOS START/////////////////////////////////////////////////// else: c.execute('INSERT INTO users (name, last_name, email, password) VALUES (%s, %s, %s, %s)', - (name, lastname, email, generate_password_hash(password))) + (name, lastname, email, generate_password_hash(password))) db.commit() flash('¡Registro exitoso! Por favor, inicia sesión.') - return redirect(url_for('pokedex.index')) + return redirect(url_for('pokedex.login')) flash(error) -#INSERTAR TADOS A LA BASE DE DATOS END///////////////////////////////////////////////////////// + #INSERTAR TADOS A LA BASE DE DATOS END///////////////////////////////////////////////////////// return render_template('registros/signup.html', error=error) + @bp.route('/login', methods=['POST', 'GET']) def login(): error = None @@ -111,33 +164,33 @@ def login(): email = request.form['email'] password = request.form['password'] db, c = get_db() - + # Validar que name y lastname no contengan números ni caracteres especiales if not email: - error = 'El correo electrónico es requerido' + error = 'email es requerido' return render_template('registros/login.html', error=error) if not password: - error = 'La contraseña es requerida' + error = 'password es requerido' return render_template('registros/login.html', error=error) - + # Validar que name y lastname no contengan números ni caracteres especiales ENDS + c.execute('SELECT * FROM users WHERE email = %s', (email,)) user = c.fetchone() - if user is None: error = 'Usuario y/o contraseña inválidos' return render_template('registros/login.html', error=error) + elif not check_password_hash(user['password'], password): error = 'Usuario y/o contraseña inválidos' return render_template('registros/login.html', error=error) if error is None: - session.clear() session['user_id'] = user['id_user'] return redirect(url_for('pokedex.index')) flash(error) - return render_template('registros/login.html') + return render_template('registros/login.html', error=error) @bp.before_app_request def load_logged_in_user(): @@ -145,11 +198,17 @@ def load_logged_in_user(): if user_id is None: g.user = None + else: db, c = get_db() c.execute('SELECT * FROM users WHERE id_user = %s', (user_id,)) g.user = c.fetchone() + + +#el decorador login_required se utiliza para proteger +# ciertas vistas o funciones de una aplicación web para +# asegurarse de que el usuario haya iniciado sesión antes de acceder a ellas. def login_required(view): @functools.wraps(view) def wrapped_view(**kwargs): @@ -160,6 +219,7 @@ def wrapped_view(**kwargs): return wrapped_view + @bp.route('/logout') def logout(): session.clear() diff --git a/poke/app/pokedex.py b/poke/app/pokedex.py index 5957b82..d64cf43 100644 --- a/poke/app/pokedex.py +++ b/poke/app/pokedex.py @@ -1,15 +1,259 @@ from flask import ( - Blueprint, flash, g, redirect, render_template, request, url_for + Blueprint, flash, g, redirect, render_template, request, url_for, session ) from werkzeug.exceptions import abort - +import requests from app.poke import login_required from app.db import get_db +import base64 +import requests -bp = Blueprint('pokedex', __name__, url_prefix='/pokedex') -@bp.route('/') -@login_required +bppoke = Blueprint('pokedex', __name__, url_prefix='/pokedex') + +class Pokemon: + def __init__(self, name, attact): + self.name = name + self.attact = attact + +@bppoke.route('/') def index(): - return render_template('menu/menu.html') + db, c = get_db() + imagen_base64 = None + received_requests = [] + + # Obtener usuario de la sesión + user = g.get('user') + user_id = None + if user: + user_id = user.get('id_user') + + # Solicitudes de amistad recibidas + try: + c.execute(""" + SELECT n.id, u.name AS sender_name, u.last_name AS sender_last_name, n.created_at + FROM notification n + JOIN users u ON n.sender_id = u.id_user + WHERE n.receptor_id = %s + ORDER BY n.created_at DESC + """, (user_id,)) + received_requests = c.fetchall() + except Exception as db_err: + print(f"[Error DB] Cargando solicitudes: {db_err}") + + # Imagen del usuario + try: + c.execute("SELECT imagen FROM users WHERE id_user = %s", (user_id,)) + image_data = c.fetchone() + if image_data and image_data['imagen']: + imagen_base64 = base64.b64encode(image_data['imagen']).decode('utf-8') + except Exception as db_err: + print(f"[Error DB] Cargando imagen: {db_err}") + + # Clase auxiliar + class Pokemon: + def __init__(self, name, image_url): + self.name = name + self.image_url = image_url + + # Paginación + try: + page = int(request.args.get('page', 1)) + if page < 1: + page = 1 + except ValueError: + page = 1 + + per_page = 12 + offset = (page - 1) * per_page + url = f'https://pokeapi.co/api/v2/pokemon?limit={per_page}&offset={offset}' + + try: + response = requests.get(url) + response.raise_for_status() + data = response.json() + results = data.get('results', []) + total_count = data.get('count', 0) + + pokemons = [] + for result in results: + name = result.get('name') + pokemon_url = result.get('url') + if not name or not pokemon_url: + continue + + try: + pokemon_data = requests.get(pokemon_url).json() + sprites = pokemon_data.get('sprites', {}) + image_url = sprites.get('front_default') + pokemons.append(Pokemon(name, image_url)) + except Exception as e: + print(f"[Error] Cargando pokémon {name}: {e}") + continue + + # Determinar si hay una siguiente página usando el total de pokémon + has_next = (page * per_page) < total_count + + return render_template( + 'menus/pokedex.html', + pokemons=pokemons, + imagen_base64=imagen_base64, + received_requests=received_requests, + page=page, + has_next=has_next + ) + except requests.exceptions.RequestException as e: + print(f"[Error API] {e}") + return "Error cargando Pokémon desde la API. Inténtalo más tarde.", 500 + + +@bppoke.route('/people', methods = ['POST', 'GET']) +@login_required +def people(): + db, c = get_db() + + user_id = g.user['id_user'] # Obtener el ID del usuario actual desde la sesión + + # Consulta para obtener las solicitudes de amistad recibidas por el usuario actual + c.execute(""" + SELECT n.id, u.name AS sender_name, u.last_name AS sender_last_name, n.created_at + FROM notification n + JOIN users u ON n.sender_id = u.id_user + WHERE n.receptor_id = %s + ORDER BY n.created_at DESC + """, (user_id,)) + + received_requests = c.fetchall() + + + + # Resto del código para mostrar las personas y sus imágenes + + if request.method == 'POST': + receptor_id = request.form['id_user'] + sender_id = g.user['id_user'] + c.execute('INSERT INTO notification (receptor_id, sender_id) VALUES (%s, %s)', (receptor_id, sender_id)) + db.commit() + return redirect(url_for(request.endpoint)) + + if g.user: + user_id = g.user['id_user'] + # Obtener la imagen del usuario de la base de datos + c.execute("SELECT imagen FROM users WHERE id_user = %s", (user_id,)) + image_data = c.fetchone() + imagen_path = image_data['imagen'] if image_data else None + + #Obtener el nombre del usuario de la base de datos//// + # Antes del bloque if request.method == 'POST' + imagen_base64 = base64.b64encode(imagen_path).decode('utf-8') if imagen_path else None + + # Obtener el nombre y el apellido de todos los usuarios de la base de datos + c.execute("SELECT id_user, name, last_name, imagen FROM users") + all_users = c.fetchall() + user_id = g.user['id_user'] if g.user else None + + # Filtrar a los usuarios para excluir al usuario actual + users_list = [{'name': user['name'], 'last_name': user['last_name'], 'id_user': user['id_user'], 'imagen': user['imagen']} for user in all_users if user['id_user'] != user_id] + + # Obtener la imagen de cada usuario y codificarla en base64 + imagen_users_base64 = [] + for user in users_list: + if 'imagen' in user and user['imagen']: + imagen_path2 = user['imagen'] + imagen_base642 = base64.b64encode(imagen_path2).decode('utf-8') + imagen_users_base64.append(imagen_base642) + else: + imagen_users_base64.append(None) + return render_template('menus/friends.html', imagen_base64=imagen_base64 if session else None, friends = users_list, imagen_users_base64 = imagen_users_base64, received_requests=received_requests ) + + +@bppoke.route('/posts', methods=['POST', 'GET']) +@login_required +def post(): + db, c = get_db() + error = None + + user_id = g.user['id_user'] # Obtener el ID del usuario actual desde la sesión + + # Consulta para obtener las solicitudes de amistad recibidas por el usuario actual + c.execute(""" + SELECT n.id, u.name AS sender_name, u.last_name AS sender_last_name, n.created_at + FROM notification n + JOIN users u ON n.sender_id = u.id_user + WHERE n.receptor_id = %s + ORDER BY n.created_at DESC + """, (user_id,)) + + received_requests = c.fetchall() + + + # Seleccionar mensajes y datos de usuario (imagen y nombre) + c.execute('SELECT up.message, up.fecha, up.users_id_post, u.name, u.imagen FROM user_post up JOIN users u ON up.users_id_post = u.id_user') + user_data = c.fetchall() + all_message = [] + + for post in user_data: + message = post['message'] + fecha = post['fecha'] + id_post = post['users_id_post'] + name = post['name'] + imagen_base64 = post['imagen'] + + # Decodificar la imagen desde base64 + imagen_decoded = base64.b64encode(imagen_base64).decode('utf-8') if imagen_base64 else None + + # Crear un diccionario con la información del mensaje + message_info = { + 'message': message, + 'fecha': fecha, + 'id': id_post, + 'name': name, + 'imagen': imagen_decoded + } + + all_message.append(message_info) + #Aqui se muestran los posts de todos Ends + + # Obtener el nombre del usuario de la base de datos + if g.user: + user_id = g.user['id_user'] + + c.execute("SELECT name, imagen FROM users WHERE id_user = %s", (user_id,)) + user_data = c.fetchone() + imagen_path = user_data['imagen'] if user_data else None + user_name = user_data['name'] + imagen_base64 = base64.b64encode(imagen_path).decode('utf-8') if imagen_path else None + else: + return abort(401) + + # Antes del bloque if request.method == 'POST': + if request.method == 'POST': + message_user = request.form['message_user'] + id_user = g.user['id_user'] + if not message_user: + error = 'Se necesita algun comentario para poder postear' + return render_template('contenido/posts.html', error = error) + + c.execute('INSERT INTO user_post (message, users_id_post) VALUES (%s, %s)', (message_user, id_user,)) + db.commit() + return redirect(url_for(request.endpoint)) + + return render_template('contenido/posts.html', + imagen_base64=imagen_base64, + username=user_name, + all_message=all_message, + received_requests=received_requests) + + +def buscar(buscar): + pokemon_name = buscar + url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_name}" + + response = requests.get(url) + if response.status_code == 200: + pokemon_data = response.json() + image_url = pokemon_data["sprites"]["front_default"] + print(f"URL de la imagen de {pokemon_name}: {image_url}") + else: + print(f"No se pudo obtener la información de {pokemon_name}") \ No newline at end of file diff --git a/poke/app/static/campana.png b/poke/app/static/campana.png new file mode 100644 index 0000000..b9a837c Binary files /dev/null and b/poke/app/static/campana.png differ diff --git a/poke/app/static/facebook.png b/poke/app/static/facebook.png new file mode 100644 index 0000000..8f9cb4e Binary files /dev/null and b/poke/app/static/facebook.png differ diff --git a/poke/app/static/github.png b/poke/app/static/github.png new file mode 100644 index 0000000..e440081 Binary files /dev/null and b/poke/app/static/github.png differ diff --git a/poke/app/static/img/logo.png b/poke/app/static/img/logo.png new file mode 100644 index 0000000..5113b16 Binary files /dev/null and b/poke/app/static/img/logo.png differ diff --git a/poke/app/static/img/logo_web.png b/poke/app/static/img/logo_web.png new file mode 100644 index 0000000..6479dad Binary files /dev/null and b/poke/app/static/img/logo_web.png differ diff --git a/poke/app/static/img/pikachu.png b/poke/app/static/img/pikachu.png new file mode 100644 index 0000000..1337de1 Binary files /dev/null and b/poke/app/static/img/pikachu.png differ diff --git a/poke/app/static/img/pokedex_logo.png b/poke/app/static/img/pokedex_logo.png new file mode 100644 index 0000000..4f16cf7 Binary files /dev/null and b/poke/app/static/img/pokedex_logo.png differ diff --git a/poke/app/static/linkedin.png b/poke/app/static/linkedin.png new file mode 100644 index 0000000..787274a Binary files /dev/null and b/poke/app/static/linkedin.png differ diff --git a/poke/app/static/no_profile.png b/poke/app/static/no_profile.png new file mode 100644 index 0000000..0ee39ee Binary files /dev/null and b/poke/app/static/no_profile.png differ diff --git a/poke/app/static/sin-perfil.png b/poke/app/static/sin-perfil.png new file mode 100644 index 0000000..5f6fe36 Binary files /dev/null and b/poke/app/static/sin-perfil.png differ diff --git a/poke/app/static/style.css b/poke/app/static/style.css index de9a087..c010617 100644 --- a/poke/app/static/style.css +++ b/poke/app/static/style.css @@ -1,7 +1,15 @@ +.body{ + font-family: 'Rufina', serif; + font-size: 17px; +} + +.posts{ + box-shadow: 5px 5px 5px rgb(194, 194, 194); +} + .layout{ - margin-top:60px; - margin-left: 23%; - width: 60%; + width:70%; + padding-left:20%; } .input{ @@ -17,3 +25,88 @@ .label{ color: rgba(0, 0, 0, 0.504); } + +.img1{ + padding-right: 2%; +} + +.imglogo{ + width: 100px; + padding-top: 3%; +} + +#XDD{ + padding-left: 2%; + padding-right:2%; + padding-bottom: 1%; + padding-top: 1%; +} + +.pika{ + +} + +.listapokemones{ + list-style: none; +} + +.img_noprofile{ + width:40px; +} + +.all_friends{ + width: 60px; +} + +.texto_perfil{ + padding-left:50%; +} + +.img-poke:hover{ + transform: scale(1.1); +} + +.small-input { + width: 200px; /* O el valor que desees */ +} + + +.icons{ + width: 50px; +} + +.imgnotify{ + width: 20px; +} + +.dropdown-toggle::after { + display: none !important; +} + +.btn-notify{ + width: auto; /* En lugar de un ancho fijo */ + padding: 5px 10px; /* Ajustar el espacio interno (padding) */ + font-size: 14px; /* Tamaño de la fuente */ +} + +/* Agrega este código a tu archivo CSS o en la etiqueta + + +
+ + +