from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory from flask_pymongo import PyMongo, ObjectId from flask_login import LoginManager, UserMixin, login_user, login_required, current_user, logout_user from werkzeug.utils import secure_filename from werkzeug.security import generate_password_hash, check_password_hash from datetime import datetime from flask_pymongo import ObjectId import os from uuid import uuid4 from flask_wtf import FlaskForm from wtforms import StringField, FileField, SubmitField, DateTimeField, SelectField, PasswordField from wtforms.validators import DataRequired, Length import requests from config import Config from geopy.geocoders import Nominatim geolocator = Nominatim(user_agent="Bachemapa @ baches.qro.mx") def create_app(config=Config): app = Flask(__name__) app.config.from_object(config) mongo = PyMongo(app) login_manager = LoginManager(app) login_manager.login_view = 'thelogin' login_manager.session_protection = "strong" class User(UserMixin): def __init__(self, user_data): self.id = str(user_data['_id']) self.username = user_data['username'] self.referral_code = user_data['referral_code'] self.invited_by = user_data.get('invited_by') self.is_admin = user_data.get('is_admin', False) self.pwd = user_data.get('pwd') @staticmethod def get(user_id): user_data = mongo.db.users.find_one({"_id": ObjectId(user_id)}) if user_data: return User(user_data) else: return None class PinForm(FlaskForm): description = StringField('¿Qué estamos viendo?', validators=[DataRequired()]) photo = FileField('Evidencia fotogénica', validators=[DataRequired()]) timedate = DateTimeField(default=datetime.now()) typeofpin = SelectField('Tipo de cosa', choices=['bache', 'coladera', 'obra sin terminar', 'escombro', 'robo-asalto', 'biciestacionamiento', 'mala iluminación', 'bici blanca', 'zapato blanco']) submit = SubmitField('Agregar') class LoginForm(FlaskForm): username = StringField('Usuario', validators=[DataRequired()]) pwd = PasswordField('Tu clave', validators=[DataRequired()]) submit = SubmitField('Entrar') def Unique(model, field, message=None): def _unique(form, field_data): if mongo.db[model.__name__.lower()].find_one({field.name: field_data.data}): raise ValidationError(message or f"{field.name} must be unique.") return _unique class RegistrationForm(FlaskForm): username = StringField('Nombre de usuarix', validators=[DataRequired(), Unique('users', StringField('username', message="Este usuario ya existe"))]) pwd = PasswordField('Clave', validators=[DataRequired(), Length(min=10), Unique('users', StringField('pwd', message="Esta clave no es muy buena, escoge otra"))]) referral = StringField('ID de quien te invito', [DataRequired()]) submit = SubmitField('Registrar') def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'] @app.route('/', methods=['GET', 'POST']) def index(): if request.method == 'GET': form = PinForm() pins = mongo.db.pins.find() clean_pins=[] for pin in pins: pin['_id'] = str(pin['_id']) if 'time' in pin and pin['time']: pin['time'] = pin['time'].strftime("%d/%m/%Y %H:%M") if 'last_mod' in pin and pin['last_mod']: pin['last_mod'] = pin['last_mod'].strftime("%d/%m/%Y %H:%M") if 'reviewed' in pin: pin['reviewed'] = str(pin['reviewed']) if 'fixed' in pin: pin['fixed'] = str(pin['fixed']) clean_pins.append(pin) print(type(pins)) return render_template('index.html', pins=clean_pins, form=form) else: form = request.form try: photo = request.files["photo"] except Exception as e: print(e) if photo and allowed_file(photo.filename): #try: filename = secure_filename(photo.filename) filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) photo.save(filepath) pin = { 'time': datetime.now(), 'photo': filepath, 'lat': request.form['lat'], 'lng': request.form['lng'], 'typeofpin': request.form['typeofpin'], 'added_by': current_user.id, 'description': request.form['description'], 'address': str(geolocator.reverse(str(request.form['lat'])+ ", "+request.form['lng'])), 'reviewed': True, 'fixed': False, 'last_mod': datetime.now(), 'votes': -1, 'removal_reason': None, } print(geolocator.reverse(str(request.form['lat'])+ ", "+request.form['lng'])) mongo.db.pins.insert_one(pin) flash('¡Gracias por tu aportación!') return redirect(url_for('index')) else: return allowed_file(photo.filename), 404 @app.route('/quienes') def quienes(): return render_template('quienes.html') @app.route('/uploads/') def download_file(name): return send_from_directory(app.config["UPLOAD_FOLDER"], name) @login_manager.user_loader def load_user(user_id): return User.get(user_id) @app.route('/registrame/', methods=['GET', 'POST']) def registrame(referral_code): inviter = mongo.db.users.find_one({"referral_code": referral_code}) if not inviter: message = "Ooops, parece que nadie te invitó aquí... Puedes saber más sobre esta decisión aquí https://baches.qro.mx/quienes#preguntas" return render_template('error.html', message = message), 406 if request.method == 'POST': username = request.form['username'] pwd = request.form['pwd'] new_user_data = { "username": username, "pwd": generate_password_hash(pwd), "referral_code": str(uuid4()), "invited_by": str(inviter['_id']), "is_admin": False } new_user_id = mongo.db.users.insert_one(new_user_data).inserted_id str_id = str(new_user_id) mongo.db.users.update_one( {'_id': new_user_id}, {'$set': {'str_id': str_id}} ) invite_link = url_for('registrame', referral_code=new_user_data['referral_code'], _external=True) login_user(load_user(new_user_id), remember=True) flash('Usuario registrado exitosamente') return redirect(url_for('index')) #return render_template('dashboard.html', invite_link=invite_link) if request.method == 'GET': return render_template('register.html', rform=RegistrationForm()) @app.route('/thelogin', methods=['GET', 'POST']) def thelogin(): form2 = LoginForm() if request.method == 'POST': username = request.form['username'] pwd = request.form['pwd'] user_data = mongo.db.users.find_one({"username": username}) if user_data and check_password_hash(user_data['pwd'], pwd): user = User(user_data) login_user(user, remember=True) return redirect(url_for('index')) else: return render_template('login.html', messages = 'Ooops, no hay tal usuario', form=form2) return render_template('login.html',form=form2) @app.route('/logout') @login_required def logout(): logout_user() flash('Cerraste sesión exitosamente') return redirect('/') @app.route('/desheredame/') @login_required def desheredame(user_id): if not current_user.is_admin: return redirect(url_for('index')) else: user_to_remove = mongo.db.users.find_one({"_id": ObjectId(user_id)}) if not user_to_remove: message = "A este ya me lo desheredaron" return render_template('error.html', message=message), 404 else: # invited_by es un string con el user id, pero para eliminar a un usuario por id, ocupamos el ObjectId(string del user id) # encontrando todos los usuarios invitados por el traidor para eliminar todos los pines que pusieron for user in mongo.db.users.find({'invited_by': user_to_remove.id}): mongo.db.pins.delete_many({"added_by": user.id}) # luego eliminamos a todos los usuarios cuyos pines acabamos de eliminar mongo.db.users.delete_many({"invited_by": user_id}) #eliminamos los pines del usuario infractor mongo.db.pins.delete_many({"added_by": user_to_remove.id}) # finalmente, eliminamos al usuario infractor mongo.db.users.delete_one({"_id": ObjectId(user_id)}) return redirect(url_for('dashboard')) @app.route("/remove_pin/") @login_required def remove_pin(pin_id): actual_pin = mongo.db.pins.find_one({"_id": ObjectId(pin_id)}) print(actual_pin) added_by = actual_pin.get("added_by") print(added_by) print(current_user.id) if current_user.is_admin or current_user.id == added_by: mongo.db.pins.delete_one({"_id": ObjectId(pin_id)}) return redirect(url_for('dashboard')) else: return redirect(url_for('index')) @app.route("/edit_pin/", methods=['POST']) @login_required def edit_pin(pin_id): actual_pin = mongo.db.pins.find_one({"_id": ObjectId(pin_id)}) if not actual_pin: flash('Pin no encontrado') return redirect(url_for('dashboard')) added_by = actual_pin.get("added_by") if current_user.is_admin or current_user.id == added_by: # Get form data typeofpin = request.form.get('typeofpin') description = request.form.get('description') address = request.form.get('address') # Update the pin mongo.db.pins.update_one( {"_id": ObjectId(pin_id)}, {"$set": { "typeofpin": typeofpin, "description": description, "address": address, "last_mod": datetime.now() }} ) flash('Pin actualizado exitosamente') return redirect(url_for('dashboard')) else: flash('No tienes permiso para editar este pin') return render_template('error.html', message='No tienes permiso para editar este pin'), 403 @app.route('/manifest.json') def manifest(): return send_from_directory('static', 'manifest.json') @app.route('/src') def src(): return send_from_directory('static', 'src') @app.route('/dashboard') @login_required def dashboard(): #if request.method == 'GET': #if current_user.is_authenticated == False: # return redirect(url_for('thelogin')) #else: old_qr = mongo.db.users.find_one({'_id': ObjectId(current_user.id)}) print(old_qr.get('referral_code')) invite_code = str(uuid4()) qr_update = mongo.db.users.update_one({'_id': ObjectId(current_user.id)}, {'$set': {'referral_code': invite_code}}) print(invite_code) if not current_user.is_admin: pins = list(mongo.db.pins.find({"added_by": current_user.id})) return render_template('dashboard.html', pins=pins, invite_code=invite_code) if current_user.is_admin: users = list(mongo.db.users.find()) pins = list(mongo.db.pins.find()) return render_template('dashboard.html', users=users, pins=pins, invite_code=invite_code) @app.cli.command('add_user') def add_user(): username = input("Enter Username:\n") pwd = input("Add pass:\n") admin_user = { "username": username, "pwd": generate_password_hash(pwd), "referral_code": str(uuid4()), "invited_by": None, "is_admin": True } mongo.db.users.insert_one(admin_user) print(f"Admin {username} added! Referral code is {admin_user['referral_code']}") @app.route("/camera", methods=["GET", "POST"]) @login_required def camera(): if request.method == "POST": # 1. Grab geolocation data from the form latitude = request.form.get("latitude") longitude = request.form.get("longitude") # 2. Grab the photo file photo = request.files.get("photo") # 3. Save the file if it exists if photo: # Make sure your 'uploads' folder exists or handle dynamically photo_path = os.path.join(app.config['UPLOAD_FOLDER'], photo.filename) photo.save(photo_path) # Create a new pin with the uploaded photo data pin = { 'time': datetime.now(), 'photo': photo_path, 'lat': latitude, 'lng': longitude, 'typeofpin': request.form.get('typeofpin', 'bache'), # Default to bache if not specified 'added_by': current_user.id, 'description': request.form.get('description', 'Foto desde cámara'), # Default description 'reviewed': False, # Added reviewed field set to False 'address': str(geolocator.reverse(f"{latitude}, {longitude}")) if latitude and longitude else "Ubicación desconocida" } # Insert the new pin into the database mongo.db.pins.insert_one(pin) flash('¡Gracias por tu aportación!') # For demonstration, just redirect or return a success message return f"Photo uploaded! Lat: {latitude}, Lng: {longitude}" # If GET request, just render the camera page return render_template("camera.html") @app.route('/leaderboard') def leaderboard(): pipeline = [ {"$group": {"_id": "$added_by", "count": {"$sum": 1}}}, {"$sort": {"count": -1}}, {"$limit": 10}, ] # Convert the aggregation result to a list leaders_by_id = list(mongo.db.pins.aggregate(pipeline)) # Create a list to store the final results cleaned_leaders = [] # Process each leader for leader in leaders_by_id: user_id = leader["_id"] count = leader["count"] # Find the corresponding user try: user = mongo.db.users.find_one({"_id": ObjectId(user_id)}) if user and "username" in user: username = user["username"] if len(username) >= 2: cleaned_username = username[0] + "***" + username[-1] else: cleaned_username = username cleaned_leaders.append({"username": cleaned_username, "count": count}) except Exception as e: print(f"Error processing user_id {user_id}: {e}") continue print('THIS IS THE LEADERBOARD', cleaned_leaders) # Get stats using aggregation pipeline # Count total users total_users_result = list(mongo.db.users.aggregate([ {"$count": "total"} ])) total_users = total_users_result[0]["total"] if total_users_result else 0 # Count unique users who have added pins active_users_result = list(mongo.db.pins.aggregate([ {"$group": {"_id": None, "active_users": {"$addToSet": "$added_by"}}}, {"$project": {"active_count": {"$size": "$active_users"}}} ])) active_users = active_users_result[0]["active_count"] if active_users_result else 0 # Calculate percentage (handle case where total_users might be 0) active_percentage = round((active_users / total_users) * 100, 1) if total_users > 0 else 0 return render_template('leaderboard.html', leaders=cleaned_leaders, percentage = active_percentage) return app app=create_app() if __name__ == '__main__': app.run(debug=True)