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:
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:
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"
}
Authentication endpoints use HTML form data:
POST /login
Content-Type: application/x-www-form-urlencoded
email = [email protected] & password = securepass123
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
Request completed successfully
Invalid request parameters or missing required fields
Authentication required or session expired
Requested resource does not exist
Server-side error occurred
Common Error Examples
Authentication Error
Validation Error
Not Found
{
"status" : "error" ,
"message" : "Unauthorized"
}
Authentication Decorator
The API uses two decorators for route protection:
HTML Route Protection
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
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
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:
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
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