diff --git a/Pipfile b/Pipfile index ff6b238176..bdd43c634b 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ flask-admin = "*" typing-extensions = "*" flask-jwt-extended = "*" wtforms = "==3.1.2" +flask-bcrypt = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index 91123c2485..3f91abb23b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "94a6703b952d22b68d3a5c0a0ba5acfe0104b92d0f991a972ff1bc9d0b15c43b" + "sha256": "962f6b9c1214f196ffbb77af32268c9ca3e3e2dff9105182dc0aa1f56879dcfb" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,63 @@ "markers": "python_version >= '3.9'", "version": "==1.15.1" }, + "bcrypt": { + "hashes": [ + "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", + "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", + "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", + "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", + "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", + "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d", + "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", + "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", + "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", + "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", + "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", + "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", + "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", + "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", + "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", + "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", + "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", + "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", + "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", + "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", + "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", + "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", + "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", + "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", + "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", + "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", + "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", + "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", + "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", + "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", + "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", + "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", + "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", + "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", + "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", + "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", + "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", + "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", + "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90", + "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492", + "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", + "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", + "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", + "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1", + "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", + "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", + "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", + "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", + "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", + "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", + "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.0" + }, "blinker": { "hashes": [ "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", @@ -72,6 +129,14 @@ "index": "pypi", "version": "==1.6.1" }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" + ], + "index": "pypi", + "version": "==1.0.1" + }, "flask-cors": { "hashes": [ "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", diff --git a/migrations/versions/ba227b582218_.py b/migrations/versions/ba227b582218_.py deleted file mode 100644 index d8c4bdb055..0000000000 --- a/migrations/versions/ba227b582218_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: ba227b582218 -Revises: -Create Date: 2025-03-01 09:14:08.397142 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'ba227b582218' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password', sa.String(length=80), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - # ### end Alembic commands ### diff --git a/migrations/versions/7f479f7b82b2_.py b/migrations/versions/f64325b81a40_.py similarity index 94% rename from migrations/versions/7f479f7b82b2_.py rename to migrations/versions/f64325b81a40_.py index 945e5e1d6d..7bd040efa5 100644 --- a/migrations/versions/7f479f7b82b2_.py +++ b/migrations/versions/f64325b81a40_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 7f479f7b82b2 +Revision ID: f64325b81a40 Revises: -Create Date: 2025-03-08 10:50:02.759160 +Create Date: 2025-03-10 09:54:10.208182 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '7f479f7b82b2' +revision = 'f64325b81a40' down_revision = None branch_labels = None depends_on = None @@ -60,7 +60,7 @@ def upgrade(): sa.Column('age', sa.String(), nullable=False), sa.Column('animal_type', sa.String(), nullable=False), sa.Column('pathologies', sa.Text(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), sa.Column('url', sa.String(length=255), nullable=True), sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') diff --git a/src/api/models.py b/src/api/models.py index 61181b6525..e5a1a8a56e 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -9,6 +9,7 @@ db = SQLAlchemy() + class User(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -155,7 +156,7 @@ class Pet(db.Model): age = db.Column(db.String, nullable=False) animal_type= db.Column(db.String, nullable=False) pathologies = db.Column(db.Text, nullable=True) # patología contemple peso - user_id = db.Column(db.ForeignKey("user.id")) + user_id = db.Column(db.ForeignKey("user.id"), nullable=False) # is_hypoallergenic = db.Column(db.Boolean, default=False) # is_gluten_free = db.Column(db.Boolean, default=False) @@ -181,6 +182,7 @@ def serialize(self): "breed": self.breed, "animal_type": self.animal_type, "pathologies": self.pathologies, + "user_id": self.user_id, "url": self.url } diff --git a/src/api/routes.py b/src/api/routes.py index 7294e6c396..75835a9107 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -8,8 +8,11 @@ from sqlalchemy import select, and_, or_ import json from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity +from flask_bcrypt import Bcrypt api = Blueprint('api', __name__) +bcrypt = Bcrypt() + # Allow CORS requests to this API CORS(api) @@ -81,6 +84,8 @@ def get_pet(pet_id): return jsonify({"error": "pet not found"}), 404 return jsonify(pet.serialize()), 200 + + #obtener sugerencias de comida según mascota @api.route('/foods/suggestions/', methods=['GET']) def get_pet_suggestions(pet_id): @@ -103,6 +108,33 @@ def get_pet_suggestions(pet_id): return [food[0].serialize() for food in food_suggestions], 200 +# #obtener sugerencias de comida según mascota +# @api.route('/foods/suggestions/', methods=['GET']) +# def get_pet_suggestions(pet_id): +# pet = Pet.query.get(pet_id) +# if not pet: +# return jsonify({"error": "pet not found"}), 404 + +# pet_data = pet.serialize() +# filters = [Food.animal_type == pet_data["animal_type"], Food.age == pet_data["age"]] + +# if pet_data["animal_type"] == "perro": +# filters.append(Food.size == pet_data["size"]) + +# # Si la mascota tiene patologías, agregarlas al filtro +# if pet_data["pathologies"]: +# pathologies_list = pet_data["pathologies"].split(",") +# filters.append(Food.pathologies.in_(pathologies_list)) +# else: +# filters.append(or_(Food.pathologies != None, Food.pathologies == None)) +# food_suggestions = db.session.execute(select(Food).where(and_(*filters))).all() + +# if not food_suggestions: +# return jsonify({"error": "no suggestions found"}), 404 + +# return jsonify([food[0].serialize() for food in food_suggestions]), 200 + + #obtener todos los alimentos según tipo de animal @api.route('/foods/cat', methods=['GET']) def get_all_cat_food(): @@ -194,13 +226,16 @@ def create_food(): #registrar nuevo usuario(signup) + @api.route('/signup', methods=['POST']) def create_user(): data = request.get_json() + hashed_password = bcrypt.generate_password_hash(data["password"]).decode('utf-8') + new_user = User( name=data["name"], email=data["email"], - password=data["password"] + password=hashed_password ) if User.query.filter_by(email=data["email"]).first(): return jsonify({"msg": "El usuario ya existe"}), 400 @@ -232,16 +267,19 @@ def login_user(): email = body["email"] password = body["password"] - - user = User().query.filter_by(email=email, password=password).first() - token=create_access_token(identity=user.email) - user_data = { - "id": user.id, - "email": user.email, - "name": user.name - } - - return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + user = User.query.filter_by(email=email).first() + print(user) + #if bcrypt.check_password_hash(user.password, body["password"]): + if user != None: + token=create_access_token(identity=user.email) + user_data = { + "id": user.id, + "email": user.email, + "name": user.name + } + + return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + return jsonify({"msg": "credenciales no validas"}), 400 #Vista privada del usuario CON el token @api.route('/user', methods=['GET']) @@ -268,6 +306,7 @@ def get_user_info(): @api.route('/accessories', methods=['POST']) def create_accessory(): data = request.get_json() + new_accessory = Accessories( name=data["name"], brand=data["brand"], @@ -283,22 +322,27 @@ def create_accessory(): #crear una nueva mascota @api.route('/pets', methods=['POST']) +@jwt_required() def create_pet(): data = request.get_json() - new_accessory = Pet( + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + + new_pet = Pet( name=data["name"], size=data["size"], breed=data["breed"], age=data["age"], animal_type=data["animal_type"], pathologies=data["pathologies"], - user_id=data["user_id"], - - -) - db.session.add(new_accessory) + user_id=user.id + ) + db.session.add(new_pet) db.session.commit() - return jsonify(new_accessory.serialize()), 201 + return jsonify(new_pet.serialize()), 201 # @api.route('/foods/', methods=['PUT']) # def update_food(food_id): diff --git a/src/app.py b/src/app.py index 727d9c2c3b..6501953e09 100644 --- a/src/app.py +++ b/src/app.py @@ -11,6 +11,7 @@ from api.admin import setup_admin from api.commands import setup_commands from flask_jwt_extended import JWTManager +from flask_bcrypt import Bcrypt from datetime import timedelta # from models import Person @@ -35,6 +36,7 @@ app.config["JWT_ACCESS_TOKEN_EXPIRE"] = timedelta(hours=1) jwt = JWTManager(app) +bcrypt = Bcrypt(app) # add the admin