Compare commits
3 commits
e33d74308a
...
7780f247fb
Author | SHA1 | Date | |
---|---|---|---|
Oliver Traber | 7780f247fb | ||
Oliver Traber | d25f7f9838 | ||
Oliver Traber | e5ae0bd58e |
|
@ -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
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
from app.database import engine, Base
|
from app.database import engine, Base
|
||||||
from app.models import *
|
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
|
from app.util.websocket_auth_backend import BasicAuthBackend
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
@ -25,6 +27,7 @@ def create_app():
|
||||||
)
|
)
|
||||||
|
|
||||||
app.include_router(chargepoint_v1.router, prefix="/v1")
|
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.include_router(user_v1.router, prefix="/v1")
|
||||||
app.mount(path="/v1/ocpp", app=create_ocpp_app())
|
app.mount(path="/v1/ocpp", app=create_ocpp_app())
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,11 @@ class ChargePoint(Base):
|
||||||
friendly_name = Column(String, unique=True, index=True)
|
friendly_name = Column(String, unique=True, index=True)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
password = Column(String)
|
password = Column(String)
|
||||||
|
|
||||||
last_seen = Column(DateTime, nullable=True)
|
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")
|
connectors = relationship("Connector", cascade="delete, delete-orphan")
|
||||||
|
|
|
@ -8,7 +8,7 @@ class IdToken(Base):
|
||||||
__tablename__ = "id_tokens"
|
__tablename__ = "id_tokens"
|
||||||
|
|
||||||
id = Column(Uuid, primary_key=True, default=uuid.uuid4)
|
id = Column(Uuid, primary_key=True, default=uuid.uuid4)
|
||||||
title = Column(String)
|
friendly_name = Column(String)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
token = Column(String, index=True)
|
token = Column(String, index=True)
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,11 @@ import os
|
||||||
from ocpp.routing import on
|
from ocpp.routing import on
|
||||||
from ocpp.v201 import ChargePoint as cp
|
from ocpp.v201 import ChargePoint as cp
|
||||||
from ocpp.v201 import call_result
|
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.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.connector import Connector
|
||||||
from app.models.id_token import IdToken
|
from app.models.id_token import IdToken
|
||||||
from app.schemas.connector import ConnectorStatus
|
from app.schemas.connector import ConnectorStatus
|
||||||
|
@ -15,10 +16,13 @@ from app.schemas.connector import ConnectorStatus
|
||||||
class ChargePoint(cp):
|
class ChargePoint(cp):
|
||||||
|
|
||||||
@on(Action.BootNotification)
|
@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:
|
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_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()
|
db.commit()
|
||||||
return call_result.BootNotificationPayload(
|
return call_result.BootNotificationPayload(
|
||||||
current_time=datetime.now(UTC).isoformat(),
|
current_time=datetime.now(UTC).isoformat(),
|
||||||
|
@ -29,7 +33,7 @@ class ChargePoint(cp):
|
||||||
@on(Action.Heartbeat)
|
@on(Action.Heartbeat)
|
||||||
async def on_heartbeat_request(self):
|
async def on_heartbeat_request(self):
|
||||||
with SessionLocal() as db:
|
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_chargepoint.last_seen = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
return call_result.HeartbeatPayload(
|
return call_result.HeartbeatPayload(
|
||||||
|
@ -39,7 +43,7 @@ class ChargePoint(cp):
|
||||||
@on(Action.StatusNotification)
|
@on(Action.StatusNotification)
|
||||||
async def on_status_notification(self, evse_id: int, connector_id: int, connector_status: str, **kwargs):
|
async def on_status_notification(self, evse_id: int, connector_id: int, connector_status: str, **kwargs):
|
||||||
with SessionLocal() as db:
|
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_chargepoint.last_seen = datetime.now(UTC)
|
||||||
|
|
||||||
db_connector = db.query(Connector).filter(
|
db_connector = db.query(Connector).filter(
|
||||||
|
@ -64,24 +68,38 @@ class ChargePoint(cp):
|
||||||
|
|
||||||
@on(Action.Authorize)
|
@on(Action.Authorize)
|
||||||
async def on_authorize(self, id_token, **kwargs):
|
async def on_authorize(self, id_token, **kwargs):
|
||||||
if id_token == None:
|
if id_token["type"] not in ["ISO14443", "ISO15693"]:
|
||||||
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.invalid})
|
return call_result.AuthorizePayload(
|
||||||
if id_token.type != "ISO14443" | "ISO15693":
|
id_token_info=IdTokenInfoType(
|
||||||
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.invalid})
|
status=AuthorizationStatusType.invalid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
with SessionLocal() as db:
|
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_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()
|
db.commit()
|
||||||
|
|
||||||
if db_id_token == None:
|
if db_id_token == None:
|
||||||
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.unknown})
|
id_token_info=IdTokenInfoType(
|
||||||
|
status=AuthorizationStatusType.unknown
|
||||||
|
)
|
||||||
|
else:
|
||||||
if db_id_token.is_active == False:
|
if db_id_token.is_active == False:
|
||||||
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.blocked})
|
id_token_info=IdTokenInfoType(
|
||||||
|
status=AuthorizationStatusType.blocked
|
||||||
return call_result.AuthorizePayload(id_token_info={'status': AuthorizationStatusType.accepted, 'groupIdToken': str(db_id_token.owner_id)})
|
)
|
||||||
|
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)
|
@on(Action.TransactionEvent)
|
||||||
async def on_transaction_event(self):
|
async def on_transaction_event(self):
|
||||||
|
|
|
@ -12,8 +12,8 @@ from app.models.chargepoint import ChargePoint as DbChargePoint
|
||||||
from app.security import get_api_key
|
from app.security import get_api_key
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/chargepoint",
|
prefix="/chargepoints",
|
||||||
tags=["chargepoint (v1)"],
|
tags=["Chargepoint (v1)"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get(path="", response_model=list[ChargePoint])
|
@router.get(path="", response_model=list[ChargePoint])
|
||||||
|
|
96
app/routers/id_token_v1.py
Normal file
96
app/routers/id_token_v1.py
Normal 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 []
|
|
@ -9,8 +9,8 @@ from app.models.user import User as DbUser
|
||||||
from app.security import get_api_key
|
from app.security import get_api_key
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/user",
|
prefix="/users",
|
||||||
tags=["user (v1)"],
|
tags=["User (v1)"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get(path="", response_model=list[User])
|
@router.get(path="", response_model=list[User])
|
||||||
|
|
|
@ -19,6 +19,10 @@ class ChargePointCreate(ChargePointBase):
|
||||||
class ChargePoint(ChargePointBase):
|
class ChargePoint(ChargePointBase):
|
||||||
id: UUID
|
id: UUID
|
||||||
last_seen: datetime | None
|
last_seen: datetime | None
|
||||||
|
vendor_name: str | None
|
||||||
|
model: str | None
|
||||||
|
serial_number: str | None
|
||||||
|
firmware_version: str | None
|
||||||
connectors: list[Connector] = []
|
connectors: list[Connector] = []
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
|
from typing import Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from app.schemas.user import User
|
from app.schemas.user import User
|
||||||
|
|
||||||
class IdTokenBase(BaseModel):
|
class IdTokenBase(BaseModel):
|
||||||
title: str
|
friendly_name: str
|
||||||
is_active: bool
|
is_active: bool
|
||||||
|
owner_id: UUID
|
||||||
|
token: str
|
||||||
|
|
||||||
class IdTokenCreate(IdTokenBase):
|
class IdTokenCreate(IdTokenBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class IdTokenUpdate(BaseModel):
|
||||||
|
friendly_name: Optional[str] = None
|
||||||
|
is_active: Optional[bool] = None
|
||||||
|
owner_id: Optional[UUID] = None
|
||||||
|
|
||||||
class IdToken(IdTokenBase):
|
class IdToken(IdTokenBase):
|
||||||
id: UUID
|
id: UUID
|
||||||
owner: User
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
Loading…
Reference in a new issue