Skip to content

Cambios varios #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="icon" href="/4geeks.ico" />
<link rel="icon" type="image/png" href="/logo.png" />
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hello Rigo</title>
<title>SportConnect</title>
</head>
<body>
<div id="root"></div>
Expand Down
Empty file added logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 16 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
"main": "index.js",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"start": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"author": {
"name": "Alejandro Sanchez",
Expand All @@ -30,13 +30,13 @@
"license": "ISC",
"devDependencies": {
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.4",
"eslint": "^8.46.0",
"eslint-plugin-react": "^7.33.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.4.8"
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.4",
"eslint": "^8.46.0",
"eslint-plugin-react": "^7.33.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.4.8"
},
"babel": {
"presets": [
Expand All @@ -54,9 +54,11 @@
]
},
"dependencies": {
"bootstrap": "^5.3.6",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.18.0"
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"react-router-dom": "^6.18.0"
}
}
Binary file added public/Deportistas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Escalada.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/FondoFeed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Running.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logo_sin_fondo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/opcionFeed.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/portada.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions src/api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from flask import Blueprint, request, jsonify
from models import db, User
from werkzeug.security import generate_password_hash, check_password_hash

# Creamos un blueprint para agrupar las rutas de autentificacion
auth_bp = Blueprint('auth', __name__)

@auth_bp.route('/register', methods=['POST'])
def register():
data = request.get_json()# Recibimos los datos JSON del cliente(nombre,email y contraseña)

# Verificamos si el mail ya existe
existing_user = User.query.filter_by(email=data['email']).firts()
if existing_user:
return jsonify({"error": "Email ya registrado"}), 409

# Ciframos la contraseña
hashed_password = generate_password_hash(data['password'], method='sha256')
# sha256(secure hashing algoritme 256bits)
# Creamos el nuevo usuario
user = User(name=data['name'], email=data['email'], password=hashed_password)
db.session.add(user)
db.session.commit()

return jsonify({"msg": "Usuario creado correctamente"}), 201

@auth_bp.route('/login', methods=['POST'])
def login():
data = request.get_json()
# Recibimos email y contraseña

user = User.query.filter_by(email=data['email']).first()
if not user or not check_password_hash(user.password, data['password']):
return jsonify({"error":"Credenciales invalidadas"}), 401

return jsonify({"message": f"Bienvenido, {user.name}"}), 200
65 changes: 55 additions & 10 deletions src/api/models.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,64 @@
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import String, Boolean
from sqlalchemy.orm import Mapped, mapped_column

db = SQLAlchemy()

# Definición de la tabla intermedia para la relación muchos a muchos
# entre usuarios y eventos
event_participants = db.Table('event_participants',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('event_id', db.Integer, db.ForeignKey('event.id'))
)

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)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(256), nullable=False)

created_events = db.relationship('Event', backref='creator', lazy=True)
joined_events = db.relationship( # 👈 aquí corregido
'Event',
secondary=event_participants,
back_populates='joined_users', # 👈 nuevo nombre sin colisión
lazy='dynamic'
)

def serialize(self):
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"email": self.email
}
# Definición del modelo de evento
class Event(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
description = db.Column(db.Text)
date = db.Column(db.String(50))
latitude = db.Column(db.Float, nullable=False)
longitude = db.Column(db.Float, nullable=False)
weather = db.Column(db.String(100)) # en caso de que uses clima
distance = db.Column(db.Float) # en caso de que uses distancia
duration = db.Column(db.Float) # en caso de que uses duración
creator_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Una relación inversa para obtener el creador del evento
joined_users = db.relationship( # 👈 también corregido
'User',
secondary=event_participants,
back_populates='joined_events'
)
# Función para obtener la ubicación como un diccionario
def to_dict(self):
return {
"id": self.id,
"email": self.email,
# do not serialize the password, its a security breach
}
"title": self.title,
"description": self.description,
"date": self.date,
"latitude": self.latitude,
"longitude": self.longitude,
"weather": self.weather,
"distance": self.distance,
"duration": self.duration,
"creator_id": self.creator_id,
"participants": [user.id for user in self.joined_users]
}
38 changes: 29 additions & 9 deletions src/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,37 @@
from api.utils import generate_sitemap, APIException
from flask_cors import CORS

api = Blueprint('api', __name__)
from flask import Blueprint
from src.controllers.events_controller import (
create_event,
get_events,
get_event,
update_event,
delete_event,
leave_event,
join_event,

# Allow CORS requests to this API
CORS(api)
)


@api.route('/hello', methods=['POST', 'GET'])
def handle_hello():
events_bp = Blueprint('events', __name__)

response_body = {
"message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request"
}
events_bp.route('/', methods=['POST'])(create_event) # Crear un nuevo evento
events_bp.route('/', methods=['GET'])(get_events)# Obtener todos los eventos
events_bp.route('/<int:event_id>/join', methods=['POST'])(join_event)# Unirse a un evento
events_bp.route('/<int:event_id>', methods=['GET'])(get_event) # Obtener un evento por ID
events_bp.route('/<int:event_id>', methods=['PUT'])(update_event) # Actualizar un evento por ID
events_bp.route('/<int:event_id>', methods=['DELETE'])(delete_event) # Eliminar evento
events_bp.route('/<int:event_id>/leave', methods=['POST'])(leave_event)

return jsonify(response_body), 200

users_bp = Blueprint('users', __name__)

users_bp.route('/', methods=['POST'])(create_user)
users_bp.route('/', methods=['GET'])(get_users)
users_bp.route('/<int:user_id>', methods=['GET'])(get_user)
users_bp.route('/<int:user_id>', methods=['PUT'])(update_user) # <--- Esto es necesario
users_bp.route('/<int:user_id>', methods=['DELETE'])(delete_user)
users_bp.route('/<int:user_id>/join', methods=['POST'])(join_event)
users_bp.route('/<int:user_id>/leave', methods=['POST'])(leave_event)
users_bp.route('/<int:user_id>/events', methods=['GET'])(get_user_events)
41 changes: 41 additions & 0 deletions src/api/services/route_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os
import requests

def get_route_info(start_coords, end_coords, profile='foot-hiking'):

"""
Llama a la API de OpenRouteService para obtener información sobre una ruta entre dos puntos.
- start_coords: [lng, lat]
- end_coords: [lng, lat]
- profile: 'foot-hiking', 'cycling-mountain', etc.
"""
api_key = os.getenv("ORS_API_KEY")
if not api_key:
return {"error": "API Key ORS no configurada"}

url = f"https://api.openrouteservice.org/v2/directions/{profile}"

headers = {
"Authorization": api_key,
"Content-Type": "application/json"
}

body = {
"coordinates": [start_coords, end_coords]
}

try:
response = requests.post(url, json=body, headers=headers)
if response.status_code == 200:
data = response.json()
summary = data["features"][0]["properties"]["summary"]
distance = summary["distance"] / 1000 # en kilómetros
duration = summary["duration"] / 60 # en minutos
return {
"distance": round(distance, 2),
"duration": round(duration, 2)
}
else:
return {"error": f"HTTP {response.status_code}: {response.text}"}
except Exception as e:
return {"error": f"Excepción al obtener la ruta: {str(e)}"}
Loading