340 lines
10 KiB
Python
340 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Direct inline execution to generate all source code files"""
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Add the project directory to sys.path so we can import the script
|
|
project_dir = r'C:\Users\Nabeel\Nextcloud\Projects\firefly_reports'
|
|
sys.path.insert(0, project_dir)
|
|
os.chdir(project_dir)
|
|
|
|
def create_dir_and_file(filepath, content):
|
|
"""Create directory and file"""
|
|
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
print(f"✓ Created: {filepath}")
|
|
|
|
BASE = project_dir
|
|
|
|
# Backend files - simpler version with key files only
|
|
backend_files = {
|
|
f"{BASE}\\backend\\__init__.py": "",
|
|
f"{BASE}\\backend\\app\\__init__.py": "# Firefly Analytics Backend",
|
|
f"{BASE}\\backend\\app\\config.py": """from pydantic_settings import BaseSettings
|
|
|
|
class Settings(BaseSettings):
|
|
firefly_url: str = "https://firefly.scsimedia.duckdns.org"
|
|
firefly_api_token: str = ""
|
|
database_url: str = "sqlite:///./data/app.db"
|
|
sync_interval_minutes: int = 30
|
|
|
|
class Config:
|
|
env_file = ".env"
|
|
|
|
settings = Settings()
|
|
""",
|
|
f"{BASE}\\backend\\app\\models.py": """from sqlalchemy import Column, Integer, String, Float, DateTime
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from datetime import datetime
|
|
|
|
Base = declarative_base()
|
|
|
|
class Transaction(Base):
|
|
__tablename__ = "transactions"
|
|
id = Column(Integer, primary_key=True)
|
|
firefly_id = Column(Integer, unique=True, index=True)
|
|
date = Column(DateTime, index=True)
|
|
amount = Column(Float)
|
|
description = Column(String)
|
|
type = Column(String)
|
|
category_id = Column(Integer, nullable=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
""",
|
|
f"{BASE}\\backend\\app\\database.py": """from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker, Session
|
|
from .config import settings
|
|
from .models import Base
|
|
|
|
engine = create_engine(settings.database_url)
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
|
|
def get_db():
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
""",
|
|
f"{BASE}\\backend\\app\\main.py": """from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from .database import Base, engine
|
|
from .models import *
|
|
|
|
app = FastAPI(title="Firefly III Analytics")
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {"status": "healthy"}
|
|
""",
|
|
}
|
|
|
|
# Frontend files
|
|
frontend_files = {
|
|
f"{BASE}\\frontend\\__init__.py": "",
|
|
f"{BASE}\\frontend\\src\\types.ts": """export interface Transaction {
|
|
id: string;
|
|
date: string;
|
|
amount: number;
|
|
description: string;
|
|
type: 'withdrawal' | 'deposit' | 'transfer';
|
|
category_id: string;
|
|
}
|
|
|
|
export interface Category {
|
|
id: string;
|
|
name: string;
|
|
type: 'expense' | 'revenue';
|
|
}
|
|
""",
|
|
f"{BASE}\\frontend\\src\\services\\api.ts": """import axios from 'axios';
|
|
|
|
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000/api';
|
|
export const api = axios.create({ baseURL: API_BASE_URL });
|
|
""",
|
|
f"{BASE}\\frontend\\src\\components\\DateRangePicker.tsx": """import React, { useState } from 'react';
|
|
|
|
export const DateRangePicker = ({ onDateRangeChange }) => {
|
|
const [rangeType, setRangeType] = useState('30days');
|
|
|
|
return (
|
|
<div style={{ padding: '1rem', border: '1px solid #ccc' }}>
|
|
<h3>Reporting Period</h3>
|
|
<button onClick={() => setRangeType('30days')}>Last 30 Days</button>
|
|
</div>
|
|
);
|
|
};
|
|
""",
|
|
f"{BASE}\\frontend\\src\\components\\SpendingChart.tsx": """import React from 'react';
|
|
import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts';
|
|
|
|
export const SpendingChart = ({ data, title = 'Spending by Category' }) => {
|
|
return (
|
|
<div style={{ padding: '1rem' }}>
|
|
<h3>{title}</h3>
|
|
{data && data.length > 0 && (
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<PieChart data={data}>
|
|
<Pie dataKey="amount" cx="50%" cy="50%" />
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
""",
|
|
f"{BASE}\\frontend\\src\\components\\TrendChart.tsx": """import React from 'react';
|
|
import { LineChart, Line, XAxis, YAxis, ResponsiveContainer } from 'recharts';
|
|
|
|
export const TrendChart = ({ data, title = 'Spending Trends' }) => {
|
|
return (
|
|
<div style={{ padding: '1rem' }}>
|
|
<h3>{title}</h3>
|
|
{data && data.length > 0 && (
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<LineChart data={data}>
|
|
<XAxis dataKey="date" />
|
|
<YAxis />
|
|
<Line type="monotone" dataKey="amount" stroke="#8884d8" />
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
""",
|
|
f"{BASE}\\frontend\\src\\pages\\Dashboard.tsx": """import React, { useEffect, useState } from 'react';
|
|
import { apiService } from '../services/api';
|
|
|
|
export const Dashboard = () => {
|
|
const [summary, setSummary] = useState(null);
|
|
|
|
useEffect(() => {
|
|
apiService.getDashboardSummary().then(setSummary);
|
|
}, []);
|
|
|
|
return (
|
|
<div style={{ padding: '2rem' }}>
|
|
<h1>Financial Dashboard</h1>
|
|
{summary && <pre>{JSON.stringify(summary, null, 2)}</pre>}
|
|
</div>
|
|
);
|
|
};
|
|
""",
|
|
f"{BASE}\\frontend\\src\\pages\\ReportsPage.tsx": """import React, { useState } from 'react';
|
|
import { DateRangePicker } from '../components/DateRangePicker';
|
|
import { SpendingChart } from '../components/SpendingChart';
|
|
|
|
export const ReportsPage = () => {
|
|
const [reportType, setReportType] = useState('spending_by_category');
|
|
|
|
return (
|
|
<div style={{ padding: '2rem' }}>
|
|
<h1>Analytics & Reports</h1>
|
|
<DateRangePicker onDateRangeChange={() => {}} />
|
|
<SpendingChart data={[]} />
|
|
</div>
|
|
);
|
|
};
|
|
""",
|
|
f"{BASE}\\frontend\\src\\App.tsx": """import React, { useState } from 'react';
|
|
import { Dashboard } from './pages/Dashboard';
|
|
import { ReportsPage } from './pages/ReportsPage';
|
|
|
|
const App = () => {
|
|
const [currentPage, setCurrentPage] = useState('dashboard');
|
|
|
|
return (
|
|
<div style={{ fontFamily: 'Arial, sans-serif' }}>
|
|
<nav style={{ background: '#333', padding: '1rem', color: 'white' }}>
|
|
<button onClick={() => setCurrentPage('dashboard')}>Dashboard</button>
|
|
<button onClick={() => setCurrentPage('reports')}>Reports</button>
|
|
</nav>
|
|
<main>
|
|
{currentPage === 'dashboard' && <Dashboard />}
|
|
{currentPage === 'reports' && <ReportsPage />}
|
|
</main>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App;
|
|
""",
|
|
f"{BASE}\\frontend\\src\\index.tsx": """import React from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
import App from './App';
|
|
|
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
root.render(<App />);
|
|
""",
|
|
f"{BASE}\\frontend\\public\\index.html": """<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Firefly III Analytics</title>
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
</body>
|
|
</html>
|
|
""",
|
|
}
|
|
|
|
# Additional backend routers and services
|
|
additional_backend = {
|
|
f"{BASE}\\backend\\app\\routers\\__init__.py": "",
|
|
f"{BASE}\\backend\\app\\routers\\transactions.py": """from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from ..database import get_db
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("")
|
|
async def list_transactions(db: Session = Depends(get_db)):
|
|
return {"data": []}
|
|
""",
|
|
f"{BASE}\\backend\\app\\routers\\categories.py": """from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from ..database import get_db
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("")
|
|
async def list_categories(db: Session = Depends(get_db)):
|
|
return {"data": []}
|
|
""",
|
|
f"{BASE}\\backend\\app\\routers\\accounts.py": """from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from ..database import get_db
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("")
|
|
async def list_accounts(db: Session = Depends(get_db)):
|
|
return {"data": []}
|
|
""",
|
|
f"{BASE}\\backend\\app\\routers\\reports.py": """from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from ..database import get_db
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("/spending-by-category")
|
|
async def spending_by_category(db: Session = Depends(get_db)):
|
|
return {"data": []}
|
|
""",
|
|
f"{BASE}\\backend\\app\\routers\\summary.py": """from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from ..database import get_db
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("/dashboard")
|
|
async def dashboard_summary(db: Session = Depends(get_db)):
|
|
return {}
|
|
""",
|
|
f"{BASE}\\backend\\app\\clients\\__init__.py": "",
|
|
f"{BASE}\\backend\\app\\clients\\firefly_client.py": """import httpx
|
|
from ..config import settings
|
|
|
|
class FireflyClient:
|
|
def __init__(self):
|
|
self.base_url = settings.firefly_url.rstrip("/")
|
|
self.headers = {"Authorization": f"Bearer {settings.firefly_api_token}"}
|
|
|
|
async def get_transactions(self, start_date=None, end_date=None):
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(f"{self.base_url}/api/v1/transactions", headers=self.headers)
|
|
return response.json().get("data", [])
|
|
|
|
firefly_client = FireflyClient()
|
|
""",
|
|
f"{BASE}\\backend\\app\\services\\__init__.py": "",
|
|
f"{BASE}\\backend\\app\\services\\sync_service.py": """from apscheduler.schedulers.background import BackgroundScheduler
|
|
from datetime import datetime
|
|
|
|
scheduler = BackgroundScheduler()
|
|
|
|
def start_sync_scheduler():
|
|
if not scheduler.running:
|
|
scheduler.start()
|
|
""",
|
|
}
|
|
|
|
# Merge all files
|
|
all_files = {**backend_files, **frontend_files, **additional_backend}
|
|
|
|
# Create all files
|
|
print(f"Creating {len(all_files)} source code files...")
|
|
count = 0
|
|
for filepath, content in all_files.items():
|
|
try:
|
|
create_dir_and_file(filepath, content)
|
|
count += 1
|
|
except Exception as e:
|
|
print(f"✗ Error creating {filepath}: {e}")
|
|
|
|
print(f"\n✅ Successfully created {count} files!")
|
|
print("✅ Source code generation complete!")
|