diff --git a/migrations/versions/7f3fdc0ecd62_.py b/migrations/versions/7f3fdc0ecd62_.py new file mode 100644 index 000000000..7d9cd826d --- /dev/null +++ b/migrations/versions/7f3fdc0ecd62_.py @@ -0,0 +1,35 @@ +"""empty message + +Revision ID: 7f3fdc0ecd62 +Revises: a5cffa318ac2 +Create Date: 2025-05-05 18:45:14.298755 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7f3fdc0ecd62' +down_revision = 'a5cffa318ac2' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('email', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.Column('password', sa.VARCHAR(length=80), autoincrement=False, nullable=False), + sa.Column('is_active', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='user_pkey'), + sa.UniqueConstraint('email', name='user_email_key') + ) + # ### end Alembic commands ### diff --git a/src/admin.py b/src/admin.py index bb934027c..6c168b8be 100644 --- a/src/admin.py +++ b/src/admin.py @@ -1,16 +1,16 @@ import os from flask_admin import Admin -from models import db, User +from models import db from flask_admin.contrib.sqla import ModelView + def setup_admin(app): app.secret_key = os.environ.get('FLASK_APP_KEY', 'sample key') app.config['FLASK_ADMIN_SWATCH'] = 'cerulean' admin = Admin(app, name='4Geeks Admin', template_mode='bootstrap3') - # Add your models here, for example this is how we add a the User model to the admin - admin.add_view(ModelView(User, db.session)) + # admin.add_view(ModelView(User, db.session)) # You can duplicate that line to add mew models - # admin.add_view(ModelView(YourModelName, db.session)) \ No newline at end of file + # admin.add_view(ModelView(YourModelName, db.session)) diff --git a/src/app.py b/src/app.py index 016c5bff9..89fea0dd0 100644 --- a/src/app.py +++ b/src/app.py @@ -8,42 +8,54 @@ from flask_cors import CORS from utils import APIException, generate_sitemap from admin import setup_admin -from models import db, User -#from models import Person +from models import db, Personaje, Planeta, Vehiculo, Usuario, PersonajeFavorito, PlanetaFavorito, VehiculoFavorito +from routes.personaje_routes import personaje_bp +from routes.planeta_routes import planeta_bp +from routes.vehiculo_routes import vehiculo_bp +from routes.usuario_routes import usuario_bp +from routes.favoritos_routes import favoritos_bp + app = Flask(__name__) app.url_map.strict_slashes = False +# Configuración de la base de datos db_url = os.getenv("DATABASE_URL") if db_url is not None: - app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace("postgres://", "postgresql://") + app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace( + "postgres://", "postgresql://") else: app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:////tmp/test.db" app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +# Inicialización de extensiones MIGRATE = Migrate(app, db) db.init_app(app) CORS(app) setup_admin(app) -# Handle/serialize errors like a JSON object +with app.app_context(): + print("Tablas registradas:", db.metadata.tables.keys()) + db.create_all() + +app.register_blueprint(personaje_bp) +app.register_blueprint(planeta_bp) +app.register_blueprint(vehiculo_bp) +app.register_blueprint(usuario_bp) +app.register_blueprint(favoritos_bp) + @app.errorhandler(APIException) def handle_invalid_usage(error): return jsonify(error.to_dict()), error.status_code -# generate sitemap with all your endpoints @app.route('/') def sitemap(): return generate_sitemap(app) -@app.route('/user', methods=['GET']) -def handle_hello(): - - response_body = { - "msg": "Hello, this is your GET /user response " - } - - return jsonify(response_body), 200 +def validar_tipo(valor, tipo_esperado, nombre_campo): + if valor is not None and not isinstance(valor, tipo_esperado): + raise ValueError( + f"Campo '{nombre_campo}' debe ser de tipo {tipo_esperado.__name__}") # this only runs if `$ python src/app.py` is executed if __name__ == '__main__': diff --git a/src/models.py b/src/models.py deleted file mode 100644 index 7f535f618..000000000 --- a/src/models.py +++ /dev/null @@ -1,19 +0,0 @@ -from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean -from sqlalchemy.orm import Mapped, mapped_column - -db = SQLAlchemy() - -class User(db.Model): - id: Mapped[int] = mapped_column(primary_key=True) - email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) - password: Mapped[str] = mapped_column(nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) - - - def serialize(self): - return { - "id": self.id, - "email": self.email, - # do not serialize the password, its a security breach - } diff --git a/src/models/__init__.py b/src/models/__init__.py new file mode 100644 index 000000000..68ae2180f --- /dev/null +++ b/src/models/__init__.py @@ -0,0 +1,6 @@ +from .database import db +from .usuario import Usuario +from .personaje import Personaje +from .vehiculo import Vehiculo +from .planeta import Planeta +from .associations import PersonajeFavorito, PlanetaFavorito, VehiculoFavorito diff --git a/src/models/associations.py b/src/models/associations.py new file mode 100644 index 000000000..7cc1c712e --- /dev/null +++ b/src/models/associations.py @@ -0,0 +1,63 @@ +from .database import db +from sqlalchemy import ForeignKey, DateTime, func +from sqlalchemy.orm import Mapped, mapped_column, relationship +from datetime import datetime + +class PlanetaFavorito(db.Model): + __tablename__ = "planetas_favoritos" + + id: Mapped[int] = mapped_column(primary_key=True) + usuario_id: Mapped[int] = mapped_column(ForeignKey("usuarios.id"), nullable=False) + planeta_id: Mapped[int] = mapped_column(ForeignKey("planetas.id"), nullable=False) + fecha_agregado: Mapped[datetime] = mapped_column(DateTime, default=func.now()) + + def serialize(self): + return { + "id": self.id, + "usuario_id": self.usuario_id, + "planeta_id": self.planeta_id, + "fecha_agregado": self.fecha_agregado + } + + usuario = relationship("Usuario", back_populates="planetas_favoritos") + planeta = relationship("Planeta", back_populates="favoritos") + + +class PersonajeFavorito(db.Model): + __tablename__ = "personajes_favoritos" + + id: Mapped[int] = mapped_column(primary_key=True) + usuario_id: Mapped[int] = mapped_column(ForeignKey("usuarios.id"), nullable=False) + personaje_id: Mapped[int] = mapped_column(ForeignKey("personajes.id"), nullable=False) + fecha_agregado: Mapped[datetime] = mapped_column(DateTime, default=func.now()) + + def serialize(self): + return { + "id": self.id, + "usuario_id": self.usuario_id, + "personaje_id": self.planeta_id, + "fecha_agregado": self.fecha_agregado + } + + usuario = relationship("Usuario", back_populates="personajes_favoritos") + personaje = relationship("Personaje", back_populates="favoritos") + + +class VehiculoFavorito(db.Model): + __tablename__ = "vehiculos_favoritos" + + id: Mapped[int] = mapped_column(primary_key=True) + usuario_id: Mapped[int] = mapped_column(ForeignKey("usuarios.id"), nullable=False) + vehiculo_id: Mapped[int] = mapped_column(ForeignKey("vehiculos.id"), nullable=False) + fecha_agregado: Mapped[datetime] = mapped_column(DateTime, default=func.now()) + + def serialize(self): + return { + "id": self.id, + "usuario_id": self.usuario_id, + "vehiculo_id": self.planeta_id, + "fecha_agregado": self.fecha_agregado + } + + usuario = relationship("Usuario", back_populates="vehiculos_favoritos") + vehiculo = relationship("Vehiculo", back_populates="favoritos") \ No newline at end of file diff --git a/src/models/database.py b/src/models/database.py new file mode 100644 index 000000000..2e1eeb63f --- /dev/null +++ b/src/models/database.py @@ -0,0 +1,3 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() \ No newline at end of file diff --git a/src/models/personaje.py b/src/models/personaje.py new file mode 100644 index 000000000..a0d203a4c --- /dev/null +++ b/src/models/personaje.py @@ -0,0 +1,60 @@ +from .database import db +from sqlalchemy import String, Integer, ForeignKey, Text +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import TYPE_CHECKING +from .associations import PersonajeFavorito + +if TYPE_CHECKING: + from .vehiculo import Vehiculo + from .planeta import Planeta + + +class Personaje(db.Model): + __tablename__ = "personajes" + + id: Mapped[int] = mapped_column(primary_key=True) + nombre: Mapped[str] = mapped_column(String(100), nullable=False) + especie: Mapped[str] = mapped_column(String(50), nullable=True) + altura: Mapped[int] = mapped_column(Integer, nullable=True) + peso: Mapped[int] = mapped_column(Integer, nullable=True) + genero: Mapped[str] = mapped_column(String(20), nullable=True) + imagen_url: Mapped[str] = mapped_column(String(255), nullable=True) + descripcion: Mapped[str] = mapped_column(Text, nullable=True) + planeta_natal_id: Mapped[int] = mapped_column( + ForeignKey("planetas.id"), nullable=True) + vehiculo_id: Mapped[int] = mapped_column( + ForeignKey("vehiculos.id"), unique=True, nullable=True) + + vehiculo: Mapped["Vehiculo"] = relationship( + "Vehiculo", + foreign_keys=[vehiculo_id], + back_populates="personaje", + uselist=False + ) + planeta_natal: Mapped["Planeta"] = relationship( + "Planeta", back_populates="personajes") + favoritos: Mapped["PersonajeFavorito"] = relationship( + "PersonajeFavorito", back_populates="personaje") + + def serialize(self): + return { + "id": self.id, + "nombre": self.nombre, + "especie": self.especie, + "altura": self.altura, + "peso": self.peso, + "genero": self.genero, + "imagen_url": self.imagen_url, + "descripcion": self.descripcion, + "planeta_natal_id": self.planeta_natal_id, + "vehiculo_id": self.vehiculo_id + } + + def serialize_with_relations(self): + data = self.serialize() + if self.planeta_natal: + data['planeta_natal'] = self.planeta_natal.serialize() + if self.vehiculo: + data['vehiculo'] = self.vehiculo.serialize() + data['favoritos'] = [f.serialize() for f in self.favoritos] + return data diff --git a/src/models/planeta.py b/src/models/planeta.py new file mode 100644 index 000000000..592424b0e --- /dev/null +++ b/src/models/planeta.py @@ -0,0 +1,42 @@ +from .database import db +from sqlalchemy import String, Integer, Text +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import TYPE_CHECKING +from .associations import PlanetaFavorito + +if TYPE_CHECKING: + from .personaje import Personaje + + +class Planeta(db.Model): + __tablename__ = "planetas" + + id: Mapped[int] = mapped_column(primary_key=True) + nombre: Mapped[str] = mapped_column(String(100), nullable=False) + diametro: Mapped[int] = mapped_column(Integer, nullable=True) + clima: Mapped[str] = mapped_column(String(50), nullable=True) + poblacion: Mapped[int] = mapped_column(Integer, nullable=True) + imagen_url: Mapped[str] = mapped_column(String(255), nullable=True) + descripcion: Mapped[str] = mapped_column(Text, nullable=True) + + personajes: Mapped["Personaje"] = relationship( + "Personaje", back_populates="planeta_natal") + favoritos: Mapped["PlanetaFavorito"] = relationship( + "PlanetaFavorito", back_populates="planeta") + + def serialize(self): + return { + "id": self.id, + "nombre": self.nombre, + "diametro": self.diametro, + "clima": self.clima, + "poblacion": self.poblacion, + "imagen_url": self.imagen_url, + "descripcion": self.descripcion + } + + def serialize_with_relations(self): + data = self.serialize() + data['personajes'] = [p.serialize() for p in self.personajes] + data['favoritos'] = [f.serialize() for f in self.favoritos] + return data diff --git a/src/models/usuario.py b/src/models/usuario.py new file mode 100644 index 000000000..436a6c7b5 --- /dev/null +++ b/src/models/usuario.py @@ -0,0 +1,50 @@ +from .database import db +from sqlalchemy import String, Boolean, DateTime, func +from sqlalchemy.orm import Mapped, mapped_column, relationship +from datetime import datetime +from .associations import PersonajeFavorito, PlanetaFavorito, VehiculoFavorito + + +class Usuario(db.Model): + __tablename__ = "usuarios" + + id: Mapped[int] = mapped_column(primary_key=True) + nombre: Mapped[str] = mapped_column(String(100), nullable=False) + apellido: Mapped[str] = mapped_column(String(100), nullable=False) + email: Mapped[str] = mapped_column( + String(100), unique=True, nullable=False) + password: Mapped[str] = mapped_column(String(255), nullable=False) + fecha_registro: Mapped[datetime] = mapped_column( + DateTime, default=func.now()) + ultima_conexion: Mapped[datetime] = mapped_column( + DateTime, default=func.now()) + activo: Mapped[bool] = mapped_column(Boolean(), default=True) + + # Relaciones + planetas_favoritos: Mapped["PlanetaFavorito"] = relationship( + "PlanetaFavorito", back_populates="usuario") + personajes_favoritos: Mapped["PersonajeFavorito"] = relationship( + "PersonajeFavorito", back_populates="usuario") + vehiculos_favoritos: Mapped["VehiculoFavorito"] = relationship( + "VehiculoFavorito", back_populates="usuario") + + def serialize(self): + return { + "id": self.id, + "nombre": self.nombre, + "apellido": self.apellido, + "email": self.email, + "fecha_registro": self.fecha_registro, + "ultima_conexion": self.ultima_conexion, + "activo": self.activo + } + + def serialize_with_relations(self): + data = self.serialize() + data['planetas_favoritos'] = [pf.serialize() + for pf in self.planetas_favoritos] + data['personajes_favoritos'] = [pef.serialize() + for pef in self.personajes_favoritos] + data['vehiculos_favoritos'] = [vf.serialize() + for vf in self.vehiculos_favoritos] + return data diff --git a/src/models/vehiculo.py b/src/models/vehiculo.py new file mode 100644 index 000000000..53b557d54 --- /dev/null +++ b/src/models/vehiculo.py @@ -0,0 +1,56 @@ +from .database import db +from sqlalchemy import String, Integer, ForeignKey, Float, Text +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import TYPE_CHECKING +from .associations import VehiculoFavorito + +if TYPE_CHECKING: + from .personaje import Personaje + + +class Vehiculo(db.Model): + __tablename__ = "vehiculos" + + id: Mapped[int] = mapped_column(primary_key=True) + nombre: Mapped[str] = mapped_column(String(100), nullable=False) + modelo: Mapped[str] = mapped_column(String(100), nullable=True) + longitud: Mapped[float] = mapped_column(Float, nullable=True) + velocidad_maxima: Mapped[int] = mapped_column(Integer, nullable=True) + tripulacion: Mapped[int] = mapped_column(Integer, nullable=True) + pasajeros: Mapped[int] = mapped_column(Integer, nullable=True) + imagen_url: Mapped[str] = mapped_column(String(255), nullable=True) + descripcion: Mapped[str] = mapped_column(Text, nullable=True) + personaje_id: Mapped[int] = mapped_column( + ForeignKey("personajes.id"), nullable=True) + + personaje: Mapped["Personaje"] = relationship( + "Personaje", + back_populates="vehiculo", + uselist=False, + foreign_keys="Personaje.vehiculo_id", + remote_side="Personaje.vehiculo_id", + primaryjoin="Vehiculo.id == Personaje.vehiculo_id" + ) + favoritos: Mapped["VehiculoFavorito"] = relationship( + "VehiculoFavorito", back_populates="vehiculo") + + def serialize(self): + return { + "id": self.id, + "nombre": self.nombre, + "modelo": self.modelo, + "longitud": self.longitud, + "velocidad_maxima": self.velocidad_maxima, + "tripulacion": self.tripulacion, + "pasajeros": self.pasajeros, + "imagen_url": self.imagen_url, + "descripcion": self.descripcion, + "personaje_id": self.personaje_id + } + + def serialize_with_relations(self): + data = self.serialize() + if self.personaje: + data['personaje'] = self.personaje.serialize() + data['favoritos'] = [f.serialize() for f in self.favoritos] + return data diff --git a/src/routes/favoritos_routes.py b/src/routes/favoritos_routes.py new file mode 100644 index 000000000..1513cadc6 --- /dev/null +++ b/src/routes/favoritos_routes.py @@ -0,0 +1,122 @@ +from flask import Blueprint, jsonify, request +from models.database import db +from models.usuario import Usuario +from models.planeta import Planeta, PlanetaFavorito +from models.vehiculo import Vehiculo, VehiculoFavorito +from models.personaje import Personaje, PersonajeFavorito + +favoritos_bp = Blueprint('favoritos', __name__, url_prefix='/favoritos') + + +@favoritos_bp.route('/favorite/planet/', methods=['POST']) +def add_favorite_planet(planet_id): + user = Usuario.query.get(Usuario.id) + planeta = Planeta.query.get(planet_id) + + if not planeta: + return jsonify({'error': 'Planeta no encontrado'}), 404 + + favorito_existente = next( + (fav for fav in user.planetas_favoritos if fav.planeta_id == planet_id), None) + if favorito_existente: + return jsonify({'msg': 'El planeta ya está en favoritos'}), 400 + nuevo_favorito = PlanetaFavorito(usuario_id=user.id, planeta_id=planet_id) + db.session.add(nuevo_favorito) + db.session.commit() + + return jsonify({'msg': 'Planeta añadido a favoritos', 'planeta': planeta.nombre}), 201 + + +@favoritos_bp.route('/favorite/people/', methods=['POST']) +def add_favorite_personaje(people_id): + user = Usuario.query.get(Usuario.id) + personaje = Personaje.query.get(people_id) + + if not personaje: + return jsonify({'error': 'Personaje no encontrado'}), 404 + + favorito_existente = next( + (fav for fav in user.persoanjes_favoritos if fav.personaje_id == people_id), None) + if favorito_existente: + return jsonify({'msg': 'El personaje ya está en favoritos'}), 400 + + nuevo_favorito = PersonajeFavorito( + usuario_id=user.id, personaje_id=people_id) + db.session.add(nuevo_favorito) + db.session.commit() + + return jsonify({'msg': 'Personaje añadido a favoritos', 'personaje': personaje.nombre}), 201 + + +@favoritos_bp.route('/favorite/vehicle/', methods=['POST']) +def add_favorite_vehiculo(vehicle_id): + user = Usuario.query.get(Usuario.id) + vehiculo = Vehiculo.query.get(vehicle_id) + + if not vehiculo: + return jsonify({'error': 'Vehículo no encontrado'}), 404 + + favorito_existente = next( + (fav for fav in user.vehiculos_favoritos if fav.vehiculo_id == vehicle_id), None) + if favorito_existente: + return jsonify({'msg': 'El vehículo ya está en favoritos'}), 400 + + nuevo_favorito = VehiculoFavorito( + usuario_id=user.id, vehiculo_id=vehicle_id) + db.session.add(nuevo_favorito) + db.session.commit() + + return jsonify({'msg': 'Vehículo añadido a favoritos', 'vehiculo': vehiculo.nombre}), 201 + + +# DELETEs + +@favoritos_bp.route('/planet/', methods=['DELETE']) +def delete_fav_planet(planet_id): + user_id = Usuario.query.get(Usuario.id) + favorite = PlanetaFavorito.query.filter_by( + usuario_id=user_id, planeta_id=planet_id).first() + + if not favorite: + return jsonify({"message": "Favorite not found"}), 404 + + db.session.delete(favorite) + db.session.commit() + + updated_favorites = PlanetaFavorito.query.filter_by( + usuario_id=user_id).all() + return jsonify([fav.serialize() for fav in updated_favorites]), 200 + + +@favoritos_bp.route('/people/', methods=['DELETE']) +def delete_fav_person(people_id): + user_id = Usuario.query.get(Usuario.id) + favorite = PersonajeFavorito.query.filter_by( + usuario_id=user_id, personaje_id=people_id).first() + + if not favorite: + return jsonify({"message": "Person not found"}), 404 + + db.session.delete(favorite) + db.session.commit() + + updated_favorites = PersonajeFavorito.query.filter_by( + usuario_id=user_id).all() + return jsonify([p.serialize() for p in updated_favorites]), 200 + + +@favoritos_bp.route('/vehicle/', methods=['DELETE']) +def delete_fav_vehiculo(vehicle_id): + user_id = Usuario.query.get(Usuario.id) + favorite = VehiculoFavorito.query.filter_by( + usuario_id=user_id, vehiculo_id=vehicle_id).first() + + if not favorite: + return jsonify({"message": "Vehículo favorito no encontrado"}), 404 + + db.session.delete(favorite) + db.session.commit() + + updated_favorites = VehiculoFavorito.query.filter_by( + usuario_id=user_id).all() + return jsonify([p.serialize() for p in updated_favorites]), 200 diff --git a/src/routes/personaje_routes.py b/src/routes/personaje_routes.py new file mode 100644 index 000000000..21297cf2f --- /dev/null +++ b/src/routes/personaje_routes.py @@ -0,0 +1,114 @@ +from flask import Blueprint, jsonify, request +from models.personaje import Personaje +from models.database import db +from app import validar_tipo + +personaje_bp = Blueprint('personajes', __name__, url_prefix='/people') + +# GET people list + + +@personaje_bp.route('/', methods=['GET']) +def get_people(): + people = Personaje.query.all() + return jsonify([personaje.serialize() for personaje in people]), 200 + +# GET person + + +@personaje_bp.route('/', methods=['GET']) +def get_personaje(people_id): + personaje = Personaje.query.get_or_404(people_id) + return jsonify(personaje.serialize()), 200 + +# DELETE person + + +@personaje_bp.route('/', methods=['DELETE']) +def delete_personaje(people_id): + personaje = Personaje.query.get_or_404(people_id) + + db.session.delete(personaje) + db.session.commit() + return jsonify({"msg": f"Personaje {people_id} eliminado"}), 200 + +# POST person + + +@personaje_bp.route('/', methods=['POST']) +def post_people(): + request_body = request.get_json() + + if not request_body: + return jsonify({"error": "Empty request body"}), 400 + + # Validate required fields and their types + if not isinstance(request_body.get("nombre"), str): + return jsonify({"error": "'nombre' must be a string"}), 400 + if not isinstance(request_body.get("especie"), str): + return jsonify({"error": "'especie' must be a string"}), 400 + if not isinstance(request_body.get("altura"), (int, float)): + return jsonify({"error": "'altura' must be a number"}), 400 + if not isinstance(request_body.get("peso"), (int, float)): + return jsonify({"error": "'peso' must be a number"}), 400 + if not isinstance(request_body.get("genero"), str): + return jsonify({"error": "'genero' must be a string"}), 400 + if not isinstance(request_body.get("imagen_url"), str): + return jsonify({"error": "'imagen_url' must be a string"}), 400 + if not isinstance(request_body.get("descripcion"), str): + return jsonify({"error": "'descripcion' must be a string"}), 400 + + new_personaje = Personaje( + nombre=request_body['nombre'], + especie=request_body['especie'], + altura=request_body['altura'], + peso=request_body['peso'], + genero=request_body['genero'], + imagen_url=request_body['imagen_url'], + descripcion=request_body['descripcion'] + ) + + db.session.add(new_personaje) + db.session.commit() + + return jsonify({"message": "Personaje created successfully"}), 201 + +# PUT person + + +@personaje_bp.route('/people/', methods=['PUT']) +def update_personaje(people_id): + personaje = Personaje.query.get_or_404(people_id) + data = request.get_json() + + try: + if "nombre" in data: + validar_tipo(data["nombre"], str, "nombre") + personaje.nombre = data["nombre"] + if "especie" in data: + validar_tipo(data["especie"], str, "especie") + personaje.especie = data["especie"] + if "altura" in data: + validar_tipo(data["altura"], int, "altura") + personaje.altura = data["altura"] + if "peso" in data: + validar_tipo(data["peso"], int, "peso") + personaje.peso = data["peso"] + if "genero" in data: + validar_tipo(data["genero"], str, "genero") + personaje.genero = data["genero"] + if "descripcion" in data: + validar_tipo(data["descripcion"], str, "descripcion") + personaje.descripcion = data["descripcion"] + if "planeta_natal_id" in data: + validar_tipo(data["planeta_natal_id"], int, "planeta_natal_id") + personaje.planeta_natal_id = data["planeta_natal_id"] + if "vehiculo_id" in data: + validar_tipo(data["vehiculo_id"], int, "vehiculo_id") + personaje.vehiculo_id = data["vehiculo_id"] + + except ValueError as e: + return jsonify({"error": str(e)}), 400 + + db.session.commit() + return jsonify(personaje.serialize()), 200 diff --git a/src/routes/planeta_routes.py b/src/routes/planeta_routes.py new file mode 100644 index 000000000..816d18231 --- /dev/null +++ b/src/routes/planeta_routes.py @@ -0,0 +1,91 @@ +from flask import Blueprint, jsonify, request +from models.planeta import Planeta +from models.database import db +from app import validar_tipo + +planeta_bp = Blueprint('planeta', __name__, url_prefix='/planets') + + +@planeta_bp.route('/', methods=['GET']) +def get_planetas(): + planetas = Planeta.query.all() + return jsonify([planeta.serialize() for planeta in planetas]), 200 + + +@planeta_bp.route('/', methods=['GET']) +def get_planeta(planet_id): + planeta = Planeta.query.get_or_404(planet_id) + return jsonify(planeta.serialize()), 200 + + +@planeta_bp.route('/planeta', methods=['POST']) +def post_planeta(): + request_body = request.get_json() + if not request_body: + return jsonify({"error": "Empty request body"}), 400 + + # Validate required fields and their types + if not isinstance(request_body.get("nombre"), str): + return jsonify({"error": "'nombre' must be a string"}), 400 + if not isinstance(request_body.get("diametro"), int): + return jsonify({"error": "'diametro' must be a string"}), 400 + if not isinstance(request_body.get("clima"), str): + return jsonify({"error": "'clima' must be a number"}), 400 + if not isinstance(request_body.get("poblacion"), int): + return jsonify({"error": "'poblacion' must be a number"}), 400 + if not isinstance(request_body.get("imagen_url"), str): + return jsonify({"error": "'imagen_url' must be a string"}), 400 + if not isinstance(request_body.get("descripcion"), str): + return jsonify({"error": "'descripcion' must be a string"}), 400 + + new_planeta = Planeta( + nombre=request_body['nombre'], + diametro=request_body['diametro'], + clima=request_body['clima'], + poblacion=request_body['poblacion'], + imagen_url=request_body['imagen_url'], + descripcion=request_body['descripcion'] + ) + + db.session.add(new_planeta) + db.session.commit() + + return jsonify({"message": "Planet created successfully"}), 201 + + +@planeta_bp.route('/', methods=['PUT']) +def update_planeta(planet_id): + planeta = Planeta.query.get_or_404(planet_id) + data = request.get_json() + + try: + if "nombre" in data: + validar_tipo(data["nombre"], str, "nombre") + planeta.nombre = data["nombre"] + if "diametro" in data: + validar_tipo(data["diametro"], int, "diametro") + planeta.diametro = data["diametro"] + if "clima" in data: + validar_tipo(data["clima"], str, "clima") + planeta.clima = data["clima"] + if "poblacion" in data: + validar_tipo(data["poblacion"], int, "poblacion") + planeta.poblacion = data["poblacion"] + if "descripcion" in data: + validar_tipo(data["descripcion"], str, "descripcion") + planeta.descripcion = data["descripcion"] + + except ValueError as e: + return jsonify({"error": str(e)}), 400 + + db.session.commit() + return jsonify(planeta.serialize()), 200 + + +@planeta_bp.route('/', methods=['DELETE']) +def delete_planeta(planet_id): + planeta = Planeta.query.get_or_404(planet_id) + + db.session.delete(planeta) + db.session.commit() + return jsonify({"msg": f"Planeta {planet_id} eliminado"}), 200 diff --git a/src/routes/usuario_routes.py b/src/routes/usuario_routes.py new file mode 100644 index 000000000..00f4f9d34 --- /dev/null +++ b/src/routes/usuario_routes.py @@ -0,0 +1,26 @@ +from flask import Blueprint, jsonify, request +from models.usuario import Usuario +from models.database import db + +usuario_bp = Blueprint('usuarios', __name__, url_prefix='/users') + + +@usuario_bp.route('/', methods=['GET']) +def get_users(): + response = Usuario.query.all() + return jsonify(response), 200 + + +@usuario_bp.route('/favorites', methods=['GET']) +def get_favorites(): + user_id = Usuario.query.get(Usuario.id) + + if not user_id: + return jsonify({"error": "User not found"}), 404 + + favorites = { + "planetas": [p.serialize() for p in Usuario.planetas_favoritos], + "personajes": [p.serialize() for p in Usuario.personajes_favoritos], + "vehiculos": [v.serialize() for v in Usuario.vehiculos_favoritos], + } + return jsonify(favorites), 200 diff --git a/src/routes/vehiculo_routes.py b/src/routes/vehiculo_routes.py new file mode 100644 index 000000000..7f0e70db1 --- /dev/null +++ b/src/routes/vehiculo_routes.py @@ -0,0 +1,106 @@ +from flask import Blueprint, jsonify, request +from models.vehiculo import Vehiculo +from models.database import db +from app import validar_tipo + +vehiculo_bp = Blueprint('vehiculos', __name__, url_prefix='/vehicles') + + +@vehiculo_bp.route('/', methods=['GET']) +def get_vehiculos(): + vehiculos = Vehiculo.query.all() + return jsonify([vehiculo.serialize() for vehiculo in vehiculos]), 200 + +@vehiculo_bp.route('/', methods=['GET']) +def get_vehiculo(vehicle_id): + vehiculo = Vehiculo.query.get_or_404(vehicle_id) + return jsonify(vehiculo.serialize()), 200 + + +@vehiculo_bp.route('/', methods=['POST']) +def post_vehiculo(): + request_body = request.get_json() + + if not request_body: + return jsonify({"error": "Empty request body"}), 400 + + # Validate required fields and their types + if not isinstance(request_body.get("nombre"), str): + return jsonify({"error": "'nombre' must be a string"}), 400 + if not isinstance(request_body.get("modelo"), str): + return jsonify({"error": "'modelo' must be a string"}), 400 + if not isinstance(request_body.get("longitud"), (int, float)): + return jsonify({"error": "'longitud' must be a number"}), 400 + if not isinstance(request_body.get("velocidad_maxima"), (int, float)): + return jsonify({"error": "'velocidad_maxima' must be a number"}), 400 + if not isinstance(request_body.get("tripulacion"), int): + return jsonify({"error": "'tripulacion' must be a string"}), 400 + if not isinstance(request_body.get("pasajeros"), int): + return jsonify({"error": "'pasajeros' must be a string"}), 400 + if not isinstance(request_body.get("image_url"), str): + return jsonify({"error": "'imagen_url' must be a string"}), 400 + if not isinstance(request_body.get("descripcion"), str): + return jsonify({"error": "'descripcion' must be a string"}), 400 + + new_vehiculo = Vehiculo( + nombre=request_body['nombre'], + modelo=request_body['modelo'], + longitud=request_body['longitud'], + velocidad_maxima=request_body['velocidad_maxima'], + tripulacion=request_body['tripulacion'], + pasajeros=request_body['pasajeros'], + imagen_url=request_body['imagen_url'], + descripcion=request_body['descripcion'] + ) + + db.session.add(new_vehiculo) + db.session.commit() + + return jsonify({"message": "Vehiculo created successfully"}), 201 + + +@vehiculo_bp.route('/', methods=['PUT']) +def update_vehiculo(vehicle_id): + vehiculo = Vehiculo.query.get_or_404(vehicle_id) + data = request.get_json() + + try: + if "nombre" in data: + validar_tipo(data["nombre"], str, "nombre") + vehiculo.nombre = data["nombre"] + if "modelo" in data: + validar_tipo(data["modelo"], str, "modelo") + vehiculo.modelo = data["modelo"] + if "longitud" in data: + validar_tipo(data["longitud"], float, "longitud") + vehiculo.longitud = data["longitud"] + if "velocidad_maxima" in data: + validar_tipo(data["velocidad_maxima"], int, "velocidad_maxima") + vehiculo.velocidad_maxima = data["velocidad_maxima"] + if "tripulacion" in data: + validar_tipo(data["tripulacion"], int, "tripulacion") + vehiculo.tripulacion = data["tripulacion"] + if "pasajeros" in data: + validar_tipo(data["pasajeros"], int, "pasajeros") + vehiculo.pasajeros = data["pasajeros"] + if "descripcion" in data: + validar_tipo(data["descripcion"], str, "descripcion") + vehiculo.descripcion = data["descripcion"] + if "personaje_id" in data: + validar_tipo(data["personaje_id"], int, "personaje_id") + vehiculo.personaje_id = data["personaje_id"] + + except ValueError as e: + return jsonify({"error": str(e)}), 400 + + db.session.commit() + return jsonify(vehiculo.serialize()), 200 + + +@vehiculo_bp.route('/', methods=['DELETE']) +def delete_vehiculo(vehicle_id): + vehiculo = Vehiculo.query.get_or_404(vehicle_id) + + db.session.delete(vehiculo) + db.session.commit() + return jsonify({"msg": f"Vehículo {vehicle_id} eliminado"}), 200 \ No newline at end of file