diff --git a/.gitignore b/.gitignore
index 80704f4378..85ca896945 100755
--- a/.gitignore
+++ b/.gitignore
@@ -73,6 +73,7 @@ dist/*
!.eslintrc
!.env.example
.now
+migrations/
# backend stuff
.venv
diff --git a/Pipfile b/Pipfile
index 4d377014ae..5675b6c95f 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,7 +6,6 @@ verify_ssl = true
[dev-packages]
[packages]
-flask = "*"
flask-sqlalchemy = "*"
flask-migrate = "*"
flask-swagger = "*"
@@ -17,9 +16,11 @@ gunicorn = "*"
cloudinary = "*"
flask-admin = "==2.0.0"
typing-extensions = "*"
-flask-jwt-extended = "==4.6.0"
wtforms = "==3.1.2"
sqlalchemy = "*"
+flask = "*"
+flask-jwt-extended = "*"
+flask-bcrypt = "*"
[requires]
python_version = "3.13"
diff --git a/src/api/commands.py b/src/api/commands.py
index 19806164d3..78ae930da0 100644
--- a/src/api/commands.py
+++ b/src/api/commands.py
@@ -11,7 +11,7 @@ def setup_commands(app):
"""
This is an example command "insert-test-users" that you can run from the command line
- by typing: $ flask insert-test-users 5
+ by typing: $ pipenv run flask insert-test-users 5 ---numero de tablas que quieras crear
Note: 5 is the number of users to add
"""
@app.cli.command("insert-test-users") # name of our command
diff --git a/src/api/models.py b/src/api/models.py
index da515f6a1a..b047513a08 100644
--- a/src/api/models.py
+++ b/src/api/models.py
@@ -5,15 +5,21 @@
db = SQLAlchemy()
class User(db.Model):
+ __tablename__= 'user'
id: Mapped[int] = mapped_column(primary_key=True)
+ name: Mapped[str] = mapped_column(String(90), unique=False, nullable=False)
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 __repr__(self):
+ return f'Usuario {self.name}'
+
def serialize(self):
return {
"id": self.id,
+ "name": self.name,
"email": self.email,
# do not serialize the password, its a security breach
}
\ No newline at end of file
diff --git a/src/app.py b/src/app.py
index 1b3340c0fa..9d3c4300e5 100644
--- a/src/app.py
+++ b/src/app.py
@@ -6,10 +6,23 @@
from flask_migrate import Migrate
from flask_swagger import swagger
from api.utils import APIException, generate_sitemap
-from api.models import db
+from api.models import db, User
from api.routes import api
from api.admin import setup_admin
from api.commands import setup_commands
+from flask_cors import CORS
+#from datetime import timedelta
+#------import datetime para los refresh
+from flask_jwt_extended import create_access_token
+from flask_jwt_extended import get_jwt_identity
+from flask_jwt_extended import jwt_required
+from flask_jwt_extended import JWTManager
+# Al importar Bcrypt se tiene que instalar la libreria con el comando \
+# : $ pip("pipenv" en este repo) install flask-bcrypt
+from flask_bcrypt import Bcrypt
+from sqlalchemy.exc import IntegrityError
+#from flask_jwt_extended import create_refresh_token
+#------import refresh------
# from models import Person
@@ -19,6 +32,23 @@
app = Flask(__name__)
app.url_map.strict_slashes = False
+app.config["JWT_SECRET_KEY"] = os.getenv('JWT_KEY')
+
+bcrypt = Bcrypt(app)
+
+CORS(app)
+
+#ACCESS_MIN = int(os.getenv("JWT_ACCESS_TOKEN_EXPIRES_MIN", "60"))
+#REFRESH_DAYS = int(os.getenv("JWT_REFRESH_TOKEN_EXPIRES_DAYS", "30"))
+#----------------------------------------------
+# Pruevas para los refrsh tokens /\ \/
+#--------------------------------------------------
+#app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(minutes=ACCESS_MIN)
+#app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=REFRESH_DAYS)
+
+
+jwt = JWTManager(app)
+
# database condiguration
db_url = os.getenv("DATABASE_URL")
if db_url is not None:
@@ -66,6 +96,96 @@ def serve_any_other_file(path):
return response
+
+
+@app.route('/api/login', methods=['POST'])
+def login():
+ body = request.get_json(silent=True)
+ if body is None:
+ return jsonify({'msg': ' Debes enviar informacion en el body'}), 400
+ if 'email' not in body:
+ return jsonify({'msg': 'El campo email es obligatorio'}), 400
+ if 'password' not in body:
+ return jsonify({'msg': ' El campo password es obligatorio'}), 400
+
+ user = User.query.filter_by(email=body['email']).first()
+
+ # var = VarEnModels.query.filer_by(nombredelcampo=body['nombredelcampo']).first()
+ # para traer el usuario orphan en cascade
+ ##########!!!!! \/
+ # user = User.query.filter_by(email=body['email'], password=body['password']).first()
+ # idea de Leon para ahorrarse el if de password!!! Idea alternativa
+ print(user)
+ if user is None:
+ return jsonify({'msg': 'Usuario o contraseña incorrecta'}), 400
+ is_correct = bcrypt.check_password_hash(user.password, body['password'])
+ if is_correct == False:
+ return jsonify({'msg': 'Usuario o contraseña incorrecta'}), 400
+ #if user.password != body['password']:
+ # return jsonify({'msg': 'Usuario o contraseña incorrecta'}), 400
+
+ acces_token = create_access_token(identity=user.email)
+ #refresh_token = create_refresh_token(identity=user.email)
+ #-----refresh token
+ #print(user)
+ return jsonify({'msg': 'Usuario logeado correctamente!', \
+ 'token': acces_token}), 200
+ #'refresh_token': refresh_token,-------
+ #'access_expires_minutes': ACCESS_MIN,------SOLO PRUEBAS REFRESH
+ #'refresh_expires_days': REFRESH_DAYS}), 200 ------
+
+
+@app.route('/api/register', methods=['POST'])
+def register_user():
+ body = request.get_json(silent=True)
+ if body is None:
+ return jsonify({'msg': 'Debes enviar informacion en el body'}), 400
+ if 'email' not in body:
+ return jsonify({'msg': 'El campo email es obligatorio'}), 400
+ if 'name' not in body:
+ return jsonify({'msg': 'Debes proporcionar un nombre'}), 400
+ if 'password' not in body:
+ return jsonify({'msg': 'Debes proporcionar una contraseña'}), 400
+
+ new_register = User() # Otra opcion seria instanciar todo el body dentro del User \
+ # así: new_register = User( email = body['email], name = body['name]...) \
+ # En este caso lo instanciamos por separado
+ new_register.name = body['name']
+ new_register.email = body['email']
+ hash_password = bcrypt.generate_password_hash(body['password']).decode('utf-8')
+ #new_register.password = body['password']
+ new_register.password = hash_password
+
+ new_register.is_active = True
+
+ try:
+ db.session.add(new_register)
+ db.session.commit()
+
+ except IntegrityError:
+ db.session.rollback()
+ return jsonify({'msg': 'Ya existe un usuario con ese email'}), 400
+
+
+ return jsonify({'msg': 'Usuario registrado!', 'register': new_register.serialize()}), 200
+
+
+#-----ENDPOINTS PROTEGIDOS \/
+@app.route('/api/private', methods=['GET'])
+@jwt_required()
+def privado():
+ current_user_email = get_jwt_identity()
+ #print(current_user)
+ current_user = User.query.filter_by(email=current_user_email).first()
+ #----Para autorizar por primera vez un token en Postman /headers -> //crear key// -> Authorization y //Value -> Bearer (espacio) nuevo token
+ #---Una vez autorizado -> /Authorization/Bearer Token/ poner el token
+ if current_user is None:
+ return jsonify({'msg': 'Usuario no encontrado'}), 404
+
+ return jsonify({
+ 'msg': 'Gracias por probar que estas logeado',
+ 'name': current_user.name}), 200
+
# this only runs if `$ python src/main.py` is executed
if __name__ == '__main__':
PORT = int(os.environ.get('PORT', 3001))
diff --git a/src/front/assets/img/BG4j.gif b/src/front/assets/img/BG4j.gif
new file mode 100644
index 0000000000..0e16310bf0
Binary files /dev/null and b/src/front/assets/img/BG4j.gif differ
diff --git a/src/front/components/Navbar.jsx b/src/front/components/Navbar.jsx
index 30d43a2636..fd3f45663f 100644
--- a/src/front/components/Navbar.jsx
+++ b/src/front/components/Navbar.jsx
@@ -1,19 +1,46 @@
-import { Link } from "react-router-dom";
+import React from "react";
+import { Link, useNavigate } from "react-router-dom";
-export const Navbar = () => {
+export default function Navbar() {
+ const navigate = useNavigate();
+ const token = sessionStorage.getItem("token");
- return (
-
- );
-};
\ No newline at end of file
+ const handleLogout = () => {
+ sessionStorage.removeItem("token");
+ navigate("/login", { replace: true });
+ };
+
+ return (
+
+ );
+}
diff --git a/src/front/pages/Layout.jsx b/src/front/pages/Layout.jsx
index 9bfa31325c..8706dab9b7 100644
--- a/src/front/pages/Layout.jsx
+++ b/src/front/pages/Layout.jsx
@@ -1,15 +1,15 @@
-import { Outlet } from "react-router-dom/dist"
-import ScrollToTop from "../components/ScrollToTop"
-import { Navbar } from "../components/Navbar"
-import { Footer } from "../components/Footer"
+import { Outlet } from "react-router-dom";
+//import ScrollToTop from "../components/ScrollToTop"
+import Navbar from "../components/Navbar";
+//import { Footer } from "../components/Footer"
// Base component that maintains the navbar and footer throughout the page and the scroll to top functionality.
export const Layout = () => {
- return (
-
Bienvenid@ {loger}
+
+
+ {loger} mi amigo de aqui arriba es Hipnosapo.
+ Hipnosapo es feliz porque te encanta este proyecto y, antes de pulsar el botón,
+ sabrás que quieres que su desarrollador tenga un APROBADO ^.^U.
+