from flask import Flask, request, render_template, redirect, url_for, flash, session, jsonify from functions.database import ( Base, Militante, CotaMensal, TipoPagamento, Pagamento, MaterialVendido, TipoMaterial, VendaJornalAvulso, engine, AssinaturaAnual, RelatorioCotasMensais, RelatorioVendasMateriais, Usuario, get_db_connection, ) from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from datetime import datetime, timedelta from flask_bootstrap import Bootstrap5 from routes.cota import cota_bp from functions.validations import validar_cpf from functools import wraps from pathlib import Path from time import time from create_admin import generate_qr_code app = Flask(__name__) app.secret_key = 'sua_chave_secreta_aqui' # Necessário para sessões do Flask bootstrap = Bootstrap5(app) # Configuração da sessão do SQLAlchemy db_session = get_db_connection() # Decorator para verificar se o usuário está logado def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: flash('Por favor, faça login para acessar esta página.', 'warning') return redirect(url_for('login')) return f(*args, **kwargs) return decorated_function # Decorator para verificar se a sessão expirou def session_timeout(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: return redirect(url_for('login')) # Verificar se existe timestamp do último acesso if 'last_activity' in session: last_activity = datetime.fromtimestamp(session['last_activity']) now = datetime.now() # Se passaram mais de 2 horas if now - last_activity > timedelta(hours=2): # Registrar o logout por timeout try: user = db_session.query(Usuario).get(session['user_id']) if user: user.ultimo_logout = datetime.now() user.motivo_logout = "Timeout de sessão" db_session.commit() except Exception as e: print(f"Erro ao registrar logout por timeout: {e}") session.clear() flash('Sua sessão expirou. Por favor, faça login novamente.', 'warning') return redirect(url_for('login')) # Atualizar timestamp de último acesso session['last_activity'] = time() return f(*args, **kwargs) return decorated_function # Rota raiz - redireciona para login se não estiver autenticado @app.route("/") def index(): if 'user_id' not in session: return redirect(url_for('login')) return redirect(url_for('home')) # Rota de login @app.route("/login", methods=["GET", "POST"]) def login(): if 'user_id' in session: return redirect(url_for('home')) if request.method == "POST": username = request.form.get("username") password = request.form.get("password") otp = request.form.get("otp") # Log dos dados recebidos (sem a senha) print(f"Tentativa de login - Username: {username}, OTP fornecido: {'Sim' if otp else 'Não'}") user = db_session.query(Usuario).filter_by(username=username).first() if not user: print(f"Erro: Usuário '{username}' não encontrado") flash('Usuário não encontrado', 'danger') return render_template('login.html') if not user.check_password(password): print(f"Erro: Senha incorreta para o usuário '{username}'") flash('Senha incorreta', 'danger') return render_template('login.html') if not user.verify_otp(otp): print(f"Erro: Código OTP inválido para o usuário '{username}'") print(f"OTP fornecido: {otp}") print(f"OTP secret do usuário: {user.otp_secret}") flash('Código OTP inválido', 'danger') return render_template('login.html') # Se chegou aqui, login bem sucedido print(f"Login bem sucedido para o usuário '{username}'") session['user_id'] = user.id session['is_admin'] = user.is_admin session['last_activity'] = time() # Inicializar timestamp # Registrar horário do login user.ultimo_login = datetime.now() db_session.commit() next_page = request.args.get('next') flash('Login realizado com sucesso!', 'success') return redirect(next_page or url_for('home')) return render_template('login.html') # Rota de logout @app.route("/logout") def logout(): session.clear() # Limpa a sessão do Flask flash('Você foi desconectado com sucesso.', 'info') return redirect(url_for('login')) # Rota home (protegida) @app.route("/home") @login_required @session_timeout def home(): """Página inicial do sistema""" try: links = [] # Filtrar apenas as rotas que queremos mostrar allowed_endpoints = { 'listar_militantes': 'Militantes', 'listar_cotas': 'Cotas', 'listar_pagamentos': 'Pagamentos', 'listar_materiais': 'Materiais', 'listar_vendas_jornal': 'Vendas de Jornal', 'listar_assinaturas': 'Assinaturas' } for rule in app.url_map.iter_rules(): if (rule.endpoint in allowed_endpoints and "GET" in rule.methods and len(rule.arguments) == 0): # Apenas rotas sem parâmetros url = url_for(rule.endpoint) nome = allowed_endpoints[rule.endpoint] links.append((url, nome)) # Ordenar links pelo nome links.sort(key=lambda x: x[1]) return render_template('home.html', links=links) except Exception as e: print(f"Erro na página inicial: {e}") import traceback traceback.print_exc() flash('Erro ao carregar a página inicial', 'error') return render_template('home.html', links=[]) # Rota para criar um novo militante @app.route("/militantes/novo", methods=["GET", "POST"]) @login_required @session_timeout def novo_militante(): if request.method == "POST": cpf = request.form["cpf"] if not validar_cpf(cpf): flash('CPF inválido. Por favor, verifique o número informado.', 'error') return render_template("novo_militante.html", dados_anteriores=request.form) novo_militante = Militante( nome=request.form["nome"], cpf=cpf, email=request.form["email"], telefone=request.form["telefone"], endereco=request.form["endereco"], filiado=bool(request.form.get("filiado", False)) ) db_session.add(novo_militante) try: db_session.commit() flash('Militante cadastrado com sucesso!', 'success') return redirect(url_for("listar_militantes")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar militante. Verifique se o CPF ou email já não estão cadastrados.', 'error') return render_template("novo_militante.html", dados_anteriores=request.form) return render_template("novo_militante.html") # Rota para listar militantes @app.route("/militantes") @login_required @session_timeout def listar_militantes(): militantes = db_session.query(Militante).all() return render_template("listar_militantes.html", militantes=militantes) # Rota para criar uma nova cota mensal @app.route("/cotas/novo", methods=["GET", "POST"]) @login_required @session_timeout def nova_cota(): if request.method == "POST": cotas_mensais = CotaMensal( militante_id=request.form["militante_id"], valor_antigo=request.form["valor_antigo"], valor_novo=request.form["valor_novo"], data_alteracao=datetime.strptime(request.form["data_alteracao"], "%Y-%m-%d") ) db_session.add(cotas_mensais) try: db_session.commit() return redirect(url_for("listar_cotas")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar cota mensal. Verifique se os dados fornecidos são válidos.', 'error') return render_template("nova_cota.html") return render_template("nova_cota.html") # Rota para listar cotas mensais @app.route("/cotas") @login_required @session_timeout def listar_cotas(): cotas = db_session.query(CotaMensal).all() return render_template("listar_cotas.html", cotas=cotas) # Rota para criar um novo pagamento @app.route("/pagamentos/novo", methods=["GET", "POST"]) @login_required @session_timeout def novo_pagamento(): if request.method == "POST": pagamentos = Pagamento( militante_id=request.form["militante_id"], tipo_pagamento_id=request.form["tipo_pagamento_id"], valor=request.form["valor"], data_pagamento=datetime.strptime(request.form["data_pagamento"], "%Y-%m-%d") ) db_session.add(pagamentos) try: db_session.commit() return redirect(url_for("listar_pagamentos")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar pagamento. Verifique se os dados fornecidos são válidos.', 'error') return render_template("novo_pagamento.html") return render_template("novo_pagamento.html") # Rota para listar pagamentos @app.route("/pagamentos") @login_required @session_timeout def listar_pagamentos(): pagamentos = db_session.query(Pagamento).all() return render_template("listar_pagamentos.html", pagamentos=pagamentos) # Rota para criar um novo material vendido @app.route("/materiais/novo", methods=["GET", "POST"]) @login_required @session_timeout def novo_material(): if request.method == "POST": materiais_vendidos = MaterialVendido( militante_id=request.form["militante_id"], tipo_material_id=request.form["tipo_material_id"], descricao=request.form["descricao"], valor=request.form["valor"], data_venda=datetime.strptime(request.form["data_venda"], "%Y-%m-%d"), ) db_session.add(materiais_vendidos) try: db_session.commit() return redirect(url_for("listar_materiais")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar material vendido. Verifique se os dados fornecidos são válidos.', 'error') return render_template("novo_material.html") return render_template("novo_material.html") # Rota para listar materiais vendidos @app.route("/materiais") @login_required @session_timeout def listar_materiais(): materiais = db_session.query(MaterialVendido).all() return render_template("listar_materiais.html", materiais=materiais) # Rota para criar uma nova venda de jornais avulsos @app.route("/jornais/novo", methods=["GET", "POST"]) @login_required @session_timeout def nova_venda_jornal(): if request.method == "POST": vendas_jornais_avulsos = VendaJornalAvulso( militante_id=request.form["militante_id"], quantidade=request.form["quantidade"], valor_total=request.form["valor_total"], data_venda=datetime.strptime(request.form["data_venda"], "%Y-%m-%d"), ) db_session.add(vendas_jornais_avulsos) try: db_session.commit() return redirect(url_for("listar_vendas_jornal")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar venda de jornal avulso. Verifique se os dados fornecidos são válidos.', 'error') return render_template("nova_venda_jornal.html") return render_template("nova_venda_jornal.html") # Rota para listar vendas de jornais avulsos @app.route("/jornais") @login_required @session_timeout def listar_vendas_jornal(): vendas = db_session.query(VendaJornalAvulso).all() return render_template("listar_vendas_jornal.html", vendas=vendas) # Rota para criar uma nova assinatura anual @app.route("/assinaturas/novo", methods=["GET", "POST"]) @login_required @session_timeout def nova_assinatura(): if request.method == "POST": assinaturas_anuais = AssinaturaAnual( militante_id=request.form["militante_id"], tipo_material_id=request.form["tipo_material_id"], quantidade=request.form["quantidade"], valor_total=request.form["valor_total"], data_inicio=datetime.strptime(request.form["data_inicio"], "%Y-%m-%d"), data_fim=datetime.strptime(request.form["data_fim"], "%Y-%m-%d") ) db_session.add(assinaturas_anuais) try: db_session.commit() return redirect(url_for("listar_assinaturas")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar assinatura anual. Verifique se os dados fornecidos são válidos.', 'error') return render_template("nova_assinatura.html") return render_template("nova_assinatura.html") # Rota para listar assinaturas anuais @app.route("/assinaturas") @login_required @session_timeout def listar_assinaturas(): assinaturas = db_session.query(AssinaturaAnual).all() return render_template("listar_assinaturas.html", assinaturas=assinaturas) # Rota para criar um novo relatório de cotas mensais @app.route("/relatorios/cotas/novo", methods=["GET", "POST"]) @login_required @session_timeout def novo_relatorio_cotas(): if request.method == "POST": relatorio_cotas_mensais = RelatorioCotasMensais( setor_id=request.form["setor_id"], comite_id=request.form["comite_id"], total_cotas=request.form["total_cotas"], data_relatorio=datetime.strptime(request.form["data_relatorio"], "%Y-%m-%d") ) db_session.add(relatorio_cotas_mensais) try: db_session.commit() return redirect(url_for("listar_relatorios_cotas")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar relatório de cotas mensais. Verifique se os dados fornecidos são válidos.', 'error') return render_template("novo_relatorio_cotas.html") return render_template("novo_relatorio_cotas.html") # Rota para listar relatórios de cotas mensais @app.route("/relatorios/cotas") @login_required @session_timeout def listar_relatorios_cotas(): relatorios = db_session.query(RelatorioCotasMensais).all() return render_template("listar_relatorios_cotas.html", relatorios=relatorios) # Rota para criar um novo relatório de vendas de materiais @app.route("/relatorios/vendas/novo", methods=["GET", "POST"]) @login_required @session_timeout def novo_relatorio_vendas(): if request.method == "POST": relatorio_vendas_materiais = RelatorioVendasMateriais( setor_id=request.form["setor_id"], comite_id=request.form["comite_id"], total_vendas=request.form["total_vendas"], data_relatorio=datetime.strptime(request.form["data_relatorio"], "%Y-%m-%d") ) db_session.add(relatorio_vendas_materiais) try: db_session.commit() return redirect(url_for("listar_relatorios_vendas")) except Exception as e: print(e) db_session.rollback() flash('Erro ao cadastrar relatório de vendas de materiais. Verifique se os dados fornecidos são válidos.', 'error') return render_template("novo_relatorio_vendas.html") return render_template("novo_relatorio_vendas.html") # Rota para listar relatórios de vendas de materiais @app.route("/relatorios/vendas") @login_required @session_timeout def listar_relatorios_vendas(): relatorios = db_session.query(RelatorioVendasMateriais).all() return render_template("listar_relatorios_vendas.html", relatorios=relatorios) @app.route("/militantes/editar/", methods=["GET", "POST"]) @login_required @session_timeout def editar_militante(id): militante = db_session.query(Militante).get(id) if not militante: flash('Militante não encontrado.', 'error') return redirect(url_for('listar_militantes')) if request.method == "POST": cpf = request.form["cpf"] if cpf != militante.cpf and not validar_cpf(cpf): # Só valida se o CPF foi alterado flash('CPF inválido. Por favor, verifique o número informado.', 'error') return render_template("editar_militante.html", militante=militante) try: militante.nome = request.form["nome"] militante.cpf = cpf militante.email = request.form["email"] militante.telefone = request.form["telefone"] militante.endereco = request.form["endereco"] militante.filiado = bool(request.form.get("filiado", False)) db_session.commit() flash('Militante atualizado com sucesso!', 'success') return redirect(url_for('listar_militantes')) except Exception as e: db_session.rollback() flash('Erro ao atualizar militante. Verifique se o CPF ou email já não estão cadastrados.', 'error') return render_template("editar_militante.html", militante=militante) return render_template("editar_militante.html", militante=militante) # Rota para criar novo usuário @app.route("/usuarios/novo", methods=["GET", "POST"]) @login_required def novo_usuario(): if not session.get('is_admin'): flash('Acesso negado. Apenas administradores podem criar novos usuários.', 'danger') return redirect(url_for('home')) if request.method == "POST": username = request.form.get("username") email = request.form.get("email") password = request.form.get("password") confirm_password = request.form.get("confirm_password") is_admin = bool(request.form.get("is_admin")) # Validações if password != confirm_password: flash('As senhas não conferem.', 'danger') return render_template('novo_usuario.html') # Verificar se usuário já existe if db_session.query(Usuario).filter_by(username=username).first(): flash('Nome de usuário já existe.', 'danger') return render_template('novo_usuario.html') if db_session.query(Usuario).filter_by(email=email).first(): flash('E-mail já cadastrado.', 'danger') return render_template('novo_usuario.html') # Criar novo usuário try: novo_usuario = Usuario( username=username, password=password, is_admin=is_admin ) novo_usuario.email = email db_session.add(novo_usuario) db_session.commit() # Gerar QR code usando a função do create_admin.py qr_uri = novo_usuario.get_otp_uri() qr_path = generate_qr_code(novo_usuario) flash('Usuário criado com sucesso!', 'success') return render_template('mostrar_qr_code.html', qr_uri=qr_uri) except Exception as e: db_session.rollback() flash('Erro ao criar usuário. Por favor, tente novamente.', 'danger') print(f"Erro: {e}") return render_template('novo_usuario.html') return render_template('novo_usuario.html') @app.route('/check_session') def check_session(): if 'last_activity' not in session: return jsonify({'expired': True}) last_activity = datetime.fromtimestamp(session['last_activity']) now = datetime.now() if now - last_activity > timedelta(hours=2): # Registrar o logout por timeout try: user = db_session.query(Usuario).get(session.get('user_id')) if user: user.ultimo_logout = datetime.now() user.motivo_logout = "Timeout de sessão" db_session.commit() except Exception as e: print(f"Erro ao registrar logout por timeout: {e}") session.clear() return jsonify({'expired': True}) return jsonify({'expired': False}) def create_app(): app = Flask(__name__) # ... existing code ... app.register_blueprint(cota_bp) # ... existing code ... return app # Iniciar o servidor Flask if __name__ == "__main__": # Verificar se existe usuário admin e gerar QR code admin = db_session.query(Usuario).filter_by(username="admin").first() if admin: print("\n=== Gerando QR Code para usuário admin existente ===") generate_qr_code(admin) app.run(debug=True)