Initial commit
This commit is contained in:
commit
983cee0320
322 changed files with 57174 additions and 0 deletions
31
backup/first -fina app/scripts/cleanup_for_github.sh
Executable file
31
backup/first -fina app/scripts/cleanup_for_github.sh
Executable 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!"
|
||||
86
backup/first -fina app/scripts/create_all_files.sh
Executable file
86
backup/first -fina app/scripts/create_all_files.sh
Executable 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"
|
||||
74
backup/first -fina app/scripts/migrate_custom_recurring.py
Executable file
74
backup/first -fina app/scripts/migrate_custom_recurring.py
Executable 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)
|
||||
36
backup/first -fina app/scripts/migrate_smart_features.sh
Executable file
36
backup/first -fina app/scripts/migrate_smart_features.sh
Executable 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!"
|
||||
6
backup/first -fina app/scripts/test_bank_statement.csv
Normal file
6
backup/first -fina app/scripts/test_bank_statement.csv
Normal 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
|
||||
|
193
backup/first -fina app/scripts/test_predictions.py
Executable file
193
backup/first -fina app/scripts/test_predictions.py
Executable 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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue