789 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			789 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from flask import Flask, request, jsonify
 | 
						|
from flask_cors import CORS
 | 
						|
import sqlite3
 | 
						|
import matplotlib
 | 
						|
matplotlib.use('Agg')
 | 
						|
import matplotlib.pyplot as plt
 | 
						|
import base64
 | 
						|
import io
 | 
						|
import bcrypt
 | 
						|
import os
 | 
						|
from datetime import datetime, timedelta
 | 
						|
import json
 | 
						|
 | 
						|
 | 
						|
app = Flask(__name__)
 | 
						|
CORS(app)
 | 
						|
 | 
						|
# Configuração do SQLite
 | 
						|
DB_PATH = 'ctrlcash.db'
 | 
						|
 | 
						|
# Funções do Banco de Dados
 | 
						|
def init_db():
 | 
						|
    """Inicializa o banco de dados e cria as tabelas"""
 | 
						|
    conn = sqlite3.connect(DB_PATH)
 | 
						|
    cursor = conn.cursor()
 | 
						|
    
 | 
						|
    # Tabela de usuários
 | 
						|
    cursor.execute('''
 | 
						|
        CREATE TABLE IF NOT EXISTS users (
 | 
						|
            id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
						|
            email TEXT UNIQUE NOT NULL,
 | 
						|
            password TEXT NOT NULL,
 | 
						|
            name TEXT NOT NULL,
 | 
						|
            monthly_income_goal REAL DEFAULT 5000,
 | 
						|
            monthly_expense_limit REAL DEFAULT 2500,
 | 
						|
            currency TEXT DEFAULT 'BRL',
 | 
						|
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 | 
						|
        )
 | 
						|
    ''')
 | 
						|
    
 | 
						|
    # Tabela de categorias
 | 
						|
    cursor.execute('''
 | 
						|
        CREATE TABLE IF NOT EXISTS categories (
 | 
						|
            id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
						|
            user_id INTEGER NOT NULL,
 | 
						|
            name TEXT NOT NULL,
 | 
						|
            type TEXT NOT NULL,
 | 
						|
            color TEXT NOT NULL,
 | 
						|
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            FOREIGN KEY (user_id) REFERENCES users (id),
 | 
						|
            UNIQUE(user_id, name, type)
 | 
						|
        )
 | 
						|
    ''')
 | 
						|
    
 | 
						|
    # Tabela de transações
 | 
						|
    cursor.execute('''
 | 
						|
        CREATE TABLE IF NOT EXISTS transactions (
 | 
						|
            id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
						|
            user_id INTEGER NOT NULL,
 | 
						|
            amount REAL NOT NULL,
 | 
						|
            description TEXT NOT NULL,
 | 
						|
            category TEXT NOT NULL,
 | 
						|
            type TEXT NOT NULL,
 | 
						|
            date DATE NOT NULL,
 | 
						|
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            FOREIGN KEY (user_id) REFERENCES users (id)
 | 
						|
        )
 | 
						|
    ''')
 | 
						|
    
 | 
						|
    conn.commit()
 | 
						|
    conn.close()
 | 
						|
 | 
						|
def get_db_connection():
 | 
						|
    """Cria uma conexão com o banco de dados"""
 | 
						|
    conn = sqlite3.connect(DB_PATH)
 | 
						|
    conn.row_factory = sqlite3.Row  # Para retornar dicionários
 | 
						|
    return conn
 | 
						|
 | 
						|
# 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"""
 | 
						|
    conn = get_db_connection()
 | 
						|
    cursor = conn.cursor()
 | 
						|
    
 | 
						|
    for cat in default_categories:
 | 
						|
        cursor.execute('''
 | 
						|
            INSERT OR IGNORE INTO categories (user_id, name, type, color)
 | 
						|
            VALUES (?, ?, ?, ?)
 | 
						|
        ''', (user_id, cat['name'], cat['type'], cat['color']))
 | 
						|
    
 | 
						|
    conn.commit()
 | 
						|
    conn.close()
 | 
						|
 | 
						|
 | 
						|
 | 
						|
# 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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        # Verificar se usuário já existe
 | 
						|
        cursor.execute('SELECT id FROM users WHERE email = ?', (email,))
 | 
						|
        if cursor.fetchone():
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "Email já cadastrado"}), 400
 | 
						|
        
 | 
						|
        # Criar usuário
 | 
						|
        cursor.execute('''
 | 
						|
            INSERT INTO users (email, password, name)
 | 
						|
            VALUES (?, ?, ?)
 | 
						|
        ''', (email, hash_password(password), name))
 | 
						|
        
 | 
						|
        user_id = cursor.lastrowid
 | 
						|
        
 | 
						|
        conn.commit()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        # Inicializar categorias e dados mock
 | 
						|
        init_default_categories(user_id)
 | 
						|
        
 | 
						|
        return jsonify({
 | 
						|
            "message": "Usuário criado com sucesso",
 | 
						|
            "user": {
 | 
						|
                "id": user_id,
 | 
						|
                "email": email,
 | 
						|
                "name": name,
 | 
						|
                "profile": {
 | 
						|
                    "monthly_income_goal": 5000,
 | 
						|
                    "monthly_expense_limit": 2500,
 | 
						|
                    "currency": "BRL"
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }), 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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        cursor.execute('''
 | 
						|
            SELECT id, email, password, name, monthly_income_goal, monthly_expense_limit, currency 
 | 
						|
            FROM users WHERE email = ?
 | 
						|
        ''', (email,))
 | 
						|
        
 | 
						|
        user = cursor.fetchone()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        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": user['id'],
 | 
						|
                "email": user['email'],
 | 
						|
                "name": user['name'],
 | 
						|
                "profile": {
 | 
						|
                    "monthly_income_goal": user['monthly_income_goal'],
 | 
						|
                    "monthly_expense_limit": user['monthly_expense_limit'],
 | 
						|
                    "currency": user['currency']
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }), 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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        cursor.execute('''
 | 
						|
            SELECT id, user_id, amount, description, category, type, date
 | 
						|
            FROM transactions 
 | 
						|
            WHERE user_id = ?
 | 
						|
            ORDER BY date DESC
 | 
						|
        ''', (user_id,))
 | 
						|
        
 | 
						|
        transactions = []
 | 
						|
        for row in cursor.fetchall():
 | 
						|
            transactions.append({
 | 
						|
                "id": row['id'],
 | 
						|
                "user_id": row['user_id'],
 | 
						|
                "amount": row['amount'],
 | 
						|
                "description": row['description'],
 | 
						|
                "category": row['category'],
 | 
						|
                "type": row['type'],
 | 
						|
                "date": row['date']
 | 
						|
            })
 | 
						|
        
 | 
						|
        conn.close()
 | 
						|
        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
 | 
						|
        
 | 
						|
        # ✅ CORREÇÃO: Usar a data EXATA que veio do frontend
 | 
						|
        # Não converter para datetime, usar como string mesmo
 | 
						|
        if not date_str:
 | 
						|
            date_str = datetime.now().strftime('%Y-%m-%d')
 | 
						|
        
 | 
						|
        # Validar formato da data (YYYY-MM-DD)
 | 
						|
        try:
 | 
						|
            datetime.strptime(date_str, '%Y-%m-%d')
 | 
						|
        except ValueError:
 | 
						|
            return jsonify({"error": "Formato de data inválido. Use YYYY-MM-DD"}), 400
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        # ✅ CORREÇÃO: Inserir a data EXATA como string
 | 
						|
        cursor.execute('''
 | 
						|
            INSERT INTO transactions (user_id, amount, description, category, type, date)
 | 
						|
            VALUES (?, ?, ?, ?, ?, ?)
 | 
						|
        ''', (user_id, amount, description, category, transaction_type, date_str))
 | 
						|
        
 | 
						|
        transaction_id = cursor.lastrowid
 | 
						|
        
 | 
						|
        # Buscar transação criada
 | 
						|
        cursor.execute('SELECT * FROM transactions WHERE id = ?', (transaction_id,))
 | 
						|
        new_transaction = cursor.fetchone()
 | 
						|
        
 | 
						|
        conn.commit()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        return jsonify({
 | 
						|
            "message": "Transação adicionada com sucesso",
 | 
						|
            "transaction": {
 | 
						|
                "id": new_transaction['id'],
 | 
						|
                "user_id": new_transaction['user_id'],
 | 
						|
                "amount": new_transaction['amount'],
 | 
						|
                "description": new_transaction['description'],
 | 
						|
                "category": new_transaction['category'],
 | 
						|
                "type": new_transaction['type'],
 | 
						|
                "date": new_transaction['date']  # ✅ Já vem correto do banco
 | 
						|
            }
 | 
						|
        }), 201
 | 
						|
        
 | 
						|
    except Exception as e:
 | 
						|
        print(f"Erro ao adicionar transação: {e}")
 | 
						|
        return jsonify({"error": "Erro ao adicionar transação"}), 500
 | 
						|
        
 | 
						|
    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/<int: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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        cursor.execute('''
 | 
						|
            DELETE FROM transactions 
 | 
						|
            WHERE id = ? AND user_id = ?
 | 
						|
        ''', (transaction_id, user_id))
 | 
						|
        
 | 
						|
        if cursor.rowcount == 0:
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "Transação não encontrada"}), 404
 | 
						|
        
 | 
						|
        conn.commit()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        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
 | 
						|
            conn = get_db_connection()
 | 
						|
            cursor = conn.cursor()
 | 
						|
            
 | 
						|
            cursor.execute('''
 | 
						|
                SELECT id, user_id, name, type, color
 | 
						|
                FROM categories 
 | 
						|
                WHERE user_id = ?
 | 
						|
            ''', (user_id,))
 | 
						|
            
 | 
						|
            categories = []
 | 
						|
            for row in cursor.fetchall():
 | 
						|
                categories.append({
 | 
						|
                    "id": row['id'],
 | 
						|
                    "user_id": row['user_id'],
 | 
						|
                    "name": row['name'],
 | 
						|
                    "type": row['type'],
 | 
						|
                    "color": row['color']
 | 
						|
                })
 | 
						|
            
 | 
						|
            conn.close()
 | 
						|
        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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        # Verificar se categoria já existe
 | 
						|
        cursor.execute('''
 | 
						|
            SELECT id FROM categories 
 | 
						|
            WHERE user_id = ? AND name = ? AND type = ?
 | 
						|
        ''', (user_id, name, category_type))
 | 
						|
        
 | 
						|
        if cursor.fetchone():
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "Categoria já existe"}), 400
 | 
						|
        
 | 
						|
        # Inserir nova categoria
 | 
						|
        cursor.execute('''
 | 
						|
            INSERT INTO categories (user_id, name, type, color)
 | 
						|
            VALUES (?, ?, ?, ?)
 | 
						|
        ''', (user_id, name, category_type, color))
 | 
						|
        
 | 
						|
        category_id = cursor.lastrowid
 | 
						|
        
 | 
						|
        conn.commit()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        return jsonify({
 | 
						|
            "message": "Categoria adicionada com sucesso",
 | 
						|
            "category": {
 | 
						|
                "id": category_id,
 | 
						|
                "user_id": user_id,
 | 
						|
                "name": name,
 | 
						|
                "type": category_type,
 | 
						|
                "color": color
 | 
						|
            }
 | 
						|
        }), 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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        # Calcular totais do mês atual
 | 
						|
        first_day_of_month = datetime.now().replace(day=1).strftime('%Y-%m-%d')
 | 
						|
        
 | 
						|
        # Receitas do mês
 | 
						|
        cursor.execute('''
 | 
						|
            SELECT COALESCE(SUM(amount), 0) as total
 | 
						|
            FROM transactions 
 | 
						|
            WHERE user_id = ? AND type = 'income' AND date >= ?
 | 
						|
        ''', (user_id, first_day_of_month))
 | 
						|
        
 | 
						|
        total_income = cursor.fetchone()['total'] or 0
 | 
						|
        
 | 
						|
        # Despesas do mês
 | 
						|
        cursor.execute('''
 | 
						|
            SELECT COALESCE(SUM(amount), 0) as total
 | 
						|
            FROM transactions 
 | 
						|
            WHERE user_id = ? AND type = 'expense' AND date >= ?
 | 
						|
        ''', (user_id, first_day_of_month))
 | 
						|
        
 | 
						|
        total_expenses = cursor.fetchone()['total'] or 0
 | 
						|
        balance = total_income - total_expenses
 | 
						|
        
 | 
						|
        # Últimas 5 transações
 | 
						|
        cursor.execute('''
 | 
						|
            SELECT id, user_id, amount, description, category, type, date
 | 
						|
            FROM transactions 
 | 
						|
            WHERE user_id = ?
 | 
						|
            ORDER BY date DESC
 | 
						|
            LIMIT 5
 | 
						|
        ''', (user_id,))
 | 
						|
        
 | 
						|
        recent_transactions = []
 | 
						|
        for row in cursor.fetchall():
 | 
						|
            recent_transactions.append({
 | 
						|
                "id": row['id'],
 | 
						|
                "user_id": row['user_id'],
 | 
						|
                "amount": row['amount'],
 | 
						|
                "description": row['description'],
 | 
						|
                "category": row['category'],
 | 
						|
                "type": row['type'],
 | 
						|
                "date": row['date']
 | 
						|
            })
 | 
						|
        
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        plt.figure(figsize=(10, 6))
 | 
						|
        
 | 
						|
        if chart_type == 'monthly':
 | 
						|
            # Últimos 6 meses
 | 
						|
            six_months_ago = (datetime.now() - timedelta(days=180)).strftime('%Y-%m-%d')
 | 
						|
            
 | 
						|
            cursor.execute('''
 | 
						|
                SELECT 
 | 
						|
                    strftime('%Y-%m', date) as month,
 | 
						|
                    SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END) as income,
 | 
						|
                    SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END) as expenses
 | 
						|
                FROM transactions 
 | 
						|
                WHERE user_id = ? AND date >= ?
 | 
						|
                GROUP BY strftime('%Y-%m', date)
 | 
						|
                ORDER BY month DESC
 | 
						|
                LIMIT 6
 | 
						|
            ''', (user_id, six_months_ago))
 | 
						|
            
 | 
						|
            result = cursor.fetchall()
 | 
						|
            
 | 
						|
            if result:
 | 
						|
                months = [row['month'] for row in result]
 | 
						|
                income = [row['income'] for row in result]
 | 
						|
                expenses = [row['expenses'] for row in result]
 | 
						|
                
 | 
						|
                # Reverter para ordem cronológica
 | 
						|
                months.reverse()
 | 
						|
                income.reverse()
 | 
						|
                expenses.reverse()
 | 
						|
                
 | 
						|
                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)).strftime('%Y-%m-%d')
 | 
						|
            
 | 
						|
            cursor.execute('''
 | 
						|
                SELECT category, SUM(amount) as total
 | 
						|
                FROM transactions 
 | 
						|
                WHERE user_id = ? AND type = 'expense' AND date >= ?
 | 
						|
                GROUP BY category
 | 
						|
            ''', (user_id, thirty_days_ago))
 | 
						|
            
 | 
						|
            result = cursor.fetchall()
 | 
						|
            
 | 
						|
            if result:
 | 
						|
                categories = [row['category'] for row in result]
 | 
						|
                amounts = [row['total'] for row 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)
 | 
						|
        
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        # 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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        cursor.execute('''
 | 
						|
            SELECT id, email, name, monthly_income_goal, monthly_expense_limit, currency
 | 
						|
            FROM users WHERE id = ?
 | 
						|
        ''', (user_id,))
 | 
						|
        
 | 
						|
        user = cursor.fetchone()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        if not user:
 | 
						|
            return jsonify({"error": "Usuário não encontrado"}), 404
 | 
						|
        
 | 
						|
        return jsonify({
 | 
						|
            "user": {
 | 
						|
                "id": user['id'],
 | 
						|
                "name": user['name'],
 | 
						|
                "email": user['email'],
 | 
						|
                "profile": {
 | 
						|
                    "monthly_income_goal": user['monthly_income_goal'],
 | 
						|
                    "monthly_expense_limit": user['monthly_expense_limit'],
 | 
						|
                    "currency": user['currency']
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }), 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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        # Verificar se email já existe (para outro usuário)
 | 
						|
        cursor.execute('SELECT id FROM users WHERE email = ? AND id != ?', (email, user_id))
 | 
						|
        if cursor.fetchone():
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "Email já está em uso"}), 400
 | 
						|
        
 | 
						|
        # Atualizar perfil
 | 
						|
        cursor.execute('''
 | 
						|
            UPDATE users 
 | 
						|
            SET name = ?, email = ?, monthly_income_goal = ?, monthly_expense_limit = ?
 | 
						|
            WHERE id = ?
 | 
						|
        ''', (name, email, profile.get('monthly_income_goal', 5000), profile.get('monthly_expense_limit', 2500), user_id))
 | 
						|
        
 | 
						|
        if cursor.rowcount == 0:
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "Nenhuma alteração realizada"}), 400
 | 
						|
        
 | 
						|
        conn.commit()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        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
 | 
						|
        
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        
 | 
						|
        cursor.execute('SELECT password FROM users WHERE id = ?', (user_id,))
 | 
						|
        user = cursor.fetchone()
 | 
						|
        
 | 
						|
        if not user:
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "Usuário não encontrado"}), 404
 | 
						|
        
 | 
						|
        if not check_password(current_password, user['password']):
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "Senha atual incorreta"}), 400
 | 
						|
        
 | 
						|
        if len(new_password) < 6:
 | 
						|
            conn.close()
 | 
						|
            return jsonify({"error": "A nova senha deve ter pelo menos 6 caracteres"}), 400
 | 
						|
        
 | 
						|
        cursor.execute('UPDATE users SET password = ? WHERE id = ?', (hash_password(new_password), user_id))
 | 
						|
        conn.commit()
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        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 SQLite
 | 
						|
        conn = get_db_connection()
 | 
						|
        cursor = conn.cursor()
 | 
						|
        cursor.execute('SELECT 1')
 | 
						|
        conn.close()
 | 
						|
        
 | 
						|
        return jsonify({
 | 
						|
            "status": "OK", 
 | 
						|
            "message": "Backend e SQLite funcionando",
 | 
						|
            "timestamp": datetime.now().isoformat()
 | 
						|
        }), 200
 | 
						|
    except Exception as e:
 | 
						|
        return jsonify({
 | 
						|
            "status": "ERROR",
 | 
						|
            "message": f"Erro no SQLite: {e}"
 | 
						|
        }), 500
 | 
						|
 | 
						|
@app.route('/')
 | 
						|
def index():
 | 
						|
    return jsonify({
 | 
						|
        "message": "CtrlCash API está funcionando!",
 | 
						|
        "version": "1.0.0",
 | 
						|
        "timestamp": datetime.now().isoformat()
 | 
						|
    })
 | 
						|
 | 
						|
# Inicializar o banco de dados quando o app iniciar
 | 
						|
init_db()
 | 
						|
print("✅ Banco de dados SQLite inicializado!")
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    print("🚀 Iniciando CtrlCash API com SQLite...")
 | 
						|
    print(f"💾 Database: {DB_PATH}")
 | 
						|
    app.run(debug=False, host='0.0.0.0', port=5000)
 | 
						|
 |