Compare commits

...

2 Commits
main ... main

Author SHA1 Message Date
67fae641a3 Models Asafe 2025-05-22 20:23:14 -04:00
9e9d8958c1 Template MVC básico com Jinja 2025-04-03 11:59:32 -04:00
14 changed files with 314 additions and 0 deletions

View File

@ -1,8 +1,31 @@
from flask import Flask from flask import Flask
from app.database import db
# Importação dos blueprints
from app.routes.usuario import usuarios_bp
from app.routes.organizacao import organizacoes_bp
from app.routes.documentos import documentos_bp
def create_app():
app = Flask(__name__)
app.config.from_object("config")
# Inicializa a extensão SQLAlchemy
db.init_app(app)
# Registra os blueprints
app.register_blueprint(usuarios_bp)
app.register_blueprint(organizacoes_bp)
app.register_blueprint(documentos_bp)
return app
from flask import Flask
from flasgger import Swagger from flasgger import Swagger
from app.database import db from app.database import db
from app.models.user import User from app.models.user import User
from app.routes.user_routes import user_bp from app.routes.user_routes import user_bp
from app.routes.login_form import login_bp
from app.routes.dashboard import dashboard_bp
from app.routes.invoice_routes import invoice_bp from app.routes.invoice_routes import invoice_bp
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
@ -54,5 +77,7 @@ def create_app():
# Registrar Blueprints # Registrar Blueprints
app.register_blueprint(user_bp, url_prefix="/users") app.register_blueprint(user_bp, url_prefix="/users")
app.register_blueprint(invoice_bp, url_prefix="/invoices") app.register_blueprint(invoice_bp, url_prefix="/invoices")
app.register_blueprint(login_bp, url_prefix="/login_bp")
app.register_blueprint(dashboard_bp, url_prefix="/dashboard")
return app return app

View File

View File

@ -0,0 +1,13 @@
from app.auth import generate_token
from app.models.user import User
from werkzeug.security import check_password_hash
from flask import jsonify
def authenticate_user(email, password):
user = User.query.filter_by(email=email).first()
if not user or not check_password_hash(user.password, password):
return None, None
token = generate_token(user)
return user, token

15
app/models/Documento.py Normal file
View File

@ -0,0 +1,15 @@
# documento.py
from app.database import db
import uuid
class Documento(db.Model): #
__tablename__ = 'documentos'
id = db.Column(db.UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)
nome_arquivo = db.Column(db.String, nullable=False)
caminho_arquivo = db.Column(db.String, nullable=False)
usuario_id = db.Column(db.Integer, db.ForeignKey('usuario.id'), nullable=False)
criado_em = db.Column(db.DateTime(timezone=True), server_default=db.func.now(), nullable=False)
usuario = db.relationship('Usuario', back_populates='documentos')

13
app/models/Organizacao.py Normal file
View File

@ -0,0 +1,13 @@
from app.database import db
class Organizacao(db.Model):
__tablename__ = 'organizacao'
id = db.Column(db.Integer, primary_key=True,
autoincrement=True, unique=True)
nome = db.Column(db.String, nullable=False, unique=True)
criado_em = db.Column(db.DateTime(timezone=True),
server_default=db.func.now(), nullable=False)
usuarios = db.relationship('Usuario', back_populates='organizacao')

24
app/models/usuario.py Normal file
View File

@ -0,0 +1,24 @@
# usuario.py
from app.database import db
class Usuario(db.Model):
__tablename__ = 'usuario'
id = db.Column(db.Integer, primary_key=True,
autoincrement=True, unique=True)
nome = db.Column(db.String, nullable=False)
email = db.Column(db.String, nullable=False, unique=True)
senha_hash = db.Column(db.String, nullable=False)
organizacao_id = db.Column(db.Integer, db.ForeignKey(
'organizacao.id'), nullable=False)
criado_em = db.Column(db.DateTime(timezone=True),
server_default=db.func.now(), nullable=False)
atualizado_em = db.Column(db.DateTime(
timezone=True), onupdate=db.func.now())
organizacao = db.relationship('Organizacao', back_populates='usuarios')
documentos = db.relationship('Documento', back_populates='usuario')
def __repr__(self):
return f'<Usuario {self.nome}>'

12
app/routes/dashboard.py Normal file
View File

@ -0,0 +1,12 @@
from flask import Blueprint, render_template, session, redirect, url_for, flash
dashboard_bp = Blueprint("dashboard", __name__, template_folder="templates")
@dashboard_bp.route("/")
def dashboard():
"""Protege a rota do dashboard"""
if "user_id" not in session:
flash("Faça login para acessar o dashboard.", "warning")
return redirect(url_for("login_form.login"))
return render_template("dashboard.html", user_email=session.get("user_email"))

44
app/routes/documentos.py Normal file
View File

@ -0,0 +1,44 @@
from flask import Blueprint, request, jsonify
from app.database import db
from app.models import Documento
documentos_bp = Blueprint('documentos', __name__, url_prefix='/documentos')
@documentos_bp.route('/', methods=['GET'])
def get_documentos():
documentos = Documento.query.all()
documentos_data = [
{
'id': str(doc.id),
'nome_arquivo': doc.nome_arquivo,
'caminho_arquivo': doc.caminho_arquivo,
'usuario_id': doc.usuario_id,
'criado_em': doc.criado_em
}
for doc in documentos
]
return jsonify(documentos_data), 200
@documentos_bp.route('/', methods=['POST'])
def create_documento():
data = request.get_json()
if not all(k in data for k in ('nome_arquivo', 'caminho_arquivo', 'usuario_id')):
return jsonify({'error': 'Campos obrigatórios ausentes'}), 400
try:
novo_documento = Documento(
nome_arquivo=data['nome_arquivo'],
caminho_arquivo=data['caminho_arquivo'],
usuario_id=data['usuario_id']
)
db.session.add(novo_documento)
db.session.commit()
return jsonify({
'message': 'documento criado com sucesso',
'id': str(novo_documento.id)
}), 201
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500

30
app/routes/login_form.py Normal file
View File

@ -0,0 +1,30 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
from app.controllers.auth_controller import authenticate_user
login_bp = Blueprint("login_form", __name__, template_folder="templates")
@login_bp.route("/", methods=["GET", "POST"])
def login():
"""Rota de login"""
if request.method == "POST":
email = request.form.get("email")
password = request.form.get("password")
user, token = authenticate_user(email, password)
if user:
session["user_id"] = user.id
session["user_email"] = user.email
flash("Login bem-sucedido!", "success")
return redirect(url_for("dashboard.dashboard"))
flash("Credenciais inválidas!", "danger")
return render_template("login.html")
@login_bp.route("/logout")
def logout():
"""Rota de logout"""
session.clear()
flash("Você saiu da conta.", "info")
return redirect(url_for("login_form.login"))

43
app/routes/organizacao.py Normal file
View File

@ -0,0 +1,43 @@
from flask import Blueprint, request, jsonify
from app.database import db
from app.models import Organizacao # ajuste conforme sua estrutura
organizacoes_bp = Blueprint(
'organizacoes', __name__, url_prefix='/organizacoes')
@organizacoes_bp.route('/', methods=['GET'])
def get_organizacoes():
organizacoes = Organizacao.query.all()
data = [
{
'id': org.id,
'nome': org.nome,
'criado_em': org.criado_em
}
for org in organizacoes
]
return jsonify(data), 200
@organizacoes_bp.route('/', methods=['POST'])
def create_organizacao():
data = request.get_json()
if not data or 'nome' not in data:
return jsonify({'error': 'O campo "nome" é obrigatório'}), 400
if Organizacao.query.filter_by(nome=data['nome']).first():
return jsonify({'error': 'Já existe uma organização com este nome'}), 409
try:
nova_org = Organizacao(nome=data['nome'])
db.session.add(nova_org)
db.session.commit()
return jsonify({
'message': 'Organização criada com sucesso',
'id': nova_org.id
}), 201
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500

44
app/routes/usuario.py Normal file
View File

@ -0,0 +1,44 @@
from flask import Blueprint, request, jsonify
from app.database import db
from app.models.usuario import Usuario # nome correto do modelo
usuarios_bp = Blueprint('usuarios', __name__, url_prefix='/usuarios')
@usuarios_bp.route('/', methods=['GET'])
def get_usuarios():
usuarios = Usuario.query.all()
usuarios_data = [
{
'id': usuario.id,
'nome': usuario.nome,
'email': usuario.email,
'organizacao_id': usuario.organizacao_id,
'criado_em': usuario.criado_em,
'atualizado_em': usuario.atualizado_em
}
for usuario in usuarios
]
return jsonify(usuarios_data), 200
@usuarios_bp.route('/', methods=['POST'])
def create_usuario():
data = request.get_json()
if not all(k in data for k in ('nome', 'email', 'senha_hash', 'organizacao_id')):
return jsonify({'error': 'Campos obrigatórios ausentes'}), 400
if Usuario.query.filter_by(email=data['email']).first():
return jsonify({'error': 'E-mail já está em uso'}), 409
novo_usuario = Usuario(
nome=data['nome'],
email=data['email'],
senha_hash=data['senha_hash'],
organizacao_id=data['organizacao_id']
)
db.session.add(novo_usuario)
db.session.commit()
return jsonify({'message': 'Usuário criado com sucesso', 'id': novo_usuario.id}), 201

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
</head>
<body>
<h1>Bem-vindo ao Dashboard!</h1>
<p>Usuário logado: {{ user_email }}</p>
<a href="{{ url_for('login_form.login') }}">Sair</a>
</body>
</html>

32
app/templates/login.html Normal file
View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="login-container">
<h2>Login</h2>
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<label for="email">E-mail:</label>
<input type="email" name="email" required>
<label for="password">Senha:</label>
<input type="password" name="password" required>
<button type="submit">Entrar</button>
</form>
</div>
</body>
</html>

View File

@ -1,13 +1,16 @@
attrs==25.1.0 attrs==25.1.0
blinker==1.9.0 blinker==1.9.0
click==8.1.8 click==8.1.8
exceptiongroup==1.2.2
factory_boy==3.3.3 factory_boy==3.3.3
Faker==36.2.2 Faker==36.2.2
flasgger==0.9.7.1 flasgger==0.9.7.1
Flask==3.1.0 Flask==3.1.0
Flask-JWT-Extended==4.7.1
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
Flask-Testing==0.8.1 Flask-Testing==0.8.1
greenlet==3.1.1 greenlet==3.1.1
importlib_metadata==8.6.1
iniconfig==2.0.0 iniconfig==2.0.0
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.6 Jinja2==3.1.6
@ -17,12 +20,15 @@ MarkupSafe==3.0.2
mistune==3.1.2 mistune==3.1.2
packaging==24.2 packaging==24.2
pluggy==1.5.0 pluggy==1.5.0
PyJWT==2.10.1
pytest==8.3.5 pytest==8.3.5
PyYAML==6.0.2 PyYAML==6.0.2
referencing==0.36.2 referencing==0.36.2
rpds-py==0.23.1 rpds-py==0.23.1
six==1.17.0 six==1.17.0
SQLAlchemy==2.0.38 SQLAlchemy==2.0.38
tomli==2.2.1
typing_extensions==4.12.2 typing_extensions==4.12.2
tzdata==2025.1 tzdata==2025.1
Werkzeug==3.1.3 Werkzeug==3.1.3
zipp==3.21.0