diff --git a/btc-portfolio/backend/app/main.py b/btc-portfolio/backend/app/main.py index bafeb7e..2496285 100644 --- a/btc-portfolio/backend/app/main.py +++ b/btc-portfolio/backend/app/main.py @@ -2,7 +2,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .database import engine, Base -from .routes import users, purchases, stats +from .routes import users, purchases, stats, history Base.metadata.create_all(bind=engine) @@ -19,6 +19,7 @@ app.add_middleware( app.include_router(users.router) app.include_router(purchases.router) app.include_router(stats.router) +app.include_router(history.router) @app.get("/") diff --git a/btc-portfolio/backend/app/routes/history.py b/btc-portfolio/backend/app/routes/history.py new file mode 100644 index 0000000..dcae971 --- /dev/null +++ b/btc-portfolio/backend/app/routes/history.py @@ -0,0 +1,41 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session +from datetime import datetime, timezone + +from ..database import get_db +from .. import models +from ..dependencies import get_current_user +from ..services.btc import get_btc_history_eur + +router = APIRouter() + + +@router.get("/history") +def get_history( + db: Session = Depends(get_db), + current_user: models.User = Depends(get_current_user), +): + raw = get_btc_history_eur() + + # Convert timestamps and deduplicate (keep last price per date) + seen = {} + for ts, price in raw: + date = datetime.fromtimestamp(ts / 1000, tz=timezone.utc).strftime("%Y-%m-%d") + seen[date] = round(price, 2) + + prices = [{"date": d, "price": p} for d, p in seen.items()] + + purchases_db = db.query(models.Purchase).filter( + models.Purchase.user_id == current_user.id + ).all() + + purchases = [ + { + "date": p.created_at.strftime("%Y-%m-%d"), + "amount_eur": round(p.amount_eur, 2), + "price_eur": round(p.price_eur, 2), + } + for p in purchases_db + ] + + return {"prices": prices, "purchases": purchases} diff --git a/btc-portfolio/backend/app/services/btc.py b/btc-portfolio/backend/app/services/btc.py index e20acdb..de914f2 100644 --- a/btc-portfolio/backend/app/services/btc.py +++ b/btc-portfolio/backend/app/services/btc.py @@ -1,6 +1,19 @@ import requests +def get_btc_history_eur() -> list: + try: + resp = requests.get( + "https://api.coingecko.com/api/v3/coins/bitcoin/market_chart", + params={"vs_currency": "eur", "days": "365", "interval": "daily"}, + timeout=15, + ) + resp.raise_for_status() + return resp.json().get("prices", []) # [[timestamp_ms, price], ...] + except Exception: + return [] + + def get_btc_price_eur() -> float: try: resp = requests.get( diff --git a/btc-portfolio/frontend/src/components/BTCHistoryChart.js b/btc-portfolio/frontend/src/components/BTCHistoryChart.js new file mode 100644 index 0000000..7a5b1f5 --- /dev/null +++ b/btc-portfolio/frontend/src/components/BTCHistoryChart.js @@ -0,0 +1,121 @@ +import React, { useRef } from 'react'; +import { + Chart as ChartJS, + LineElement, + PointElement, + LinearScale, + CategoryScale, + Tooltip, + Legend, + Filler, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; + +ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Tooltip, Legend, Filler); + +const styles = { + card: { background: '#1a1a1a', padding: '1.5rem', borderRadius: '12px', border: '1px solid #333', marginBottom: '1.5rem' }, + title: { fontSize: '1.1rem', fontWeight: 700, marginBottom: '1rem', color: '#f7931a' }, + loading: { color: '#666', padding: '1rem 0' }, + saveBtn: { marginTop: '0.75rem', background: 'none', border: '1px solid #555', color: '#aaa', borderRadius: '6px', padding: '0.4rem 1rem', cursor: 'pointer', fontSize: '0.85rem' }, +}; + +export default function BTCHistoryChart({ history, stats }) { + const chartRef = useRef(null); + + if (!history || history.prices.length === 0) { + return ( +