Compare commits

..

3 commits

9 changed files with 160 additions and 27 deletions

View file

@ -1,11 +1,13 @@
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.middleware.authentication import AuthenticationMiddleware
import uvicorn
from app.database import engine, Base
from app.models import *
from app.routers import chargepoint_v1, user_v1, ocpp_v1
from app.routers import chargepoint_v1, id_token_v1, ocpp_v1, user_v1
from app.util.websocket_auth_backend import BasicAuthBackend
Base.metadata.create_all(bind=engine)
@ -25,6 +27,7 @@ def create_app():
)
app.include_router(chargepoint_v1.router, prefix="/v1")
app.include_router(id_token_v1.router, prefix="/v1")
app.include_router(user_v1.router, prefix="/v1")
app.mount(path="/v1/ocpp", app=create_ocpp_app())

View file

@ -11,6 +11,11 @@ class ChargePoint(Base):
friendly_name = Column(String, unique=True, index=True)
is_active = Column(Boolean, default=True)
password = Column(String)
last_seen = Column(DateTime, nullable=True)
vendor_name = Column(String, nullable=True)
model = Column(String, nullable=True)
serial_number = Column(String, nullable=True)
firmware_version = Column(String, nullable=True)
connectors = relationship("Connector", cascade="delete, delete-orphan")

View file

@ -8,7 +8,7 @@ class IdToken(Base):
__tablename__ = "id_tokens"
id = Column(Uuid, primary_key=True, default=uuid.uuid4)
title = Column(String)
friendly_name = Column(String)
is_active = Column(Boolean, default=True)
token = Column(String, index=True)

View file

@ -4,10 +4,11 @@ import os
from ocpp.routing import on
from ocpp.v201 import ChargePoint as cp
from ocpp.v201 import call_result
from ocpp.v201.enums import Action, RegistrationStatusType, AuthorizationStatusType
from ocpp.v201.datatypes import IdTokenInfoType, IdTokenType
from ocpp.v201.enums import Action, RegistrationStatusType, AuthorizationStatusType, IdTokenType as IdTokenEnumType
from app.database import SessionLocal
from app.models.chargepoint import ChargePoint
from app.models.chargepoint import ChargePoint as DbChargePoint
from app.models.connector import Connector
from app.models.id_token import IdToken
from app.schemas.connector import ConnectorStatus
@ -15,10 +16,13 @@ from app.schemas.connector import ConnectorStatus
class ChargePoint(cp):
@on(Action.BootNotification)
async def on_boot_notification(self, charging_station, reason, **kwargs):
async def on_boot_notification(self, charging_station, **kwargs):
with SessionLocal() as db:
db_chargepoint = db.query(ChargePoint).filter(ChargePoint.friendly_name == self.id).first()
db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first()
db_chargepoint.last_seen = datetime.now(UTC)
for key in charging_station.keys():
if key in db_chargepoint.__dict__:
setattr(db_chargepoint, key, charging_station[key])
db.commit()
return call_result.BootNotificationPayload(
current_time=datetime.now(UTC).isoformat(),
@ -29,7 +33,7 @@ class ChargePoint(cp):
@on(Action.Heartbeat)
async def on_heartbeat_request(self):
with SessionLocal() as db:
db_chargepoint = db.query(ChargePoint).filter(ChargePoint.friendly_name == self.id).first()
db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first()
db_chargepoint.last_seen = datetime.now(UTC)
db.commit()
return call_result.HeartbeatPayload(
@ -39,7 +43,7 @@ class ChargePoint(cp):
@on(Action.StatusNotification)
async def on_status_notification(self, evse_id: int, connector_id: int, connector_status: str, **kwargs):
with SessionLocal() as db:
db_chargepoint = db.query(ChargePoint).filter(ChargePoint.friendly_name == self.id).first()
db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first()
db_chargepoint.last_seen = datetime.now(UTC)
db_connector = db.query(Connector).filter(
@ -64,24 +68,38 @@ class ChargePoint(cp):
@on(Action.Authorize)
async def on_authorize(self, id_token, **kwargs):
if id_token == None:
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.invalid})
if id_token.type != "ISO14443" | "ISO15693":
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.invalid})
if id_token["type"] not in ["ISO14443", "ISO15693"]:
return call_result.AuthorizePayload(
id_token_info=IdTokenInfoType(
status=AuthorizationStatusType.invalid
)
)
with SessionLocal() as db:
db_chargepoint = db.query(ChargePoint).filter(ChargePoint.friendly_name == self.id).first()
db_chargepoint = db.query(DbChargePoint).filter(DbChargePoint.friendly_name == self.id).first()
db_chargepoint.last_seen = datetime.now(UTC)
db_id_token = db.query(IdToken).filter(IdToken.token == id_token.id).first()
db_id_token = db.query(IdToken).filter(IdToken.token == id_token["id_token"]).first()
db.commit()
if db_id_token == None:
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.unknown})
if db_id_token.is_active == False:
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.blocked})
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.accepted, 'groupIdToken': str(db_id_token.owner_id)})
if db_id_token == None:
id_token_info=IdTokenInfoType(
status=AuthorizationStatusType.unknown
)
else:
if db_id_token.is_active == False:
id_token_info=IdTokenInfoType(
status=AuthorizationStatusType.blocked
)
else:
id_token_info=IdTokenInfoType(
status=AuthorizationStatusType.accepted,
group_id_token=IdTokenType(
type=IdTokenEnumType.central,
id_token=str(db_id_token.owner_id)
)
)
return call_result.AuthorizePayload(id_token_info)
@on(Action.TransactionEvent)
async def on_transaction_event(self):

View file

@ -12,8 +12,8 @@ from app.models.chargepoint import ChargePoint as DbChargePoint
from app.security import get_api_key
router = APIRouter(
prefix="/chargepoint",
tags=["chargepoint (v1)"],
prefix="/chargepoints",
tags=["Chargepoint (v1)"],
)
@router.get(path="", response_model=list[ChargePoint])

View file

@ -0,0 +1,96 @@
from uuid import UUID
from fastapi import APIRouter, HTTPException, Security
from fastapi.exceptions import RequestValidationError
from fastapi.params import Depends
from sqlalchemy.orm import Session
from app.database import get_db
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.security import get_api_key
router = APIRouter(
prefix="/id-tokens",
tags=["IdToken (v1)"]
)
@router.get(path="", response_model=list[IdToken])
async def get_it_tokens(
skip: int = 0,
limit: int = 20,
api_key: str = Security(get_api_key),
db: Session = Depends(get_db)
):
return db.query(DbIdToken).offset(skip).limit(limit).all()
@router.get(path="/{id_token_id}", response_model=IdToken)
async def get_id_token(
id_token_id: UUID,
api_key: str = Security(get_api_key),
db: Session = Depends(get_db)
):
id_token = db.get(DbIdToken, id_token_id)
if id_token == None:
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,
api_key: str = Security(get_api_key),
db: Session = Depends(get_db)
):
owner = db.get(DbUser, create_id_token.owner_id)
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,
api_key: str = Security(get_api_key),
db: Session = Depends(get_db)
):
id_token = db.get(DbIdToken, id_token_id)
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":
owner = db.get(DbUser, value)
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,
api_key: str = Security(get_api_key),
db: Session = Depends(get_db)
):
id_token = db.get(DbIdToken, id_token_id)
if id_token == None:
raise HTTPException(status_code=404, detail="IdToken not found")
db.delete(id_token)
db.commit()
return []

View file

@ -9,8 +9,8 @@ from app.models.user import User as DbUser
from app.security import get_api_key
router = APIRouter(
prefix="/user",
tags=["user (v1)"],
prefix="/users",
tags=["User (v1)"],
)
@router.get(path="", response_model=list[User])

View file

@ -19,6 +19,10 @@ class ChargePointCreate(ChargePointBase):
class ChargePoint(ChargePointBase):
id: UUID
last_seen: datetime | None
vendor_name: str | None
model: str | None
serial_number: str | None
firmware_version: str | None
connectors: list[Connector] = []
class Config:

View file

@ -1,18 +1,25 @@
from typing import Optional
from uuid import UUID
from pydantic import BaseModel
from app.schemas.user import User
class IdTokenBase(BaseModel):
title: str
friendly_name: str
is_active: bool
owner_id: UUID
token: str
class IdTokenCreate(IdTokenBase):
pass
class IdTokenUpdate(BaseModel):
friendly_name: Optional[str] = None
is_active: Optional[bool] = None
owner_id: Optional[UUID] = None
class IdToken(IdTokenBase):
id: UUID
owner: User
class Config:
from_attributes = True