Prepare monorepo

This commit is contained in:
Oliver Traber 2025-03-13 22:11:20 +01:00
parent a1ddb43ed0
commit 938582155d
Signed by: Bluemedia
GPG key ID: C0674B105057136C
61 changed files with 5 additions and 5 deletions

View file

View file

@ -0,0 +1,72 @@
from uuid import UUID
from fastapi import APIRouter, HTTPException
from fastapi.params import Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.schemas.auth_token import (
AccessToken,
TokenRefreshRequest,
TokenResponse,
)
from app.schemas.user import LoginRequest
from app.security.jwt_bearer import JWTBearer
from app.services import session_service, token_service, user_service
from app.util.errors import NotFoundError
router = APIRouter(prefix="/auth", tags=["Authentication (v1)"])
@router.post(path="/login", response_model=TokenResponse)
async def login(
login_request: LoginRequest, db: Session = Depends(get_db)
):
"""
Login to a existing account. Creates a new session and returns a access and refresh token.
"""
user = await user_service.validate_login(
db=db, login=login_request
)
if not user:
raise HTTPException(status_code=403, detail="invalid_email_or_password")
session = await session_service.create_session(db=db, user=user, useragent="")
token, expire = await token_service.create_access_token(
user=user, session_id=session.id
)
return TokenResponse(
access_token=token, refresh_token=session.refresh_token, not_after=expire
)
@router.post(path="/logout", response_model=list[None])
async def logout(
db: Session = Depends(get_db), token: AccessToken = Depends(JWTBearer())
):
"""
Remove the current session based on the access token, effectively invalidating the current refresh token.
"""
await session_service.remove_session(
db=db, id=UUID(token.session), initiator=f"user:{token.subject}"
)
return list()
@router.post(path="/refresh", response_model=TokenResponse)
async def refresh_access_token(
token_request: TokenRefreshRequest,
db: Session = Depends(get_db),
):
"""
Use an existing refresh token to generate a new access token and a new refresh token.
"""
try:
session = await session_service.validate_and_rotate_refresh_token(
db=db, refresh_token=token_request.refresh_token
)
user = await user_service.get_user(db=db, id=session.user_id)
token, expire = await token_service.create_access_token(
user=user, session_id=session.id
)
return TokenResponse(
access_token=token, refresh_token=session.refresh_token, not_after=expire
)
except NotFoundError:
raise HTTPException(status_code=403, detail="invalid_refresh_token")

View file

@ -0,0 +1,295 @@
import random
import string
from datetime import datetime, timedelta, UTC
from uuid import UUID
from fastapi import APIRouter, HTTPException
from fastapi.params import Depends
from sqlalchemy.orm import Session
from ocpp.v201.call import Reset, SetVariables
from app.database import get_db
from app.ocpp_proto import chargepoint_manager
from app.schemas.auth_token import AccessToken
from app.schemas.chargepoint import (
ChargePoint,
ChargePointCreate,
ChargePointUpdate,
ChargePointPassword,
ChargePointConnectionInfo,
ChargePointResetRequest,
ChargePointResetResponse
)
from app.schemas.id_token import IdTokenLearnRequest, IdTokenLearnResponse
from app.schemas.chargepoint_variable import (
ChargepointVariable,
ChargepointVariableUpdate,
ChargepointVariableResponse,
MutabilityType,
SetVariableStatusType
)
from app.models.chargepoint import ChargePoint as DbChargePoint
from app.models.user import User as DbUser
from app.models.chargepoint_variable import ChargepointVariable as DbChargepointVariable
from app.security.jwt_bearer import JWTBearer
router = APIRouter(
prefix="/chargepoints",
tags=["Chargepoint (v1)"],
)
@router.get(path="", response_model=list[ChargePoint])
async def get_chargepoints(
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
return db.query(DbChargePoint).order_by(DbChargePoint.identity).offset(skip).limit(limit).all()
@router.get(path="/{chargepoint_id}", response_model=ChargePoint)
async def get_chargepoint(
chargepoint_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
return chargepoint
@router.get(path="/{chargepoint_id}/password", response_model=ChargePointPassword)
async def get_chargepoint_password(
chargepoint_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
return ChargePointPassword(password=chargepoint.password)
@router.delete(path="/{chargepoint_id}/password", response_model=ChargePointPassword)
async def reset_chargepoint_password(
chargepoint_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
chargepoint.password = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(24))
db.commit()
return ChargePointPassword(password=chargepoint.password)
@router.post(path="", status_code=201, response_model=ChargePoint)
async def create_chargepoint(
chargepoint: ChargePointCreate,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint_db = DbChargePoint(
identity=chargepoint.identity,
is_active=chargepoint.is_active,
password=''.join(random.choice(string.ascii_letters + string.digits) for i in range(24)),
price=chargepoint.price
)
db.add(chargepoint_db)
db.commit()
db.refresh(chargepoint_db)
return chargepoint_db
@router.patch(path="/{chargepoint_id}", response_model=ChargePoint)
async def update_chargepoint(
chargepoint_id: UUID,
chargepoint_update: ChargePointUpdate,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
for key, value in chargepoint_update.model_dump(exclude_unset=True).items():
setattr(chargepoint, key, value)
db.commit()
return chargepoint
@router.delete(path="/{chargepoint_id}", response_model=None)
async def delete_chargepoint(
chargepoint_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
db.delete(chargepoint)
db.commit()
return []
@router.get(path="/{chargepoint_id}/status", response_model=ChargePointConnectionInfo)
async def get_chargepoint_status(
chargepoint_id: UUID,
token: AccessToken = Depends(JWTBearer()),
):
return ChargePointConnectionInfo(
connected=chargepoint_manager.is_connected(chargepoint_id)
)
@router.post(path="/{chargepoint_id}/reset", response_model=ChargePointResetResponse)
async def reset_chargepoint(
chargepoint_id: UUID,
reset_request: ChargePointResetRequest,
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
if chargepoint_manager.is_connected(chargepoint_id) == False:
raise HTTPException(status_code=503, detail="Chargepoint not connected.")
try:
response = await chargepoint_manager.call(
chargepoint_id,
payload=Reset(type=reset_request.type, evse_id=reset_request.evse_id)
)
return ChargePointResetResponse(status=response.status)
except TimeoutError:
raise HTTPException(status_code=503, detail="Chargepoint didn't respond in time.")
@router.post(path="/{chargepoint_id}/token-learning", status_code=201, response_model=IdTokenLearnResponse)
async def create_id_token_learn_request(
chargepoint_id: UUID,
learn_request: IdTokenLearnRequest,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
owner = db.get(DbUser, learn_request.user_id)
if owner == None:
raise HTTPException(status_code=422, detail=[{
"loc": ["body", "user_id"],
"msg": "Target user not found",
"type": "invalid_relation"
}])
chargepoint.learn_user_id = learn_request.user_id
if learn_request.until == None:
chargepoint.learn_until = datetime.now(UTC) + timedelta(minutes=5)
else:
chargepoint.learn_until = learn_request.until
db.commit()
return IdTokenLearnResponse(
user_id=chargepoint.learn_user_id,
until=chargepoint.learn_until
)
@router.get(path="/{chargepoint_id}/token-learning", response_model=IdTokenLearnResponse)
async def get_id_token_learn_request(
chargepoint_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
if chargepoint.learn_user_id == None:
raise HTTPException(status_code=404, detail="No active learning request")
return IdTokenLearnResponse(
user_id=chargepoint.learn_user_id,
until=chargepoint.learn_until
)
@router.delete(path="/{chargepoint_id}/token-learning", response_model=[])
async def get_id_token_learn_request(
chargepoint_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
if chargepoint.learn_user_id == None:
raise HTTPException(status_code=404, detail="No active learning request")
chargepoint.learn_user_id = None
chargepoint.learn_until = None
db.commit()
return []
@router.get(path="/{chargepoint_id}/variables", response_model=list[ChargepointVariable])
async def get_chargepoint_variables(
chargepoint_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
return db.query(DbChargepointVariable).filter(DbChargepointVariable.chargepoint_id == chargepoint_id).all()
@router.put(path="/{chargepoint_id}/variables/{variable_id}", response_model=ChargepointVariableResponse)
async def update_chargepoint_variable(
chargepoint_id: UUID,
variable_id: UUID,
variable_update: ChargepointVariableUpdate,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
chargepoint = db.get(DbChargePoint, chargepoint_id)
if chargepoint is None:
raise HTTPException(status_code=404, detail="Chargepoint not found")
variable = db.query(DbChargepointVariable).filter(
DbChargepointVariable.chargepoint_id == chargepoint_id,
DbChargepointVariable.id == variable_id
).first()
if variable is None:
raise HTTPException(status_code=404, detail="ChargepointVariable not found")
if variable.mutability == MutabilityType.READ_ONLY:
raise HTTPException(status_code=422, detail="ChargepointVariable is read-only")
variable.value = variable_update.value
if chargepoint_manager.is_connected(chargepoint_id) == False:
raise HTTPException(status_code=503, detail="Chargepoint not connected.")
try:
evse = None
if variable.evse != None:
evse = {
'id': variable.evse
}
if variable.connector_id != None:
evse['connectorId'] = variable.connector_id
result = await chargepoint_manager.call(
chargepoint_id,
payload=SetVariables(set_variable_data=[
{
'attributeType': variable.type.value,
'attributeValue': variable_update.value,
'component': {
'name': variable.component_name,
'instance': variable.component_instance,
'evse': evse
},
'variable': {
'name': variable.name
}
}
])
)
status = result.set_variable_result[0]['attribute_status']
if SetVariableStatusType(status) in [SetVariableStatusType.ACCEPTED, SetVariableStatusType.REBOOT_REQUIRED]:
db.commit()
else:
raise HTTPException(status_code=500, detail=status)
return ChargepointVariableResponse(status=status)
except TimeoutError:
raise HTTPException(status_code=503, detail="Chargepoint didn't respond in time.")

View file

@ -0,0 +1,115 @@
from uuid import UUID
from fastapi import APIRouter, HTTPException
from fastapi.params import Depends
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.database import get_db
from app.schemas.auth_token import AccessToken
from app.schemas.id_token import IdToken, IdTokenCreate, IdTokenUpdate
from app.models.id_token import IdToken as DbIdToken
from app.models.user import User as DbUser
from app.schemas.user import Role
from app.security.jwt_bearer import JWTBearer
router = APIRouter(
prefix="/id-tokens",
tags=["IdToken (v1)"]
)
@router.get(path="", response_model=list[IdToken])
async def get_id_tokens(
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
stmt = select(Session)
if token.role != Role.ADMINISTRATOR:
stmt = stmt.where(DbIdToken.owner_id == token.subject)
stmt = stmt.order_by(DbIdToken.id).offset(skip).limit(limit)
result = db.execute(stmt)
return result.scalars().all()
@router.get(path="/{id_token_id}", response_model=IdToken)
async def get_id_token(
id_token_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
stmt = select(DbIdToken).where(DbIdToken.id == id_token_id)
result = db.execute(stmt)
id_token = result.scalars().first()
if id_token == None:
raise HTTPException(status_code=404, detail="IdToken not found")
if token.role != Role.ADMINISTRATOR & id_token.owner_id != token.subject:
raise HTTPException(status_code=404, detail="IdToken not found")
return id_token
@router.post(path="", status_code=201, response_model=IdToken)
async def create_id_token(
create_id_token: IdTokenCreate,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
stmt = select(DbUser).where(DbUser.id == create_id_token.owner_id)
result = db.execute(stmt)
owner = result.scalars().first()
if owner == None:
raise HTTPException(status_code=422, detail=[{
"loc": ["body", "owner_id"],
"msg": "Owner not found",
"type": "invalid_relation"
}])
id_token = DbIdToken(
friendly_name=create_id_token.friendly_name,
is_active=create_id_token.is_active,
token=create_id_token.token,
owner_id=create_id_token.owner_id
)
db.add(id_token)
db.commit()
db.refresh(id_token)
return id_token
@router.patch(path="/{id_token_id}", response_model=IdToken)
async def update_id_token(
id_token_id: UUID,
id_token_update: IdTokenUpdate,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
stmt = select(DbIdToken).where(DbIdToken.id == id_token_id)
result = db.execute(stmt)
id_token = result.scalars().first()
if id_token is None:
raise HTTPException(status_code=404, detail="IdToken not found")
for key, value in id_token_update.model_dump(exclude_unset=True).items():
if key == "owner_id":
stmt = select(DbUser).where(DbUser.id == id_token_update.owner_id)
result = db.execute(stmt)
owner = result.scalars().first()
if owner == None:
raise HTTPException(status_code=422, detail=[{
"loc": ["body", "owner_id"],
"msg": "Owner not found",
"type": "invalid_relation"
}])
setattr(id_token, key, value)
db.commit()
return id_token
@router.delete(path="/{id_token_id}", response_model=None)
async def delete_id_token(
id_token_id: UUID,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
stmt = select(DbIdToken).where(DbIdToken.id == id_token_id)
result = db.execute(stmt)
id_token = result.scalars().first()
if id_token == None:
raise HTTPException(status_code=404, detail="IdToken not found")
db.delete(id_token)
db.commit()
return []

View file

@ -0,0 +1,111 @@
from uuid import UUID
from fastapi import APIRouter, HTTPException
from fastapi.params import Depends
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
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()

View file

@ -0,0 +1,25 @@
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.schemas.auth_token import AccessToken
from app.database import get_db
from app.schemas.meter_value import MeterValue
from app.models.meter_value import MeterValue as DbMeterValue
from app.security.jwt_bearer import JWTBearer
router = APIRouter(
prefix="/meter-values",
tags=["MeterValue (v1)"]
)
@router.get(path="", response_model=list[MeterValue])
async def get_meter_values(
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
stmt = select(DbMeterValue).order_by(DbMeterValue.timestamp).offset(skip).limit(limit)
result = db.execute(stmt)
return result.scalars().all()

View file

@ -0,0 +1,47 @@
import logging
from fastapi import APIRouter, WebSocket, WebSocketException
from app.ocpp_proto import chargepoint_manager
from app.ocpp_proto.chargepoint import ChargePoint
from app.util.websocket_wrapper import WebSocketWrapper
router = APIRouter()
@router.websocket("/{chargepoint_identity}")
async def websocket_endpoint(
*,
websocket: WebSocket,
chargepoint_identity: str,
):
""" For every new charging station that connects, create a ChargePoint
instance and start listening for messages.
"""
if (websocket.user.identity != chargepoint_identity):
raise WebSocketException(code=1008, reason="Username doesn't match chargepoint identifier")
logging.info("Charging station '%s' (%s) connected", chargepoint_identity, websocket.user.id)
# Check protocols
try:
requested_protocols = websocket.headers['sec-websocket-protocol']
logging.info("Protocols advertised by charging station: %s", requested_protocols)
except KeyError:
logging.warning("Charging station hasn't advertised any subprotocol. "
"Closing Connection")
return await websocket.close()
if "ocpp2.0.1" in requested_protocols:
logging.info("Matched supported protocol: ocpp2.0.1")
else:
logging.warning('Protocols mismatched | Expected subprotocols: %s,'
' but client supports %s | Closing connection',
"ocpp2.0.1",
requested_protocols)
await websocket.accept()
await websocket.close()
return
# Accept connection and begin communication
await websocket.accept(subprotocol="ocpp2.0.1")
cp = ChargePoint(chargepoint_identity, WebSocketWrapper(websocket))
await chargepoint_manager.start(websocket.user.id, cp)

View file

@ -0,0 +1,100 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from ocpp.v201.call import RequestStopTransaction
from app.ocpp_proto import chargepoint_manager
from app.schemas.auth_token import AccessToken
from app.database import get_db
from app.schemas.meter_value import MeterValue
from app.schemas.transaction import Transaction, RemoteTransactionStartStopResponse, TransactionStatus, RemoteTransactionStartStopStatus
from app.models.transaction import Transaction as DbTransaction
from app.models.meter_value import MeterValue as DbMeterValue
from app.schemas.user import Role
from app.security.jwt_bearer import JWTBearer
router = APIRouter(
prefix="/transactions",
tags=["Transaction (v1)"]
)
@router.get(path="", response_model=list[Transaction])
async def get_transactions(
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
stmt = select(DbTransaction)
if (token.role != Role.ADMINISTRATOR):
stmt = stmt.where(DbTransaction.user_id == token.subject)
stmt = stmt.order_by(DbTransaction.started_at).offset(skip).limit(limit)
result = db.execute(stmt)
return result.scalars().all()
@router.get(path="/{transaction_id}", response_model=Transaction)
async def get_transaction(
transaction_id: str,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
stmt = select(DbTransaction).where(DbTransaction.id == transaction_id)
result = db.execute(stmt)
transaction = result.scalars().first()
if transaction == None:
raise HTTPException(404, "Transaction not found")
if token.role != Role.ADMINISTRATOR & transaction.user_id != token.subject:
raise HTTPException(404, "Transaction not found")
return transaction
@router.get(path="/{transaction_id}/meter-values", response_model=list[MeterValue])
async def get_transaction_meter_values(
transaction_id: str,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
stmt = select(DbTransaction).where(DbTransaction.id == transaction_id)
result = db.execute(stmt)
transaction = result.scalars().first()
if transaction == None:
raise HTTPException(404, "Transaction not found")
if token.role != Role.ADMINISTRATOR & transaction.user_id != token.subject:
raise HTTPException(404, "Transaction not found")
stmt = select(DbMeterValue).where(DbMeterValue.transaction_id == transaction_id).order_by(DbMeterValue.timestamp)
result = db.execute(stmt)
return result.scalars().all()
@router.post(path="/{transaction_id}/remote-stop", response_model=RemoteTransactionStartStopResponse)
async def remote_stop_transaction(
transaction_id: str,
db: Session = Depends(get_db),
token: AccessToken = Depends(JWTBearer()),
):
stmt = select(DbTransaction).where(DbTransaction.id == transaction_id)
result = db.execute(stmt)
transaction = result.scalars().first()
if transaction == None:
raise HTTPException(404, "Transaction not found")
if token.role != Role.ADMINISTRATOR & transaction.user_id != token.subject:
raise HTTPException(404, "Transaction not found")
if transaction.status != TransactionStatus.ONGOING:
raise HTTPException(status_code=422, detail=[{
"loc": ["path", "transaction_id"],
"msg": "Transaction is not ongoing",
"type": "invalid_transaction_state"
}])
if chargepoint_manager.is_connected(transaction.chargepoint_id) == False:
raise HTTPException(status_code=503, detail="chargepoint_offline")
try:
result = await chargepoint_manager.call(
transaction.chargepoint_id,
payload=RequestStopTransaction(
transaction_id=transaction.id
)
)
if RemoteTransactionStartStopStatus(result.status) != RemoteTransactionStartStopStatus.REJECTED:
raise HTTPException(status_code=500, detail=result.status)
return RemoteTransactionStartStopResponse(status=result.status)
except TimeoutError:
raise HTTPException(status_code=503, detail="chargepoint_operation_timeout")

View file

@ -0,0 +1,116 @@
from uuid import UUID
from fastapi import APIRouter, HTTPException
from fastapi.params import Depends
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 AdministrativeUserUpdate, User, UserCreate
from app.security.jwt_bearer import JWTBearer
from app.services import session_service, user_service
from app.util.errors import NotFoundError
router = APIRouter(
prefix="/users",
tags=["User (v1)"],
)
@router.get(path="", response_model=list[User])
async def get_users(
email: str = None,
skip: int = 0,
limit: int = 20,
db: DbSession = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
return await user_service.get_users(db, skip, limit, email)
@router.post(path="", status_code=201, response_model=User)
async def create_user(
create_user: UserCreate,
db: DbSession = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
user = await user_service.create_user(
db=db, user=create_user
)
return user
@router.patch(path="/{user_id}", response_model=User)
async def update_user(
user_id: UUID,
user_update: AdministrativeUserUpdate,
db: DbSession = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
try:
return await user_service.update_user(
db, user_id, user_update
)
except NotFoundError:
raise HTTPException(status_code=404, detail="user_not_found")
@router.delete(path="/{user_id}", response_model=None)
async def delete_user(
user_id: UUID,
db: DbSession = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
try:
await user_service.remove_user(db, user_id)
return list()
except NotFoundError:
raise HTTPException(status_code=404, detail="user_not_found")
@router.get(
path="/{user_id}/sessions", response_model=list[Session]
)
async def get_user_sessions(
user_id: UUID,
db: DbSession = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
"""
Query sessions of the specified user. Requires the "administrator" role.
"""
return await session_service.get_sessions_by_user(db=db, user_id=user_id)
@router.delete(
path="/{user_id}/sessions", response_model=list[None]
)
async def remove_all_user_session(
user_id: UUID,
db: DbSession = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
"""
Delete all sessions of the specified user. Requires the "administrator" role.
"""
await session_service.remove_all_sessions_for_user(
db=db, user_id=user_id
)
return list()
@router.delete(
path="/{user_id}/sessions/{session_id}",
response_model=list[None],
)
async def remove_user_session(
user_id: UUID,
session_id: UUID,
db: DbSession = Depends(get_db),
token: AccessToken = Depends(JWTBearer(required_roles=["administrator"])),
):
"""
Delete the specified session of the specified user. Requires the "administrator" role.
"""
try:
await session_service.remove_session_for_user(
db=db, id=session_id, user_id=user_id
)
except NotFoundError:
raise HTTPException(status_code=404, detail="session_not_found")
return list()