From 5ad07af3d2481344047216023e350108d0d7ad90 Mon Sep 17 00:00:00 2001 From: BluemediaDev Date: Mon, 28 Apr 2025 17:52:58 +0000 Subject: [PATCH] Add dashboard endpoint --- backend/app/routers/me_v1.py | 78 +++++++++++++++++++++++++++++++- backend/app/schemas/dashboard.py | 25 ++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 backend/app/schemas/dashboard.py diff --git a/backend/app/routers/me_v1.py b/backend/app/routers/me_v1.py index 635f9ed..a5635d5 100644 --- a/backend/app/routers/me_v1.py +++ b/backend/app/routers/me_v1.py @@ -1,6 +1,8 @@ +from datetime import UTC, datetime, timedelta from uuid import UUID from fastapi import APIRouter, HTTPException from fastapi.params import Depends +from sqlalchemy import select from sqlalchemy.orm import Session as DbSession from app.database import get_db @@ -10,6 +12,9 @@ from app.schemas.user import PasswordUpdate, UserUpdate, User from app.security.jwt_bearer import JWTBearer from app.services import session_service, user_service from app.util.errors import InvalidStateError, NotFoundError +from app.schemas.dashboard import DashboardResponse, DashboardStats +from app.models.transaction import Transaction as DbTransaction +from app.schemas.transaction import TransactionStatus router = APIRouter(prefix="/me", tags=["Me (v1)"]) @@ -108,4 +113,75 @@ async def delete_user_session( ) except NotFoundError: raise HTTPException(status_code=404, detail="session_not_found") - return list() \ No newline at end of file + return list() + +@router.get(path="/dashboard", response_model=DashboardResponse) +async def get_dashboard( + db: DbSession = Depends(get_db), + token: AccessToken = Depends(JWTBearer()), +): + """ + Get dashboard information for the currently authenticated user. + """ + # Currently ongoing transaction + stmt_current_transaction = select(DbTransaction).where(DbTransaction.user_id == token.subject).where(DbTransaction.status == TransactionStatus.ONGOING) + current_transaction = db.execute(stmt_current_transaction).scalars().first() + + # Common base query + stmt_base = select(DbTransaction).where(DbTransaction.user_id == token.subject).where( DbTransaction.status == TransactionStatus.ENDED).order_by(DbTransaction.ended_at.desc()) + + # 5 most recent transactions + stmt_transactions_recent = stmt_base.limit(5) + recent_transactions = db.execute(stmt_transactions_recent).scalars().all() + + # Calculate beginning of the current and previous month + current_date = datetime.now(UTC).replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) + + beginning_current_month = current_date.replace(day=1) + + beginning_previous_month = beginning_current_month.replace( + year=(beginning_current_month.year - 1 if beginning_current_month.month == 1 else beginning_current_month.year), + month=(12 if beginning_current_month.month == 1 else beginning_current_month.month - 1) + ) + + # Transactions for total calculations + stmt_transactions = stmt_base.where(DbTransaction.ended_at >= beginning_previous_month) + transactions = db.execute(stmt_transactions).scalars().all() + + # Current month totals + count = 0 + energy_total = 0 + cost_total = 0 + + # Previous month totals + count_previous = 0 + energy_total_previous = 0 + cost_total_previous = 0 + + # Calculate totals + for trans in transactions: + trans_energy = trans.meter_end - trans.meter_start + trans_cost = trans_energy * trans.price + if trans.ended_at >= beginning_current_month: + # Current month + energy_total += trans_energy + cost_total += trans_cost + count += 1 + else: + # Previous month + energy_total_previous += trans_energy + cost_total_previous += trans_cost + count_previous += 1 + + return DashboardResponse( + stats=DashboardStats( + transaction_count=count, + transaction_count_previous=count_previous, + transaction_energy_total=energy_total, + transaction_energy_total_previous=energy_total_previous, + transaction_cost_total=cost_total, + transaction_cost_total_previous=cost_total_previous + ), + current_transaction=current_transaction, + recent_transactions=recent_transactions + ) diff --git a/backend/app/schemas/dashboard.py b/backend/app/schemas/dashboard.py new file mode 100644 index 0000000..bd2c0e3 --- /dev/null +++ b/backend/app/schemas/dashboard.py @@ -0,0 +1,25 @@ +from decimal import Decimal +from typing import Optional +from pydantic import BaseModel + +from app.schemas.transaction import Transaction +from app.util.encoders import decimal_encoder + +class DashboardStats(BaseModel): + transaction_count: int + transaction_count_previous: int + transaction_energy_total: Decimal + transaction_energy_total_previous: Decimal + transaction_cost_total: Decimal + transaction_cost_total_previous: Decimal + + class Config: + json_encoders = {Decimal: decimal_encoder} + +class DashboardResponse(BaseModel): + stats: DashboardStats + current_transaction: Optional[Transaction] = None + recent_transactions: list[Transaction] + + class Config: + json_encoders = {Decimal: decimal_encoder} \ No newline at end of file