187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
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
|
|
from app.schemas.session import Session
|
|
from app.schemas.auth_token import AccessToken
|
|
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)"])
|
|
|
|
|
|
@router.get(path="", response_model=User)
|
|
async def get_myself(
|
|
db: DbSession = Depends(get_db), token: AccessToken = Depends(JWTBearer())
|
|
):
|
|
"""
|
|
Get the currently authenticated user.
|
|
"""
|
|
user = await user_service.get_user(db=db, id=UUID(token.subject))
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="user_not_found")
|
|
else:
|
|
return user
|
|
|
|
|
|
@router.patch(path="", response_model=User)
|
|
async def update_myself(
|
|
user_update: UserUpdate,
|
|
db: DbSession = Depends(get_db),
|
|
token: AccessToken = Depends(JWTBearer()),
|
|
):
|
|
"""
|
|
Update the currently authenticated user. Changing the email address automatically marks it as not verified
|
|
and starts a new verification workflow.
|
|
"""
|
|
try:
|
|
return await user_service.update_user(
|
|
db, UUID(token.subject), user_update
|
|
)
|
|
except NotFoundError:
|
|
raise HTTPException(status_code=404, detail="user_not_found")
|
|
|
|
|
|
@router.post(path="/password", response_model=list[None])
|
|
async def change_password(
|
|
update: PasswordUpdate,
|
|
db: DbSession = Depends(get_db),
|
|
token: AccessToken = Depends(JWTBearer()),
|
|
):
|
|
"""
|
|
Change the password of the currently authenticated user.
|
|
"""
|
|
try:
|
|
await user_service.change_user_password(
|
|
db=db, id=UUID(token.subject), update=update
|
|
)
|
|
return list()
|
|
except NotFoundError:
|
|
raise HTTPException(status_code=404, detail="user_not_found")
|
|
except InvalidStateError:
|
|
raise HTTPException(status_code=409, detail="incorrect_password")
|
|
|
|
|
|
@router.get(path="/sessions", response_model=list[Session])
|
|
async def get_user_sessions(
|
|
db: DbSession = Depends(get_db), token: AccessToken = Depends(JWTBearer())
|
|
):
|
|
"""
|
|
List the active sessions of the currently authenticated user.
|
|
"""
|
|
return await session_service.get_sessions_by_user(
|
|
db=db, user_id=UUID(token.subject)
|
|
)
|
|
|
|
|
|
@router.delete(path="/sessions", response_model=list[None])
|
|
async def clear_user_sessions(
|
|
db: DbSession = Depends(get_db), token: AccessToken = Depends(JWTBearer())
|
|
):
|
|
"""
|
|
Clear all sessions of the currently authenticated user.
|
|
"""
|
|
await session_service.remove_all_sessions_for_user(
|
|
db=db, user_id=UUID(token.subject),
|
|
)
|
|
return list()
|
|
|
|
|
|
@router.delete(path="/sessions/{session_id}", response_model=list[None])
|
|
async def delete_user_session(
|
|
session_id: UUID,
|
|
db: DbSession = Depends(get_db),
|
|
token: AccessToken = Depends(JWTBearer()),
|
|
):
|
|
"""
|
|
Invalidate a specific session of the currently authenticated user.
|
|
"""
|
|
try:
|
|
await session_service.remove_session_for_user(
|
|
db=db,
|
|
id=session_id,
|
|
user_id=UUID(token.subject),
|
|
)
|
|
except NotFoundError:
|
|
raise HTTPException(status_code=404, detail="session_not_found")
|
|
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
|
|
)
|