2024-08-21 19:38:01 +00:00
from flask import Flask , render_template , request , redirect , url_for , flash , send_from_directory
2024-08-25 00:54:11 +00:00
from flask_pymongo import PyMongo , ObjectId
from flask_login import LoginManager , UserMixin , login_user , login_required , current_user , logout_user
2024-08-17 19:05:28 +00:00
from werkzeug . utils import secure_filename
2024-08-25 00:54:11 +00:00
from werkzeug . security import generate_password_hash , check_password_hash
2024-08-21 19:38:01 +00:00
from datetime import datetime
2024-08-25 00:54:11 +00:00
from flask_pymongo import ObjectId
2024-08-17 19:05:28 +00:00
import os
2024-08-25 00:54:11 +00:00
from uuid import uuid4
from flask_wtf import FlaskForm
from wtforms import StringField , FileField , SubmitField , DateTimeField , SelectField , PasswordField
from wtforms . validators import DataRequired , Length
2024-09-05 21:02:43 +00:00
import requests
from config import Config
2024-09-09 01:27:19 +00:00
from geopy . geocoders import Nominatim
geolocator = Nominatim ( user_agent = " Bachemapa @ baches.qro.mx " )
2024-09-12 22:56:41 +00:00
2024-09-05 21:02:43 +00:00
def create_app ( config = Config ) :
2024-08-25 00:54:11 +00:00
app = Flask ( __name__ )
2024-09-05 21:02:43 +00:00
app . config . from_object ( config )
2024-08-17 19:05:28 +00:00
2024-08-25 00:54:11 +00:00
mongo = PyMongo ( app )
login_manager = LoginManager ( app )
2024-09-12 22:56:41 +00:00
login_manager . login_view = ' thelogin '
2024-08-25 00:54:11 +00:00
login_manager . session_protection = " strong "
2024-08-17 19:05:28 +00:00
2024-08-25 00:54:11 +00:00
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 ) :
2024-09-05 03:22:51 +00:00
description = StringField ( ' ¿Qué estamos viendo? ' , validators = [ DataRequired ( ) ] )
2024-08-25 00:54:11 +00:00
photo = FileField ( ' Evidencia fotogénica ' , validators = [ DataRequired ( ) ] )
timedate = DateTimeField ( default = datetime . now ( ) )
2024-09-09 01:03:06 +00:00
typeofpin = SelectField ( ' Tipo de cosa ' , choices = [ ' bache ' , ' coladera ' , ' obra sin terminar ' , ' escombro ' , ' robo-asalto ' , ' biciestacionamiento ' , ' mala iluminación ' , ' bici blanca ' , ' zapato blanco ' ] )
2024-08-31 23:50:22 +00:00
2024-08-25 00:54:11 +00:00
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 ) :
2024-09-09 01:03:06 +00:00
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 " ) ) ] )
2024-08-25 00:54:11 +00:00
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 ( )
2025-03-24 18:02:15 +00:00
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 )
2024-08-25 00:54:11 +00:00
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 ,
2024-09-09 01:27:19 +00:00
' description ' : request . form [ ' description ' ] ,
2025-03-16 01:11:32 +00:00
' address ' : str ( geolocator . reverse ( str ( request . form [ ' lat ' ] ) + " , " + request . form [ ' lng ' ] ) ) ,
' reviewed ' : True ,
' fixed ' : False ,
' last_mod ' : datetime . now ( ) ,
' votes ' : - 1 ,
' removal_reason ' : None ,
2024-08-25 00:54:11 +00:00
}
2024-09-09 01:27:19 +00:00
print ( geolocator . reverse ( str ( request . form [ ' lat ' ] ) + " , " + request . form [ ' lng ' ] ) )
2024-08-25 00:54:11 +00:00
mongo . db . pins . insert_one ( pin )
flash ( ' ¡Gracias por tu aportación! ' )
return redirect ( url_for ( ' index ' ) )
else :
return allowed_file ( photo . filename ) , 404
2024-08-31 23:50:22 +00:00
@app.route ( ' /quienes ' )
def quienes ( ) :
return render_template ( ' quienes.html ' )
2024-08-25 00:54:11 +00:00
@app.route ( ' /uploads/<name> ' )
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/<referral_code> ' , methods = [ ' GET ' , ' POST ' ] )
def registrame ( referral_code ) :
inviter = mongo . db . users . find_one ( { " referral_code " : referral_code } )
if not inviter :
2025-03-08 21:29:24 +00:00
message = " Ooops, parece que nadie te invitó aquí... Puedes saber más sobre esta decisión aquí https://baches.qro.mx/quienes#preguntas "
2024-09-05 21:02:43 +00:00
return render_template ( ' error.html ' , message = message ) , 406
2024-08-25 00:54:11 +00:00
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
2024-08-21 19:38:01 +00:00
}
2024-08-25 00:54:11 +00:00
new_user_id = mongo . db . users . insert_one ( new_user_data ) . inserted_id
2024-09-12 22:13:15 +00:00
str_id = str ( new_user_id )
mongo . db . users . update_one (
{ ' _id ' : new_user_id } ,
{ ' $set ' : { ' str_id ' : str_id } }
)
2024-08-25 00:54:11 +00:00
invite_link = url_for ( ' registrame ' , referral_code = new_user_data [ ' referral_code ' ] , _external = True )
2025-04-14 20:17:39 +00:00
login_user ( load_user ( new_user_id ) , remember = True )
flash ( ' Usuario registrado exitosamente ' )
2024-09-05 03:22:51 +00:00
return redirect ( url_for ( ' index ' ) )
#return render_template('dashboard.html', invite_link=invite_link)
2024-08-25 00:54:11 +00:00
if request . method == ' GET ' :
2025-03-24 18:02:15 +00:00
return render_template ( ' register.html ' , rform = RegistrationForm ( ) )
2024-08-25 00:54:11 +00:00
@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 )
2025-04-14 20:17:39 +00:00
login_user ( user , remember = True )
2024-09-05 03:22:51 +00:00
return redirect ( url_for ( ' index ' ) )
2024-08-25 00:54:11 +00:00
else :
2025-04-14 21:08:35 +00:00
return render_template ( ' login.html ' , messages = ' Ooops, no hay tal usuario ' , login_form = form2 )
return render_template ( ' login.html ' , login_form = form2 )
2024-08-25 00:54:11 +00:00
@app.route ( ' /logout ' )
@login_required
def logout ( ) :
logout_user ( )
2024-08-31 23:50:22 +00:00
flash ( ' Cerraste sesión exitosamente ' )
2024-08-25 00:54:11 +00:00
return redirect ( ' / ' )
@app.route ( ' /desheredame/<user_id> ' )
@login_required
def desheredame ( user_id ) :
if not current_user . is_admin :
return redirect ( url_for ( ' index ' ) )
else :
2024-09-05 03:22:51 +00:00
user_to_remove = mongo . db . users . find_one ( { " _id " : ObjectId ( user_id ) } )
2024-08-25 00:54:11 +00:00
if not user_to_remove :
2024-09-05 21:02:43 +00:00
message = " A este ya me lo desheredaron "
return render_template ( ' error.html ' , message = message ) , 404
2024-08-25 00:54:11 +00:00
else :
2024-09-05 03:22:51 +00:00
# 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
2024-08-25 00:54:11 +00:00
mongo . db . users . delete_many ( { " invited_by " : user_id } )
2024-09-05 03:22:51 +00:00
#eliminamos los pines del usuario infractor
mongo . db . pins . delete_many ( { " added_by " : user_to_remove . id } )
# finalmente, eliminamos al usuario infractor
2024-08-25 00:54:11 +00:00
mongo . db . users . delete_one ( { " _id " : ObjectId ( user_id ) } )
return redirect ( url_for ( ' dashboard ' ) )
@app.route ( " /remove_pin/<pin_id> " )
@login_required
def remove_pin ( pin_id ) :
2024-09-03 20:22:02 +00:00
actual_pin = mongo . db . pins . find_one ( { " _id " : ObjectId ( pin_id ) } )
print ( actual_pin )
added_by = actual_pin . get ( " added_by " )
print ( added_by )
2024-09-09 02:04:19 +00:00
print ( current_user . id )
if current_user . is_admin or current_user . id == added_by :
2024-08-25 00:54:11 +00:00
mongo . db . pins . delete_one ( { " _id " : ObjectId ( pin_id ) } )
return redirect ( url_for ( ' dashboard ' ) )
2024-09-09 02:04:19 +00:00
else :
return redirect ( url_for ( ' index ' ) )
2025-03-15 00:00:31 +00:00
@app.route ( " /edit_pin/<pin_id> " , 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 ,
2025-03-16 01:11:32 +00:00
" address " : address ,
" last_mod " : datetime . now ( )
2025-03-15 00:00:31 +00:00
} }
)
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
2025-03-14 06:48:49 +00:00
@app.route ( ' /manifest.json ' )
def manifest ( ) :
return send_from_directory ( ' static ' , ' manifest.json ' )
2025-03-24 18:02:15 +00:00
@app.route ( ' /src ' )
def src ( ) :
return send_from_directory ( ' static ' , ' src ' )
2024-08-25 00:54:11 +00:00
@app.route ( ' /dashboard ' )
@login_required
def dashboard ( ) :
2024-09-12 22:56:41 +00:00
#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 ) } )
2025-03-24 18:02:15 +00:00
print ( old_qr . get ( ' referral_code ' ) )
2024-09-12 22:56:41 +00:00
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 )
2024-08-25 00:54:11 +00:00
@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 ' ] } " )
2025-04-14 20:17:39 +00:00
2025-03-14 06:48:49 +00:00
@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 )
2025-03-15 00:00:31 +00:00
# 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! ' )
2025-03-14 06:48:49 +00:00
# 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 " )
2024-09-10 02:37:18 +00:00
@app.route ( ' /leaderboard ' )
def leaderboard ( ) :
2025-03-07 21:55:11 +00:00
pipeline = [
2024-09-12 22:13:15 +00:00
{ " $group " : { " _id " : " $added_by " , " count " : { " $sum " : 1 } } } ,
{ " $sort " : { " count " : - 1 } } ,
{ " $limit " : 10 } ,
2025-03-07 21:55:11 +00:00
]
# 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
2024-09-12 20:35:52 +00:00
2025-03-07 21:55:11 +00:00
# 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 )
2024-09-10 02:37:18 +00:00
2024-08-25 00:54:11 +00:00
return app
2024-08-17 19:05:28 +00:00
2024-09-10 02:37:18 +00:00
2024-08-25 00:54:11 +00:00
app = create_app ( )
2024-08-17 19:05:28 +00:00
if __name__ == ' __main__ ' :
app . run ( debug = True )