Skip to main content

Architecture

FinAI is built on Flask with a modular Blueprint architecture, separating concerns across functional domains. The application uses SQLAlchemy ORM for database operations with SQLite as the default database.

Blueprint Structure

The API is organized into the following blueprints:

Authentication

User registration, login, logout, and password reset

Transactions

Income, expense, and transfer management with wallet integration

Reports

Financial analytics and spending reports

AI Integration

Category predictions and financial insights

Budget Management

Budget creation, tracking, and alerts

Admin

User management and system administration

Application Initialization

The Flask application is initialized in app/__init__.py with database and mail configuration:
app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
from config import Config

app = Flask(__name__)
app.config.from_object(Config)

db = SQLAlchemy(app)
mail = Mail(app)

# Create database tables
with app.app_context(): 
    db.create_all()

# Register blueprints
from app.routes.auth import auth_bp
from app.routes.transaction import transaction_bp
from app.routes.report import report_bp
from app.routes.ai import ai_bp
from app.routes.budget import budget_bp
from app.routes.admin import admin_bp

app.register_blueprint(auth_bp)
app.register_blueprint(transaction_bp)
app.register_blueprint(report_bp)
app.register_blueprint(ai_bp)
app.register_blueprint(budget_bp)
app.register_blueprint(admin_bp)

Base URL

All API endpoints are relative to your deployment URL:
http://localhost:5000

Request Format

JSON API Endpoints

API endpoints prefixed with /api/ expect and return JSON:
POST /api/transactions
Content-Type: application/json

{
  "type": "expense",
  "amount": 50000,
  "description": "Lunch at restaurant",
  "category_id": "cat12345",
  "source_wallet_id": "wal67890",
  "date": "2026-03-07"
}

Form-Based Endpoints

Authentication endpoints use HTML form data:
POST /login
Content-Type: application/x-www-form-urlencoded

email=[email protected]&password=securepass123

Response Format

Success Response

API endpoints return JSON with a consistent structure:
{
  "status": "success",
  "message": "Transaction created successfully",
  "data": {
    "id": "trans123",
    "amount": 50000,
    "type": "chi"
  }
}

Collection Response

List endpoints return arrays of objects:
[
  {
    "id": "trans123",
    "type": "chi",
    "amount": 50000,
    "description": "Lunch at restaurant",
    "date": "2026-03-07",
    "category_id": "cat12345",
    "category_name": "Food & Dining",
    "wallet_id": "wal67890",
    "wallet_name": "Cash Wallet"
  },
  {
    "id": "trans124",
    "type": "thu",
    "amount": 1000000,
    "description": "Monthly salary",
    "date": "2026-03-01",
    "category_id": "cat54321",
    "category_name": "Salary",
    "wallet_id": "wal67890",
    "wallet_name": "Cash Wallet"
  }
]
Transaction types use Vietnamese terminology: chi (expense), thu (income), chuyen (transfer)

Error Handling

Error Response Structure

All errors follow this format:
{
  "status": "error",
  "message": "Detailed error description"
}

HTTP Status Codes

200
Success
Request completed successfully
400
Bad Request
Invalid request parameters or missing required fields
401
Unauthorized
Authentication required or session expired
404
Not Found
Requested resource does not exist
500
Internal Server Error
Server-side error occurred

Common Error Examples

{
  "status": "error",
  "message": "Unauthorized"
}

Authentication Decorator

The API uses two decorators for route protection:

HTML Route Protection

app/utils.py
from functools import wraps
from flask import session, redirect, url_for

def login_required(f):
    """Require login for HTML routes"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return redirect(url_for('auth.login'))
        return f(*args, **kwargs)
    return decorated_function

API Route Protection

app/utils.py
def api_login_required(f):
    """Require login for API routes (returns JSON error)"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return jsonify({'status': 'error', 'message': 'Unauthorized'}), 401
        return f(*args, **kwargs)
    return decorated_function

Usage Example

app/routes/transaction.py
from app.utils import api_login_required

@transaction_bp.route('/api/transactions', methods=['GET'])
@api_login_required
def get_transactions():
    user_id = session['user_id']
    transactions = Transaction.query.filter_by(user_id=user_id).all()
    return jsonify([...])  
All protected endpoints automatically check for user_id in the Flask session. API endpoints return 401 JSON errors, while HTML routes redirect to the login page.

Database Models

Core Entities

The application uses SQLAlchemy ORM with these primary models:
  • User - User accounts with password hashing
  • UserSetting - User preferences (currency, language, theme)
  • Wallet - Financial accounts (cash, bank, etc.)
  • Category - Hierarchical transaction categories
  • Transaction - Income, expense, and transfer records
  • Budget - Budget tracking with category associations
  • AILog - AI prediction history and feedback
  • PasswordResetToken - Temporary tokens for password recovery

User Model Example

app/models.py
from werkzeug.security import generate_password_hash, check_password_hash

class User(db.Model):
    __tablename__ = 'nguoidung'

    id = db.Column('MaNguoiDung', db.String(8), primary_key=True)
    name = db.Column('HoTen', db.String(100))
    email = db.Column('Email', db.String(100), unique=True, nullable=False)
    password_hash = db.Column('MatKhau', db.String(200), nullable=False)
    role = db.Column('VaiTro', db.String(20), default='user')
    status = db.Column('TrangThai', db.Integer, default=1)
    created_at = db.Column('NgayTao', db.DateTime, default=datetime.now)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

Configuration

The application configuration is managed through environment variables:
config.py
import os
from dotenv import load_dotenv

BASE_DIR = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(BASE_DIR, '.env'))

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key-not-secure'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'instance', 'quanlychitieu.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    
    # Email configuration
    MAIL_SERVER = 'smtp.gmail.com'
    MAIL_PORT = 587
    MAIL_USE_TLS = True
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    MAIL_DEFAULT_SENDER = os.environ.get('MAIL_USERNAME')
Always set SECRET_KEY in production via environment variables. Never commit secrets to version control.

Rate Limiting

The API currently does not implement rate limiting. For production deployments, consider implementing:
  • Request throttling per user session
  • IP-based rate limits for authentication endpoints
  • Circuit breakers for AI prediction endpoints

Pagination

List endpoints currently return all results. For large datasets, consider implementing:
@transaction_bp.route('/api/transactions', methods=['GET'])
@api_login_required
def get_transactions():
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 50, type=int)
    
    pagination = Transaction.query.filter_by(user_id=session['user_id'])\
        .order_by(Transaction.date.desc())\
        .paginate(page=page, per_page=per_page)
    
    return jsonify({
        'items': [...],
        'page': page,
        'total_pages': pagination.pages,
        'total_items': pagination.total
    })

Next Steps

Authentication

Learn about session-based auth and security

Transactions API

Explore transaction management endpoints

Data Models

Deep dive into database schema

AI Integration

Implement AI-powered categorization

Build docs developers (and LLMs) love