Add dashboard endpoint

This commit is contained in:
Oliver Traber 2025-04-28 17:52:58 +00:00
parent b1a94c5359
commit 5ad07af3d2
Signed by: Bluemedia
GPG key ID: C0674B105057136C
2 changed files with 102 additions and 1 deletions

View file

@ -1,6 +1,8 @@
from datetime import UTC, datetime, timedelta
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from fastapi.params import Depends from fastapi.params import Depends
from sqlalchemy import select
from sqlalchemy.orm import Session as DbSession from sqlalchemy.orm import Session as DbSession
from app.database import get_db 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.security.jwt_bearer import JWTBearer
from app.services import session_service, user_service from app.services import session_service, user_service
from app.util.errors import InvalidStateError, NotFoundError 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)"]) router = APIRouter(prefix="/me", tags=["Me (v1)"])
@ -108,4 +113,75 @@ async def delete_user_session(
) )
except NotFoundError: except NotFoundError:
raise HTTPException(status_code=404, detail="session_not_found") raise HTTPException(status_code=404, detail="session_not_found")
return list() 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
)

View file

@ -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}