diff --git a/pkgs/clan-cli/clan_cli/webui/routers/endpoints.py b/pkgs/clan-cli/clan_cli/webui/routers/endpoints.py index 6a16fda..e8d90fe 100644 --- a/pkgs/clan-cli/clan_cli/webui/routers/endpoints.py +++ b/pkgs/clan-cli/clan_cli/webui/routers/endpoints.py @@ -4,7 +4,7 @@ from datetime import datetime from typing import List, Optional import httpx -from fastapi import APIRouter, BackgroundTasks, Depends +from fastapi import APIRouter, BackgroundTasks, Depends, Query from sqlalchemy.orm import Session from ...errors import ClanError @@ -15,6 +15,7 @@ from ..schemas import ( Eventmessage, EventmessageCreate, Resolution, + Role, Service, ServiceCreate, ) @@ -81,23 +82,6 @@ def delete_service( return {"message": "service deleted"} -######################### -# # -# REPOSITORY # -# # -######################### -@router.get( - "/api/v1/repositories", - response_model=List[Service], - tags=[Tags.repositories], -) -def get_all_repositories( - skip: int = 0, limit: int = 100, db: Session = Depends(sql_db.get_db) -) -> List[sql_models.Service]: - repositories = sql_crud.get_services(db, skip=skip, limit=limit) - return repositories - - ######################### # # # Entity # @@ -106,17 +90,29 @@ def get_all_repositories( @router.post("/api/v1/entity", response_model=Entity, tags=[Tags.entities]) def create_entity( entity: EntityCreate, db: Session = Depends(sql_db.get_db) -) -> EntityCreate: +) -> sql_models.Entity: return sql_crud.create_entity(db, entity) @router.get( - "/api/v1/entity_by_name", response_model=Optional[Entity], tags=[Tags.entities] + "/api/v1/entity_by_name_or_did", + response_model=Optional[Entity], + tags=[Tags.entities], ) -def get_entity_by_name( - entity_name: str, db: Session = Depends(sql_db.get_db) +def get_entity_by_name_or_did( + entity_name_or_did: str = "C1", db: Session = Depends(sql_db.get_db) ) -> Optional[sql_models.Entity]: - entity = sql_crud.get_entity_by_name(db, name=entity_name) + entity = sql_crud.get_entity_by_name_or_did(db, name=entity_name_or_did) + return entity + + +@router.get( + "/api/v1/entity_by_roles", response_model=List[Entity], tags=[Tags.entities] +) +def get_entity_by_roles( + roles: List[Role] = Query(...), db: Session = Depends(sql_db.get_db) +) -> List[sql_models.Entity]: + entity = sql_crud.get_entity_by_role(db, roles=roles) return entity @@ -149,7 +145,7 @@ def get_attached_entities( return entities -@router.post("/api/v1/detach", tags=[Tags.entities]) +@router.put("/api/v1/detach", tags=[Tags.entities]) def detach_entity( background_tasks: BackgroundTasks, entity_did: str = "did:sov:test:1234", @@ -164,7 +160,7 @@ def detach_entity( return {"message": f"Detached {entity_did} successfully"} -@router.post("/api/v1/attach", tags=[Tags.entities]) +@router.put("/api/v1/attach", tags=[Tags.entities]) def attach_entity( background_tasks: BackgroundTasks, entity_did: str = "did:sov:test:1234", @@ -175,7 +171,7 @@ def attach_entity( entity = sql_crud.get_entity_by_did(db, did=entity_did) if entity is None: raise ClanError(f"Entity with did '{entity_did}' not found") - url = f"http://{entity.ip}" + url = f"http://{entity.ip}/health" sql_crud.set_stop_health_task(db, entity_did, False) print("Start health query at", url) background_tasks.add_task(attach_entity_loc, db, entity_did) @@ -209,7 +205,7 @@ def attach_entity_loc(db: Session, entity_did: str) -> None: entity = sql_crud.get_entity_by_did(db, did=entity_did) try: assert entity is not None - url = f"http://{entity.ip}" + url = f"http://{entity.ip}/health" while entity.stop_health_task is False: response = httpx.get(url, timeout=2) @@ -273,7 +269,9 @@ async def get_all_resolutions( ######################### -@router.post("/api/v1/send_msg", response_model=Eventmessage, tags=[Tags.eventmessages]) +@router.post( + "/api/v1/event_message", response_model=Eventmessage, tags=[Tags.eventmessages] +) async def create_eventmessage( eventmsg: EventmessageCreate, db: Session = Depends(sql_db.get_db) ) -> EventmessageCreate: diff --git a/pkgs/clan-cli/clan_cli/webui/schemas.py b/pkgs/clan-cli/clan_cli/webui/schemas.py index ea2e3e3..169a560 100644 --- a/pkgs/clan-cli/clan_cli/webui/schemas.py +++ b/pkgs/clan-cli/clan_cli/webui/schemas.py @@ -1,8 +1,11 @@ +import logging from datetime import datetime from enum import Enum from typing import List -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, validator + +log = logging.getLogger(__name__) class Status(Enum): @@ -11,7 +14,7 @@ class Status(Enum): UNKNOWN = "unknown" -class Roles(Enum): +class Role(Enum): PROSUMER = "service_prosumer" AP = "AP" DLG = "DLG" @@ -27,35 +30,51 @@ class Machine(BaseModel): # Entity # # # ######################### +class EntityRolesBase(BaseModel): + role: Role = Field(..., example=Role("service_prosumer")) + + +class EntityRolesCreate(EntityRolesBase): + id: int = Field(...) + entity_did: str = Field(...) + + +class EntityRoles(EntityRolesBase): + class Config: + orm_mode = True + + class EntityBase(BaseModel): did: str = Field(..., example="did:sov:test:1234") name: str = Field(..., example="C1") ip: str = Field(..., example="127.0.0.1") network: str = Field(..., example="255.255.0.0") - role: Roles = Field( - ..., example=Roles("service_prosumer") - ) # roles are needed for UI to show the correct view visible: bool = Field(..., example=True) other: dict = Field( ..., example={ "network": "Carlos Home Network", - "roles": ["service repository", "service prosumer"], }, ) class EntityCreate(EntityBase): - pass + roles: List[Role] = Field(..., example=[Role("service_prosumer"), Role("AP")]) -class Entity(EntityCreate): +class Entity(EntityBase): attached: bool = Field(...) stop_health_task: bool = Field(...) + roles: List[Role] class Config: orm_mode = True + # define a custom getter function for roles + @validator("roles", pre=True) + def get_roles(cls, v: List[EntityRoles]) -> List[Role]: + return [x.role for x in v] + ######################### # # @@ -122,7 +141,6 @@ class Resolution(ResolutionCreate): # # ######################### class EventmessageBase(BaseModel): - id: int = Field(..., example=123456) timestamp: int = Field(..., example=1234123413) group: int = Field(..., example=1) # event group type (for the label) group_id: int = Field( @@ -135,9 +153,10 @@ class EventmessageBase(BaseModel): class EventmessageCreate(EventmessageBase): msg: dict = Field(..., example={"optinal": "values"}) # optional - pass class Eventmessage(EventmessageCreate): + id: int = Field(...) + class Config: orm_mode = True diff --git a/pkgs/clan-cli/clan_cli/webui/sql_crud.py b/pkgs/clan-cli/clan_cli/webui/sql_crud.py index 64b968c..d8186e0 100644 --- a/pkgs/clan-cli/clan_cli/webui/sql_crud.py +++ b/pkgs/clan-cli/clan_cli/webui/sql_crud.py @@ -65,8 +65,21 @@ def get_services_without_entity_id( ######################### def create_entity(db: Session, entity: schemas.EntityCreate) -> sql_models.Entity: db_entity = sql_models.Entity( - **entity.dict(), attached=False, stop_health_task=False + did=entity.did, + name=entity.name, + ip=entity.ip, + network=entity.network, + visible=entity.visible, + other=entity.other, + attached=False, + stop_health_task=False, ) + + db_roles = [] + for role in entity.roles: + db_roles.append(sql_models.EntityRoles(role=role)) + + db_entity.roles = db_roles db.add(db_entity) db.commit() db.refresh(db_entity) @@ -83,8 +96,22 @@ def get_entity_by_did(db: Session, did: str) -> Optional[sql_models.Entity]: return db.query(sql_models.Entity).filter(sql_models.Entity.did == did).first() -def get_entity_by_name(db: Session, name: str) -> Optional[sql_models.Entity]: - return db.query(sql_models.Entity).filter(sql_models.Entity.name == name).first() +def get_entity_by_name_or_did(db: Session, name: str) -> Optional[sql_models.Entity]: + return ( + db.query(sql_models.Entity) + .filter((sql_models.Entity.name == name) | (sql_models.Entity.did == name)) + .first() + ) + + +def get_entity_by_role( + db: Session, roles: List[schemas.Role] +) -> List[sql_models.Entity]: + return ( + db.query(sql_models.Entity) + .filter(sql_models.Entity.roles.any(sql_models.EntityRoles.role.in_(roles))) + .all() + ) # get attached @@ -107,12 +134,11 @@ def set_stop_health_task(db: Session, entity_did: str, value: bool) -> None: if db_entity is None: raise ClanError(f"Entity with did '{entity_did}' not found") - setattr(db_entity, "stop_health_task", value) + db_entity.stop_health_task = value # type: ignore # save changes in db db.add(db_entity) db.commit() - db.refresh(db_entity) def set_attached_by_entity_did(db: Session, entity_did: str, attached: bool) -> None: @@ -120,12 +146,11 @@ def set_attached_by_entity_did(db: Session, entity_did: str, attached: bool) -> if db_entity is None: raise ClanError(f"Entity with did '{entity_did}' not found") - setattr(db_entity, "attached", attached) + db_entity.attached = attached # type: ignore # save changes in db db.add(db_entity) db.commit() - db.refresh(db_entity) def delete_entity_by_did(db: Session, did: str) -> None: @@ -148,7 +173,15 @@ def delete_entity_by_did_recursive(db: Session, did: str) -> None: def create_eventmessage( db: Session, eventmsg: schemas.EventmessageCreate ) -> sql_models.Eventmessage: - db_eventmessage = sql_models.Eventmessage(**eventmsg.dict()) + db_eventmessage = sql_models.Eventmessage( + timestamp=eventmsg.timestamp, + group=eventmsg.group, + group_id=eventmsg.group_id, + msg_type=eventmsg.msg_type, + src_did=eventmsg.src_did, + des_did=eventmsg.des_did, + msg=eventmsg.msg, + ) db.add(db_eventmessage) db.commit() db.refresh(db_eventmessage) diff --git a/pkgs/clan-cli/clan_cli/webui/sql_models.py b/pkgs/clan-cli/clan_cli/webui/sql_models.py index d7c4f2a..d83918d 100644 --- a/pkgs/clan-cli/clan_cli/webui/sql_models.py +++ b/pkgs/clan-cli/clan_cli/webui/sql_models.py @@ -1,7 +1,7 @@ from sqlalchemy import JSON, Boolean, Column, Enum, ForeignKey, Integer, String, Text from sqlalchemy.orm import relationship -from .schemas import Roles +from .schemas import Role from .sql_db import Base # Relationsship example @@ -16,8 +16,6 @@ class Entity(Base): name = Column(String, index=True, unique=True) ip = Column(String, index=True) network = Column(String, index=True) - role = Column(Enum(Roles), index=True, nullable=False) # type: ignore - # role = Column(String, index=True, nullable=False) attached = Column(Boolean, index=True) visible = Column(Boolean, index=True) stop_health_task = Column(Boolean) @@ -28,6 +26,19 @@ class Entity(Base): ## Relations ## services = relationship("Service", back_populates="entity") + roles = relationship("EntityRoles", back_populates="entity") + + +class EntityRoles(Base): + __tablename__ = "entity_roles" + + ## Queryable body ## + id = Column(Integer, primary_key=True, autoincrement=True) + entity_did = Column(String, ForeignKey("entities.did")) + role = Column(Enum(Role), index=True, nullable=False) # type: ignore + + ## Relations ## + entity = relationship("Entity", back_populates="roles") class ServiceAbstract(Base): @@ -58,7 +69,7 @@ class Eventmessage(Base): __tablename__ = "eventmessages" ## Queryable body ## - id = Column(Integer, primary_key=True, index=True) + id = Column(Integer, primary_key=True, autoincrement=True) timestamp = Column(Integer, unique=True, index=True) group = Column(Integer, index=True) group_id = Column(Integer, index=True) diff --git a/pkgs/clan-cli/clan_cli/webui/tags.py b/pkgs/clan-cli/clan_cli/webui/tags.py index 3d52367..619b1f0 100644 --- a/pkgs/clan-cli/clan_cli/webui/tags.py +++ b/pkgs/clan-cli/clan_cli/webui/tags.py @@ -22,10 +22,6 @@ tags_metadata: List[Dict[str, Any]] = [ "name": str(Tags.entities), "description": "Operations on an entity.", }, - { - "name": str(Tags.repositories), - "description": "Operations on a repository.", - }, { "name": str(Tags.resolutions), "description": "Operations on a resolution.", diff --git a/pkgs/clan-cli/pyproject.toml b/pkgs/clan-cli/pyproject.toml index adbf1ca..547a4e3 100644 --- a/pkgs/clan-cli/pyproject.toml +++ b/pkgs/clan-cli/pyproject.toml @@ -68,7 +68,7 @@ ignore_missing_imports = true [tool.ruff] line-length = 88 select = [ "E", "F", "I", "N"] -ignore = [ "E501" ] +ignore = [ "E501", "N805" ] exclude = ["tests/openapi_client"] [tool.black] diff --git a/pkgs/clan-cli/tests/test_db_api.py b/pkgs/clan-cli/tests/test_db_api.py index 5cf4324..3238d40 100644 --- a/pkgs/clan-cli/tests/test_db_api.py +++ b/pkgs/clan-cli/tests/test_db_api.py @@ -38,7 +38,7 @@ def test_health(api_client: ApiClient) -> None: assert res.status == Status.ONLINE -def create_entities(num: int = 10) -> list[EntityCreate]: +def create_entities(num: int = 10, role: str = "entity") -> list[EntityCreate]: res = [] for i in range(num): en = EntityCreate( @@ -90,6 +90,7 @@ def create_service(idx: int, entity: Entity) -> ServiceCreate: def test_create_entities(api_client: ApiClient) -> None: api = EntitiesApi(api_client=api_client) + for own_entity in create_entities(): res: Entity = api.create_entity(own_entity) assert res.did == own_entity.did