Compare commits
7 Commits
f201c8edbd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96f20dc7e5 | ||
|
|
2f3441d435 | ||
|
|
89cac2cf8e | ||
|
|
afeaf36479 | ||
|
|
aac7fa0317 | ||
|
|
880c892a70 | ||
|
|
4a14f533d2 |
660
app/app.py
Normal file
660
app/app.py
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
from flask import Flask, request, jsonify, send_file
|
||||||
|
from flask_cors import CORS
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
|
import bcrypt
|
||||||
|
import os
|
||||||
|
from bson import ObjectId
|
||||||
|
import json
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
|
# Configurações do MongoDB
|
||||||
|
MONGO_URI = os.getenv('MONGO_URI', 'mongodb://localhost:27017/')
|
||||||
|
DB_NAME = 'CtrlCash'
|
||||||
|
|
||||||
|
# Conexão com MongoDB
|
||||||
|
try:
|
||||||
|
client = MongoClient(MONGO_URI)
|
||||||
|
db = client[DB_NAME]
|
||||||
|
print(f"✅ Conectado ao MongoDB: {DB_NAME}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erro ao conectar com MongoDB: {e}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Collections
|
||||||
|
users_collection = db['users']
|
||||||
|
transactions_collection = db['transactions']
|
||||||
|
categories_collection = db['categories']
|
||||||
|
|
||||||
|
class JSONEncoder(json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
if isinstance(obj, ObjectId):
|
||||||
|
return str(obj)
|
||||||
|
if isinstance(obj, datetime):
|
||||||
|
return obj.isoformat()
|
||||||
|
return super().default(obj)
|
||||||
|
|
||||||
|
app.json_encoder = JSONEncoder
|
||||||
|
|
||||||
|
# Categorias padrão
|
||||||
|
default_categories = [
|
||||||
|
{"name": "Alimentação", "type": "expense", "color": "#FF6B6B"},
|
||||||
|
{"name": "Transporte", "type": "expense", "color": "#4ECDC4"},
|
||||||
|
{"name": "Moradia", "type": "expense", "color": "#45B7D1"},
|
||||||
|
{"name": "Saúde", "type": "expense", "color": "#96CEB4"},
|
||||||
|
{"name": "Educação", "type": "expense", "color": "#FFEAA7"},
|
||||||
|
{"name": "Lazer", "type": "expense", "color": "#DDA0DD"},
|
||||||
|
{"name": "Salário", "type": "income", "color": "#98D8C8"},
|
||||||
|
{"name": "Investimentos", "type": "income", "color": "#F7DC6F"},
|
||||||
|
{"name": "Freelance", "type": "income", "color": "#BB8FCE"},
|
||||||
|
{"name": "Outros", "type": "income", "color": "#95a5a6"},
|
||||||
|
{"name": "Outros", "type": "expense", "color": "#95a5a6"}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Funções auxiliares
|
||||||
|
def hash_password(password):
|
||||||
|
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||||
|
|
||||||
|
def check_password(password, hashed):
|
||||||
|
return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
|
||||||
|
|
||||||
|
def init_default_categories(user_id):
|
||||||
|
"""Insere categorias padrão para um usuário"""
|
||||||
|
categories = []
|
||||||
|
for cat in default_categories:
|
||||||
|
categories.append({
|
||||||
|
**cat,
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"created_at": datetime.now()
|
||||||
|
})
|
||||||
|
if categories:
|
||||||
|
categories_collection.insert_many(categories)
|
||||||
|
@app.route('/')
|
||||||
|
def serve_html_home():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
|
||||||
|
@app.route('/dashboard')
|
||||||
|
def serve_html_dashboard():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
|
||||||
|
@app.route('/login')
|
||||||
|
def serve_html_login():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
|
||||||
|
@app.route('/cadastro')
|
||||||
|
def serve_html_cadastro():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
|
||||||
|
@app.route('/transacoes')
|
||||||
|
def serve_html_transacoes():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
|
||||||
|
@app.route('/configuracoes')
|
||||||
|
def serve_html_configuracoes():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
|
||||||
|
@app.route('/about')
|
||||||
|
def serve_html_about():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
|
||||||
|
@app.route('/help')
|
||||||
|
def serve_html_help():
|
||||||
|
return send_file('../web/index.html')
|
||||||
|
# ROTAS DE AUTENTICAÇÃO
|
||||||
|
@app.route('/api/auth/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
email = data.get('email', '').strip().lower()
|
||||||
|
password = data.get('password', '')
|
||||||
|
name = data.get('name', '').strip()
|
||||||
|
|
||||||
|
if not email or not password or not name:
|
||||||
|
return jsonify({"error": "Todos os campos são obrigatórios"}), 400
|
||||||
|
|
||||||
|
if len(password) < 6:
|
||||||
|
return jsonify({"error": "A senha deve ter pelo menos 6 caracteres"}), 400
|
||||||
|
|
||||||
|
# Verificar se usuário já existe
|
||||||
|
if users_collection.find_one({"email": email}):
|
||||||
|
return jsonify({"error": "Email já cadastrado"}), 400
|
||||||
|
|
||||||
|
# Criar usuário
|
||||||
|
user_data = {
|
||||||
|
'email': email,
|
||||||
|
'password': hash_password(password),
|
||||||
|
'name': name,
|
||||||
|
'created_at': datetime.now(),
|
||||||
|
'profile': {
|
||||||
|
'monthly_income_goal': 5000,
|
||||||
|
'monthly_expense_limit': 2500,
|
||||||
|
'currency': 'BRL'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = users_collection.insert_one(user_data)
|
||||||
|
user_id = result.inserted_id
|
||||||
|
|
||||||
|
# Inicializar categorias
|
||||||
|
init_default_categories(user_id)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"message": "Usuário criado com sucesso",
|
||||||
|
"user": {
|
||||||
|
"id": str(user_id),
|
||||||
|
"email": email,
|
||||||
|
"name": name,
|
||||||
|
"profile": user_data['profile']
|
||||||
|
}
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro no registro: {e}")
|
||||||
|
return jsonify({"error": "Erro interno do servidor"}), 500
|
||||||
|
|
||||||
|
@app.route('/api/auth/login', methods=['POST'])
|
||||||
|
def login():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
email = data.get('email', '').strip().lower()
|
||||||
|
password = data.get('password', '')
|
||||||
|
|
||||||
|
if not email or not password:
|
||||||
|
return jsonify({"error": "Email e senha são obrigatórios"}), 400
|
||||||
|
|
||||||
|
user = users_collection.find_one({"email": email})
|
||||||
|
|
||||||
|
if not user or not check_password(password, user['password']):
|
||||||
|
return jsonify({"error": "Credenciais inválidas"}), 401
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"message": "Login realizado com sucesso",
|
||||||
|
"user": {
|
||||||
|
"id": str(user['_id']),
|
||||||
|
"email": user['email'],
|
||||||
|
"name": user['name'],
|
||||||
|
"profile": user.get('profile', {})
|
||||||
|
}
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro no login: {e}")
|
||||||
|
return jsonify({"error": "Erro interno do servidor"}), 500
|
||||||
|
|
||||||
|
# ROTAS DE TRANSAÇÕES
|
||||||
|
@app.route('/api/transactions', methods=['GET'])
|
||||||
|
def get_transactions():
|
||||||
|
try:
|
||||||
|
user_id = request.args.get('user_id')
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"transactions": []}), 200
|
||||||
|
|
||||||
|
transactions = list(transactions_collection.find(
|
||||||
|
{"user_id": ObjectId(user_id)}
|
||||||
|
).sort("date", -1))
|
||||||
|
|
||||||
|
# Converter ObjectId para string
|
||||||
|
for transaction in transactions:
|
||||||
|
transaction['id'] = str(transaction['_id'])
|
||||||
|
transaction['user_id'] = str(transaction['user_id'])
|
||||||
|
del transaction['_id']
|
||||||
|
|
||||||
|
return jsonify({"transactions": transactions}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar transações: {e}")
|
||||||
|
return jsonify({"error": "Erro ao buscar transações"}), 500
|
||||||
|
|
||||||
|
@app.route('/api/transactions', methods=['POST'])
|
||||||
|
def add_transaction():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
user_id = data.get('user_id')
|
||||||
|
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "User ID é obrigatório"}), 400
|
||||||
|
|
||||||
|
# Validar dados
|
||||||
|
amount = float(data.get('amount', 0))
|
||||||
|
description = data.get('description', '').strip()
|
||||||
|
category = data.get('category', 'Outros')
|
||||||
|
transaction_type = data.get('type', 'expense')
|
||||||
|
date_str = data.get('date', '')
|
||||||
|
|
||||||
|
if amount <= 0:
|
||||||
|
return jsonify({"error": "Valor deve ser maior que zero"}), 400
|
||||||
|
|
||||||
|
if not description:
|
||||||
|
return jsonify({"error": "Descrição é obrigatória"}), 400
|
||||||
|
|
||||||
|
# Usar data exata do frontend
|
||||||
|
if not date_str:
|
||||||
|
date_str = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
new_transaction = {
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"amount": amount,
|
||||||
|
"description": description,
|
||||||
|
"category": category,
|
||||||
|
"type": transaction_type,
|
||||||
|
"date": date_str,
|
||||||
|
"created_at": datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = transactions_collection.insert_one(new_transaction)
|
||||||
|
new_transaction['id'] = str(result.inserted_id)
|
||||||
|
new_transaction['user_id'] = user_id
|
||||||
|
del new_transaction['_id']
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"message": "Transação adicionada com sucesso",
|
||||||
|
"transaction": new_transaction
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao adicionar transação: {e}")
|
||||||
|
return jsonify({"error": "Erro ao adicionar transação"}), 500
|
||||||
|
|
||||||
|
@app.route('/api/transactions/<transaction_id>', methods=['DELETE'])
|
||||||
|
def delete_transaction(transaction_id):
|
||||||
|
try:
|
||||||
|
user_id = request.args.get('user_id')
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "User ID é obrigatório"}), 400
|
||||||
|
|
||||||
|
result = transactions_collection.delete_one({
|
||||||
|
"_id": ObjectId(transaction_id),
|
||||||
|
"user_id": ObjectId(user_id)
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.deleted_count == 0:
|
||||||
|
return jsonify({"error": "Transação não encontrada"}), 404
|
||||||
|
|
||||||
|
return jsonify({"message": "Transação deletada com sucesso"}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao deletar transação: {e}")
|
||||||
|
return jsonify({"error": "Erro ao deletar transação"}), 500
|
||||||
|
|
||||||
|
# ROTAS DE CATEGORIAS
|
||||||
|
@app.route('/api/categories', methods=['GET'])
|
||||||
|
def get_categories():
|
||||||
|
try:
|
||||||
|
user_id = request.args.get('user_id')
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
# Buscar categorias do usuário
|
||||||
|
categories = list(categories_collection.find({"user_id": ObjectId(user_id)}))
|
||||||
|
for category in categories:
|
||||||
|
category['id'] = str(category['_id'])
|
||||||
|
category['user_id'] = str(category['user_id'])
|
||||||
|
del category['_id']
|
||||||
|
else:
|
||||||
|
# Retornar categorias padrão
|
||||||
|
categories = [{"id": i, **cat} for i, cat in enumerate(default_categories)]
|
||||||
|
|
||||||
|
return jsonify({"categories": categories}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar categorias: {e}")
|
||||||
|
return jsonify({"categories": default_categories}), 200
|
||||||
|
|
||||||
|
@app.route('/api/categories', methods=['POST'])
|
||||||
|
def add_category():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
user_id = data.get('user_id')
|
||||||
|
name = data.get('name', '').strip()
|
||||||
|
category_type = data.get('type')
|
||||||
|
color = data.get('color', '#6c757d')
|
||||||
|
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "User ID é obrigatório"}), 400
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
return jsonify({"error": "Nome da categoria é obrigatório"}), 400
|
||||||
|
|
||||||
|
if not category_type or category_type not in ['income', 'expense']:
|
||||||
|
return jsonify({"error": "Tipo de categoria inválido"}), 400
|
||||||
|
|
||||||
|
# Verificar se categoria já existe
|
||||||
|
existing_category = categories_collection.find_one({
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"name": name,
|
||||||
|
"type": category_type
|
||||||
|
})
|
||||||
|
|
||||||
|
if existing_category:
|
||||||
|
return jsonify({"error": "Categoria já existe"}), 400
|
||||||
|
|
||||||
|
new_category = {
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"name": name,
|
||||||
|
"type": category_type,
|
||||||
|
"color": color,
|
||||||
|
"created_at": datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = categories_collection.insert_one(new_category)
|
||||||
|
new_category['id'] = str(result.inserted_id)
|
||||||
|
new_category['user_id'] = user_id
|
||||||
|
del new_category['_id']
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"message": "Categoria adicionada com sucesso",
|
||||||
|
"category": new_category
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao adicionar categoria: {e}")
|
||||||
|
return jsonify({"error": "Erro ao adicionar categoria"}), 500
|
||||||
|
|
||||||
|
# ROTAS DO DASHBOARD
|
||||||
|
@app.route('/api/dashboard/summary', methods=['GET'])
|
||||||
|
def get_dashboard_summary():
|
||||||
|
try:
|
||||||
|
user_id = request.args.get('user_id')
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({
|
||||||
|
"total_income": 0,
|
||||||
|
"total_expenses": 0,
|
||||||
|
"balance": 0,
|
||||||
|
"recent_transactions": []
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
# Calcular totais do mês atual
|
||||||
|
start_of_month = datetime.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
# Pipeline para receitas do mês
|
||||||
|
pipeline_income = [
|
||||||
|
{"$match": {
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"type": "income",
|
||||||
|
"date": {"$gte": start_of_month.strftime('%Y-%m-%d')}
|
||||||
|
}},
|
||||||
|
{"$group": {"_id": None, "total": {"$sum": "$amount"}}}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Pipeline para despesas do mês
|
||||||
|
pipeline_expenses = [
|
||||||
|
{"$match": {
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"type": "expense",
|
||||||
|
"date": {"$gte": start_of_month.strftime('%Y-%m-%d')}
|
||||||
|
}},
|
||||||
|
{"$group": {"_id": None, "total": {"$sum": "$amount"}}}
|
||||||
|
]
|
||||||
|
|
||||||
|
income_result = list(transactions_collection.aggregate(pipeline_income))
|
||||||
|
expense_result = list(transactions_collection.aggregate(pipeline_expenses))
|
||||||
|
|
||||||
|
total_income = income_result[0]['total'] if income_result else 0
|
||||||
|
total_expenses = expense_result[0]['total'] if expense_result else 0
|
||||||
|
balance = total_income - total_expenses
|
||||||
|
|
||||||
|
# Últimas 5 transações
|
||||||
|
recent_transactions = list(transactions_collection.find(
|
||||||
|
{"user_id": ObjectId(user_id)}
|
||||||
|
).sort("date", -1).limit(5))
|
||||||
|
|
||||||
|
for transaction in recent_transactions:
|
||||||
|
transaction['id'] = str(transaction['_id'])
|
||||||
|
transaction['user_id'] = str(transaction['user_id'])
|
||||||
|
del transaction['_id']
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"total_income": round(total_income, 2),
|
||||||
|
"total_expenses": round(total_expenses, 2),
|
||||||
|
"balance": round(balance, 2),
|
||||||
|
"recent_transactions": recent_transactions
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro no dashboard: {e}")
|
||||||
|
return jsonify({
|
||||||
|
"total_income": 0,
|
||||||
|
"total_expenses": 0,
|
||||||
|
"balance": 0,
|
||||||
|
"recent_transactions": []
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@app.route('/api/dashboard/chart')
|
||||||
|
def get_chart():
|
||||||
|
try:
|
||||||
|
user_id = request.args.get('user_id')
|
||||||
|
chart_type = request.args.get('type', 'monthly')
|
||||||
|
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "User ID é obrigatório"}), 400
|
||||||
|
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
|
||||||
|
if chart_type == 'monthly':
|
||||||
|
# Últimos 6 meses
|
||||||
|
six_months_ago = datetime.now() - timedelta(days=180)
|
||||||
|
|
||||||
|
pipeline = [
|
||||||
|
{"$match": {
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"date": {"$gte": six_months_ago.strftime('%Y-%m-%d')}
|
||||||
|
}},
|
||||||
|
{"$group": {
|
||||||
|
"_id": {"$substr": ["$date", 0, 7]}, # Extrair YYYY-MM
|
||||||
|
"income": {"$sum": {"$cond": [{"$eq": ["$type", "income"]}, "$amount", 0]}},
|
||||||
|
"expenses": {"$sum": {"$cond": [{"$eq": ["$type", "expense"]}, "$amount", 0]}}
|
||||||
|
}},
|
||||||
|
{"$sort": {"_id": 1}},
|
||||||
|
{"$limit": 6}
|
||||||
|
]
|
||||||
|
|
||||||
|
result = list(transactions_collection.aggregate(pipeline))
|
||||||
|
|
||||||
|
if result:
|
||||||
|
months = [r['_id'] for r in result]
|
||||||
|
income = [r['income'] for r in result]
|
||||||
|
expenses = [r['expenses'] for r in result]
|
||||||
|
|
||||||
|
x = range(len(months))
|
||||||
|
width = 0.35
|
||||||
|
|
||||||
|
plt.bar([i - width/2 for i in x], income, width, label='Receitas', color='#28a745')
|
||||||
|
plt.bar([i + width/2 for i in x], expenses, width, label='Despesas', color='#dc3545')
|
||||||
|
plt.xlabel('Mês')
|
||||||
|
plt.ylabel('Valor (R$)')
|
||||||
|
plt.title('Receitas vs Despesas - Últimos 6 Meses')
|
||||||
|
plt.xticks(x, months, rotation=45)
|
||||||
|
plt.legend()
|
||||||
|
plt.tight_layout()
|
||||||
|
else:
|
||||||
|
plt.text(0.5, 0.5, 'Sem dados para exibir',
|
||||||
|
ha='center', va='center', transform=plt.gca().transAxes)
|
||||||
|
|
||||||
|
elif chart_type == 'categories':
|
||||||
|
# Gastos por categoria (últimos 30 dias)
|
||||||
|
thirty_days_ago = datetime.now() - timedelta(days=30)
|
||||||
|
|
||||||
|
pipeline = [
|
||||||
|
{"$match": {
|
||||||
|
"user_id": ObjectId(user_id),
|
||||||
|
"type": "expense",
|
||||||
|
"date": {"$gte": thirty_days_ago.strftime('%Y-%m-%d')}
|
||||||
|
}},
|
||||||
|
{"$group": {
|
||||||
|
"_id": "$category",
|
||||||
|
"total": {"$sum": "$amount"}
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
|
||||||
|
result = list(transactions_collection.aggregate(pipeline))
|
||||||
|
|
||||||
|
if result:
|
||||||
|
categories = [r['_id'] for r in result]
|
||||||
|
amounts = [r['total'] for r in result]
|
||||||
|
|
||||||
|
plt.pie(amounts, labels=categories, autopct='%1.1f%%', startangle=90)
|
||||||
|
plt.title('Distribuição de Gastos por Categoria (Últimos 30 dias)')
|
||||||
|
else:
|
||||||
|
plt.text(0.5, 0.5, 'Sem dados de gastos',
|
||||||
|
ha='center', va='center', transform=plt.gca().transAxes)
|
||||||
|
|
||||||
|
# Converter para base64
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
plt.savefig(buffer, format='png', dpi=100, bbox_inches='tight')
|
||||||
|
buffer.seek(0)
|
||||||
|
image_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"chart": f"data:image/png;base64,{image_base64}"
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao gerar gráfico: {e}")
|
||||||
|
return jsonify({"error": "Erro ao gerar gráfico"}), 500
|
||||||
|
|
||||||
|
# ROTAS DE PERFIL
|
||||||
|
@app.route('/api/user/profile', methods=['GET'])
|
||||||
|
def get_profile():
|
||||||
|
try:
|
||||||
|
user_id = request.args.get('user_id')
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "User ID é obrigatório"}), 400
|
||||||
|
|
||||||
|
user = users_collection.find_one({"_id": ObjectId(user_id)})
|
||||||
|
if not user:
|
||||||
|
return jsonify({"error": "Usuário não encontrado"}), 404
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"user": {
|
||||||
|
"id": str(user['_id']),
|
||||||
|
"name": user['name'],
|
||||||
|
"email": user['email'],
|
||||||
|
"profile": user.get('profile', {})
|
||||||
|
}
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar perfil: {e}")
|
||||||
|
return jsonify({"error": "Erro ao buscar perfil"}), 500
|
||||||
|
|
||||||
|
@app.route('/api/user/profile', methods=['PUT'])
|
||||||
|
def update_profile():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
user_id = data.get('user_id')
|
||||||
|
name = data.get('name', '').strip()
|
||||||
|
email = data.get('email', '').strip().lower()
|
||||||
|
profile = data.get('profile', {})
|
||||||
|
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "User ID é obrigatório"}), 400
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
return jsonify({"error": "Nome é obrigatório"}), 400
|
||||||
|
|
||||||
|
# Verificar se email já existe (para outro usuário)
|
||||||
|
existing_user = users_collection.find_one({
|
||||||
|
"email": email,
|
||||||
|
"_id": {"$ne": ObjectId(user_id)}
|
||||||
|
})
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
return jsonify({"error": "Email já está em uso"}), 400
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
"name": name,
|
||||||
|
"email": email,
|
||||||
|
"profile": profile
|
||||||
|
}
|
||||||
|
|
||||||
|
result = users_collection.update_one(
|
||||||
|
{"_id": ObjectId(user_id)},
|
||||||
|
{"$set": update_data}
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.modified_count == 0:
|
||||||
|
return jsonify({"error": "Nenhuma alteração realizada"}), 400
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"message": "Perfil atualizado com sucesso",
|
||||||
|
"user": {
|
||||||
|
"id": user_id,
|
||||||
|
"name": name,
|
||||||
|
"email": email,
|
||||||
|
"profile": profile
|
||||||
|
}
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar perfil: {e}")
|
||||||
|
return jsonify({"error": "Erro ao atualizar perfil"}), 500
|
||||||
|
|
||||||
|
@app.route('/api/user/change-password', methods=['PUT'])
|
||||||
|
def change_password():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
user_id = data.get('user_id')
|
||||||
|
current_password = data.get('current_password')
|
||||||
|
new_password = data.get('new_password')
|
||||||
|
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "User ID é obrigatório"}), 400
|
||||||
|
|
||||||
|
user = users_collection.find_one({"_id": ObjectId(user_id)})
|
||||||
|
if not user:
|
||||||
|
return jsonify({"error": "Usuário não encontrado"}), 404
|
||||||
|
|
||||||
|
if not check_password(current_password, user['password']):
|
||||||
|
return jsonify({"error": "Senha atual incorreta"}), 400
|
||||||
|
|
||||||
|
if len(new_password) < 6:
|
||||||
|
return jsonify({"error": "A nova senha deve ter pelo menos 6 caracteres"}), 400
|
||||||
|
|
||||||
|
users_collection.update_one(
|
||||||
|
{"_id": ObjectId(user_id)},
|
||||||
|
{"$set": {"password": hash_password(new_password)}}
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({"message": "Senha alterada com sucesso"}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao alterar senha: {e}")
|
||||||
|
return jsonify({"error": "Erro ao alterar senha"}), 500
|
||||||
|
|
||||||
|
# Rota health check
|
||||||
|
@app.route('/api/health', methods=['GET'])
|
||||||
|
def health_check():
|
||||||
|
try:
|
||||||
|
# Testar conexão com MongoDB
|
||||||
|
client.admin.command('ismaster')
|
||||||
|
return jsonify({
|
||||||
|
"status": "OK",
|
||||||
|
"message": "Backend e MongoDB funcionando",
|
||||||
|
"timestamp": datetime.now().isoformat()
|
||||||
|
}), 200
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
"status": "ERROR",
|
||||||
|
"message": f"Erro no MongoDB: {e}"
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return jsonify({
|
||||||
|
"message": "CtrlCash API está funcionando!",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"timestamp": datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("🚀 Iniciando CtrlCash API com MongoDB...")
|
||||||
|
print(f"📊 MongoDB: {MONGO_URI}")
|
||||||
|
print(f"💾 Database: {DB_NAME}")
|
||||||
|
app.run(debug=False, host='0.0.0.0', port=5000)
|
||||||
12
app/main.py
12
app/main.py
@@ -1,12 +0,0 @@
|
|||||||
from flask import Flask
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def hello_world():
|
|
||||||
return "<p>Hello, World!</p>"
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run()
|
|
||||||
@@ -1,2 +1,6 @@
|
|||||||
Flask==3.1.2
|
Flask==3.1.2
|
||||||
|
flask-cors==6.0.1
|
||||||
Werkzeug==3.1.3
|
Werkzeug==3.1.3
|
||||||
|
pymongo==4.15.3
|
||||||
|
matplotlib==3.10.7
|
||||||
|
bcrypt==5.0.0
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
web/assets/index-DvM4Z6IE.css
Normal file
1
web/assets/index-DvM4Z6IE.css
Normal file
File diff suppressed because one or more lines are too long
25
web/assets/index-_Uo-jrdC.js
Normal file
25
web/assets/index-_Uo-jrdC.js
Normal file
File diff suppressed because one or more lines are too long
@@ -7,8 +7,8 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||||
<title>CtrlCash</title>
|
<title>CtrlCash</title>
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/index-DnG1l7wa.js"></script>
|
<script type="module" crossorigin src="/assets/index-_Uo-jrdC.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Chqn8Dfe.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DvM4Z6IE.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user