import functools import itertools import operator import os from flask import Flask, render_template, request, url_for, redirect, flash, session, make_response from flask.cli import with_appcontext from flask_login import LoginManager, login_user, login_required, logout_user, current_user from sqlalchemy.exc import IntegrityError import click from sqlalchemy.orm import joinedload from werkzeug.security import generate_password_hash from flask_migrate import Migrate, upgrade, stamp from rifaserver.models import Seller, Purchase, Raffle, Ticket, db from apscheduler.schedulers.background import BackgroundScheduler from datetime import datetime import shutil def backup_db(instance_path): db_path = os.path.join(instance_path, 'raffle.db') backup_dir = os.path.join(instance_path, 'backup') os.makedirs(backup_dir, exist_ok=True) # Find the most recent backup file try: latest_backup = max(os.path.join(backup_dir, name) for name in os.listdir(backup_dir)) except ValueError: latest_backup = None # No backup files exist yet if latest_backup and os.path.getmtime(db_path) <= os.path.getmtime(latest_backup): # print("No changes to database, skipping backup.") pass else: timestamp = datetime.now().strftime("%Y%m%d%H%M%S") shutil.copy(db_path, os.path.join(backup_dir, f'raffle_{timestamp}.db')) def remove_cache(response): response = make_response(response) response.headers['Last-Modified'] = str(datetime.now()) response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' response.headers['Pragma'] = 'no-cache' response.headers['Expires'] = '-1' return response def create_app(): app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///raffle.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # optional, but can improve performance app.config['SECRET_KEY'] = 'idm&#@$8*RSXZpc6Wvb4' db.init_app(app) login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' login_manager.login_message = 'Favor entrar com senha para acessar esta página!' login_manager.login_message_category = 'danger' @login_manager.user_loader def load_user(user_id): return Seller.query.get(user_id) migrate = Migrate(app, db) with app.app_context(): if not os.path.isfile(os.path.join(app.instance_path, 'raffle.db')): db.create_all() stamp() scheduler = BackgroundScheduler() scheduler.add_job(func=backup_db, args=(app.instance_path,), trigger="interval", hours=1) scheduler.start() # print('App created!!') return app app = create_app() @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': user = Seller.query.filter_by(username=request.form['username']).first() if user and user.check_password(request.form['password']): login_user(user) return redirect(url_for('seller_screen')) return render_template('login.html') @app.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('login')) @app.route('/venda', methods=['GET', 'POST']) @login_required def seller_screen(): if request.method == 'POST': if not request.form['amount'].isdigit() or int(request.form['amount']) <= 0: flash("Quantidade inválida", 'warning') return redirect(url_for('seller_screen')) purchase = Purchase(seller_id=current_user.id, total_numbers=int(request.form['amount']), numbers_left=int(request.form['amount']), remarks=request.form['remarks']) purchase.generate_id() db.session.add(purchase) db.session.commit() purchase_link = url_for('numbers_screen', purchase_id=purchase.id, _external=True) return remove_cache(render_template('purchase_link.html', link=purchase_link)) return render_template('seller_screen.html') @app.route('/numeros/', methods=['GET', 'POST']) def numbers_screen(purchase_id): purchase = Purchase.query.get_or_404(purchase_id) buyer_data = session.get('buyer_data', {'name': '', 'contact': ''}) if request.method == 'POST': _validation_errors = False if not request.form['raffle_id']: flash("Rifa não escolhida, escolha uma rifa!", 'warning') _validation_errors = True if purchase.numbers_left <= 0: flash("Acabaram os números para escolher, compre mais!", 'danger') _validation_errors = True if not request.form['name']: flash("Preencher o nome é obrigatório", 'warning') _validation_errors = True if not request.form['number'].isdigit() or int(request.form['number']) <= 0: flash("Número inválido!", 'danger') _validation_errors = True if not _validation_errors: raffle = Raffle.query.get_or_404(request.form['raffle_id']) ticket = Ticket(purchase_id=purchase.id, raffle_id=raffle.id, buyer_name=request.form['name'], buyer_contact=request.form['contact'], chosen_number=request.form['number']) db.session.add(ticket) try: purchase.decrease_numbers() db.session.commit() flash(f"Número reservado: {request.form['number']}", 'success') except IntegrityError: db.session.rollback() flash(f"O número {request.form['number']} já foi escolhido para {raffle.description}!", 'warning') session['buyer_data'] = {'name': request.form['name'], 'contact': request.form['contact']} return redirect(url_for('numbers_screen', purchase_id=purchase_id)) raffles = Raffle.query.all() tickets = Ticket.query.filter_by(purchase_id=purchase_id).options(joinedload(Ticket.raffle)).order_by(Ticket.raffle_id).all() grouped_tickets = {k: list(v) for k, v in itertools.groupby(tickets, key=operator.attrgetter('raffle.description'))} # tickets = Ticket.query.filter_by(purchase_id=purchase_id).all() return remove_cache(render_template('purchase.html', purchase=purchase, raffles=raffles, grouped_tickets=grouped_tickets, buyer_data=buyer_data)) @app.route('/vendas', methods=['GET']) @login_required def seller_purchases(): seller_id = current_user.id purchases = Purchase.query.filter_by(seller_id=seller_id).order_by(Purchase.id.desc()).all() return render_template('seller_purchases.html', purchases=purchases) @app.cli.command('create-seller') @click.argument('username') @click.argument('password') def create_seller(username, password): """Create a new seller""" from rifaserver.models import Seller, db # check if seller exists existing_seller = Seller.query.filter_by(username=username).first() if existing_seller: click.echo(f"Username {username} already exists.") return seller = Seller( username=username, password_hash=generate_password_hash(password) ) db.session.add(seller) db.session.commit() click.echo(f"Seller: {username} created.") @app.cli.command('cria-rifa') @click.argument('descricao') @with_appcontext def create_raffle(descricao): """Create a new raffle""" from rifaserver.models import db, Raffle # Create a new raffle raffle = Raffle(description=descricao) db.session.add(raffle) db.session.commit() click.echo(f"Rifa '{descricao}' criada.") @app.cli.command('muda-senha') @click.argument('usuario') @click.argument('nova_senha') @with_appcontext def change_password(usuario, nova_senha): """Change password for a seller""" from rifaserver.models import db, Seller from werkzeug.security import generate_password_hash # Find the seller seller = Seller.query.filter_by(username=usuario).first() if not seller: click.echo(f"Vendedor não encontrado: {usuario}") return # Change the password and commit the changes seller.password_hash = generate_password_hash(nova_senha) db.session.commit() click.echo(f"Senha para {usuario} foi atualizada.") @app.cli.command('apply_migrations') @with_appcontext def apply_migrations_command(): """Apply database migrations.""" upgrade()