Initial commit

This commit is contained in:
iulian 2025-12-26 00:52:56 +00:00
commit 983cee0320
322 changed files with 57174 additions and 0 deletions

View file

@ -0,0 +1,31 @@
#!/bin/bash
echo "🧹 Cleaning personal information..."
# Remove personal data and sensitive files
rm -rf instance/
rm -rf backups/
rm -rf __pycache__/
rm -rf app/__pycache__/
rm -rf app/*/__pycache__/
rm -rf .pytest_cache/
rm -rf venv/
rm -rf env/
rm -f *.db
rm -f *.sqlite
rm -f *.sqlite3
rm -f .env
rm -f *.log
rm -f *.tar
rm -f *.tar.gz
rm -f make_admin.py
rm -f test_qr.py
rm -f migrate_db.py
rm -f backup*.sh
rm -f create_deployment_package.sh
# Remove uploaded files
rm -rf app/static/uploads/*
touch app/static/uploads/.gitkeep
echo "✅ Cleanup complete!"

View file

@ -0,0 +1,86 @@
#!/bin/bash
echo "Creating complete Flask app structure..."
# Ensure directories exist
mkdir -p app/models app/routes app/static/{css,js,uploads} app/templates
# Create app/__init__.py with create_app function
cat > app/__init__.py << 'EOF'
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
import os
from dotenv import load_dotenv
import redis
load_dotenv()
db = SQLAlchemy()
login_manager = LoginManager()
csrf = CSRFProtect()
redis_client = None
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', os.urandom(32))
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///finance.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
app.config['UPLOAD_FOLDER'] = 'app/static/uploads'
app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'pdf', 'gif'}
@app.after_request
def set_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:;"
return response
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in to access this page.'
global redis_client
redis_url = os.getenv('REDIS_URL', 'redis://localhost:6369/0')
redis_client = redis.from_url(redis_url, decode_responses=True)
from app.routes import auth, main
app.register_blueprint(auth.bp)
app.register_blueprint(main.bp)
with app.app_context():
db.create_all()
return app
EOF
# Create models/__init__.py
cat > app/models/__init__.py << 'EOF'
from app.models.user import User
from app.models.category import Category, Expense
__all__ = ['User', 'Category', 'Expense']
EOF
# Create routes/__init__.py
cat > app/routes/__init__.py << 'EOF'
# Routes package
EOF
echo "✓ Core files created!"
echo ""
echo "Files created:"
find app -name "*.py" -type f
echo ""
echo "Now you need to create:"
echo " - app/models/user.py"
echo " - app/models/category.py"
echo " - app/routes/auth.py"
echo " - app/routes/main.py"
echo " - All template files"
echo " - CSS and JS files"

View file

@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""
Database migration for custom recurring expenses feature
Adds new columns to subscriptions table for advanced scheduling
"""
import sqlite3
import sys
def migrate_database(db_path='instance/finance.db'):
"""Add new columns to subscriptions table"""
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
print("🔄 Adding custom recurring expense fields...")
# Check which columns already exist
cursor.execute("PRAGMA table_info(subscriptions)")
existing_columns = {row[1] for row in cursor.fetchall()}
# Define new columns with their SQL types
new_columns = [
('custom_interval_days', 'INTEGER'),
('start_date', 'DATE'),
('end_date', 'DATE'),
('total_occurrences', 'INTEGER'),
('occurrences_count', 'INTEGER DEFAULT 0'),
('auto_create_expense', 'BOOLEAN DEFAULT 0'),
('last_auto_created', 'DATE'),
]
# Add each column if it doesn't exist
for column_name, column_type in new_columns:
if column_name not in existing_columns:
try:
cursor.execute(f'ALTER TABLE subscriptions ADD COLUMN {column_name} {column_type}')
print(f" ✅ Added column: {column_name}")
except sqlite3.OperationalError as e:
print(f" ⚠️ Column {column_name} may already exist: {e}")
else:
print(f" Column {column_name} already exists")
# Update existing subscriptions with start dates
cursor.execute("""
UPDATE subscriptions
SET start_date = next_due_date
WHERE start_date IS NULL AND next_due_date IS NOT NULL
""")
conn.commit()
print("\n✅ Migration completed successfully!")
print("\nNew features:")
print(" • Custom frequency intervals (any number of days)")
print(" • Start and end dates for subscriptions")
print(" • Limit total number of payments")
print(" • Auto-create expenses on due date")
print(" • Track occurrence count")
return True
except sqlite3.Error as e:
print(f"\n❌ Database error: {e}")
return False
finally:
if conn:
conn.close()
if __name__ == '__main__':
db_path = sys.argv[1] if len(sys.argv) > 1 else 'instance/finance.db'
success = migrate_database(db_path)
sys.exit(0 if success else 1)

View file

@ -0,0 +1,36 @@
#!/bin/bash
# Database Migration Script for Smart Features
# This script adds the necessary tables for subscription tracking
echo "🔄 Migrating database for Smart Features..."
# Backup existing database
echo "📦 Creating backup..."
docker run --rm -v fina-db:/data -v $(pwd):/backup alpine cp /data/finance.db /backup/finance_backup_$(date +%Y%m%d_%H%M%S).db
echo "✅ Backup created"
# Stop containers
echo "🛑 Stopping containers..."
docker compose down
# Rebuild with new dependencies
echo "🏗️ Rebuilding containers..."
docker compose build
# Start containers (migrations will run automatically)
echo "🚀 Starting containers..."
docker compose up -d
echo ""
echo "✅ Migration complete!"
echo ""
echo "New features available:"
echo " • Smart recurring expense detection"
echo " • Subscription management"
echo " • Multi-language support (EN, RO, ES)"
echo " • PWA support"
echo ""
echo "Access your app at: http://localhost:5001"
echo "Navigate to Subscriptions to start tracking!"

View file

@ -0,0 +1,6 @@
Date,Description,Amount
2024-12-15,Coffee Shop,-4.50
2024-12-14,Grocery Store,-45.30
2024-12-13,Restaurant,-28.75
2024-12-12,Gas Station,-60.00
2024-12-11,Online Shopping,-89.99
1 Date Description Amount
2 2024-12-15 Coffee Shop -4.50
3 2024-12-14 Grocery Store -45.30
4 2024-12-13 Restaurant -28.75
5 2024-12-12 Gas Station -60.00
6 2024-12-11 Online Shopping -89.99

View file

@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
Test script for predictions feature
Run inside the container or with the same Python environment
"""
import sys
import os
# Add app to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def test_predictions_import():
"""Test that predictions module can be imported"""
try:
from app.predictions import (
get_spending_predictions,
predict_category_spending,
generate_insights,
get_category_forecast,
get_seasonal_factor,
compare_with_predictions
)
print("✅ All prediction functions imported successfully")
return True
except Exception as e:
print(f"❌ Import failed: {e}")
return False
def test_seasonal_factor():
"""Test seasonal factor calculations"""
from app.predictions import get_seasonal_factor
from datetime import datetime
tests = [
(datetime(2024, 12, 15), 1.15, "December (holidays)"),
(datetime(2024, 1, 15), 0.90, "January (post-holiday)"),
(datetime(2024, 7, 15), 1.05, "July (summer)"),
(datetime(2024, 3, 15), 1.0, "March (normal)"),
]
all_passed = True
for date, expected, description in tests:
result = get_seasonal_factor(date)
if result == expected:
print(f"{description}: {result}")
else:
print(f"{description}: Expected {expected}, got {result}")
all_passed = False
return all_passed
def test_translation_keys():
"""Test that all prediction translation keys exist"""
from app.translations import translations
required_keys = [
'predictions.title',
'predictions.subtitle',
'predictions.confidence_high',
'predictions.confidence_medium',
'predictions.confidence_low',
'predictions.trend_increasing',
'predictions.trend_decreasing',
'predictions.trend_stable',
'predictions.no_data',
'predictions.insights',
]
all_passed = True
for lang in ['en', 'ro', 'es']:
missing = []
for key in required_keys:
if key not in translations.get(lang, {}):
missing.append(key)
if missing:
print(f"{lang.upper()}: Missing keys: {', '.join(missing)}")
all_passed = False
else:
print(f"{lang.upper()}: All translation keys present")
return all_passed
def test_routes_exist():
"""Test that prediction routes are registered"""
try:
from app import create_app
app = create_app()
routes = [rule.rule for rule in app.url_map.iter_rules()]
required_routes = [
'/predictions',
'/api/predictions',
'/api/predictions/category/<int:category_id>'
]
all_passed = True
for route in required_routes:
# Check if route pattern exists (exact match or with converter)
found = any(route.replace('<int:category_id>', '<category_id>') in r or route in r
for r in routes)
if found:
print(f"✅ Route registered: {route}")
else:
print(f"❌ Route missing: {route}")
all_passed = False
return all_passed
except Exception as e:
print(f"❌ Route check failed: {e}")
return False
def test_template_exists():
"""Test that predictions template exists"""
template_path = os.path.join(
os.path.dirname(__file__),
'app', 'templates', 'predictions.html'
)
if os.path.exists(template_path):
print(f"✅ Template exists: {template_path}")
# Check for key elements
with open(template_path, 'r') as f:
content = f.read()
checks = [
('predictions.title', 'Title translation'),
('predictionsChart', 'Chart element'),
('showCategoryForecast', 'Forecast function'),
('confidence', 'Confidence badges'),
]
all_passed = True
for check, description in checks:
if check in content:
print(f"{description}")
else:
print(f"{description} missing")
all_passed = False
return all_passed
else:
print(f"❌ Template not found: {template_path}")
return False
def main():
"""Run all tests"""
print("\n" + "="*60)
print("PREDICTIONS FEATURE TEST SUITE")
print("="*60 + "\n")
tests = [
("Module Import", test_predictions_import),
("Seasonal Factors", test_seasonal_factor),
("Translation Keys", test_translation_keys),
("Route Registration", test_routes_exist),
("Template Existence", test_template_exists),
]
results = []
for name, test_func in tests:
print(f"\n--- {name} ---")
try:
passed = test_func()
results.append((name, passed))
except Exception as e:
print(f"❌ Test crashed: {e}")
results.append((name, False))
print("\n" + "="*60)
print("SUMMARY")
print("="*60)
total = len(results)
passed = sum(1 for _, p in results if p)
for name, passed_flag in results:
status = "✅ PASS" if passed_flag else "❌ FAIL"
print(f"{status}: {name}")
print(f"\nTotal: {passed}/{total} tests passed")
if passed == total:
print("\n🎉 All tests passed! Feature is ready for manual testing.")
return 0
else:
print(f"\n⚠️ {total - passed} test(s) failed. Please review.")
return 1
if __name__ == '__main__':
sys.exit(main())