From a0924b33603663cffb84b6209f07816461d4f383 Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Fri, 24 Jan 2025 15:35:11 +0100 Subject: [PATCH] feat(sessions): added session router --- .../bec_atlas/datasources/mongodb/mongodb.py | 14 ++- backend/bec_atlas/main.py | 22 ++++- backend/bec_atlas/model/model.py | 19 ++-- backend/bec_atlas/router/bec_access_router.py | 2 +- .../router/deployment_access_router.py | 4 +- backend/bec_atlas/router/scan_router.py | 29 +++++- backend/bec_atlas/router/session_router.py | 99 +++++++++++++++++++ 7 files changed, 168 insertions(+), 21 deletions(-) create mode 100644 backend/bec_atlas/router/session_router.py diff --git a/backend/bec_atlas/datasources/mongodb/mongodb.py b/backend/bec_atlas/datasources/mongodb/mongodb.py index 6c09cda..ea23a3a 100644 --- a/backend/bec_atlas/datasources/mongodb/mongodb.py +++ b/backend/bec_atlas/datasources/mongodb/mongodb.py @@ -114,7 +114,12 @@ class MongoDBDatasource: return UserCredentials(**out) def find_one( - self, collection: str, query_filter: dict, dtype: Type[T], user: User | None = None + self, + collection: str, + query_filter: dict, + dtype: Type[T], + fields: list[str] = None, + user: User | None = None, ) -> T | None: """ Find one document in the collection. @@ -130,7 +135,7 @@ class MongoDBDatasource: """ if user is not None: query_filter = self.add_user_filter(user, query_filter) - out = self.db[collection].find_one(query_filter) + out = self.db[collection].find_one(query_filter, projection=fields) if out is None: return None return dtype(**out) @@ -143,6 +148,7 @@ class MongoDBDatasource: limit: int = 0, offset: int = 0, fields: list[str] = None, + sort: list[str] = None, user: User | None = None, ) -> list[T]: """ @@ -159,7 +165,9 @@ class MongoDBDatasource: """ if user is not None: query_filter = self.add_user_filter(user, query_filter) - out = self.db[collection].find(query_filter, limit=limit, skip=offset, projection=fields) + out = self.db[collection].find( + query_filter, limit=limit, skip=offset, projection=fields, sort=sort + ) return [dtype(**x) for x in out] def post(self, collection: str, data: dict, dtype: Type[T], user: User | None = None) -> T: diff --git a/backend/bec_atlas/main.py b/backend/bec_atlas/main.py index f32b8a2..2823ce7 100644 --- a/backend/bec_atlas/main.py +++ b/backend/bec_atlas/main.py @@ -10,6 +10,7 @@ from bec_atlas.router.deployments_router import DeploymentsRouter from bec_atlas.router.realm_router import RealmRouter from bec_atlas.router.redis_router import RedisRouter, RedisWebsocket from bec_atlas.router.scan_router import ScanRouter +from bec_atlas.router.session_router import SessionRouter from bec_atlas.router.user_router import UserRouter CONFIG = { @@ -54,15 +55,20 @@ class AtlasApp: # pylint: disable=attribute-defined-outside-init if not self.datasources.datasources: raise ValueError("Datasources not loaded") - self.scan_router = ScanRouter(prefix=self.prefix, datasources=self.datasources) - self.app.include_router(self.scan_router.router, tags=["Scan"]) + # User self.user_router = UserRouter(prefix=self.prefix, datasources=self.datasources) self.app.include_router(self.user_router.router, tags=["User"]) + # Realm + self.realm_router = RealmRouter(prefix=self.prefix, datasources=self.datasources) + self.app.include_router(self.realm_router.router, tags=["Realm"]) + + # Deployment self.deployment_router = DeploymentsRouter(prefix=self.prefix, datasources=self.datasources) self.app.include_router(self.deployment_router.router, tags=["Deployment"]) + # Deployment Credentials self.deployment_credentials_router = DeploymentCredentialsRouter( prefix=self.prefix, datasources=self.datasources ) @@ -70,17 +76,25 @@ class AtlasApp: self.deployment_credentials_router.router, tags=["Deployment Credentials"] ) + # Deployment Access self.deployment_access_router = DeploymentAccessRouter( prefix=self.prefix, datasources=self.datasources ) self.app.include_router(self.deployment_access_router.router, tags=["Deployment Access"]) + # BEC Access self.bec_access_router = BECAccessRouter(prefix=self.prefix, datasources=self.datasources) self.app.include_router(self.bec_access_router.router, tags=["BEC Access"]) - self.realm_router = RealmRouter(prefix=self.prefix, datasources=self.datasources) - self.app.include_router(self.realm_router.router, tags=["Realm"]) + # Session + self.session_router = SessionRouter(prefix=self.prefix, datasources=self.datasources) + self.app.include_router(self.session_router.router, tags=["Session"]) + # Scan + self.scan_router = ScanRouter(prefix=self.prefix, datasources=self.datasources) + self.app.include_router(self.scan_router.router, tags=["Scan"]) + + # Redis self.redis_router = RedisRouter(prefix=self.prefix, datasources=self.datasources) self.app.include_router(self.redis_router.router, tags=["Redis"]) diff --git a/backend/bec_atlas/model/model.py b/backend/bec_atlas/model/model.py index ead756e..5bd6007 100644 --- a/backend/bec_atlas/model/model.py +++ b/backend/bec_atlas/model/model.py @@ -70,14 +70,14 @@ class UserInfo(BaseModel): class Deployments(MongoBaseModel, AccessProfile): - realm_id: str | ObjectId + realm_id: str name: str active_session_id: str | ObjectId | None = None config_templates: list[str | ObjectId] = [] class DeploymentsPartial(MongoBaseModel, AccessProfilePartial): - realm_id: str | ObjectId | None = None + realm_id: str | None = None name: str | None = None active_session_id: str | ObjectId | None = None config_templates: list[str | ObjectId] | None = None @@ -120,7 +120,7 @@ class BECAccessProfile(MongoBaseModel, AccessProfile): """ - deployment_id: str | ObjectId + deployment_id: str username: str passwords: dict[str, str] = {} categories: list[str] = [] @@ -167,10 +167,13 @@ class State(AccessProfile): class Session(MongoBaseModel, AccessProfile): - deployment_id: str | ObjectId + deployment_id: str name: str +SessionPartial = make_all_fields_optional(Session, "SessionPartial") + + class Datasets(AccessProfile): realm_id: str dataset_id: str @@ -214,8 +217,8 @@ class SignalData(AccessProfile, MongoBaseModel): min, and max values for the signal. """ - scan_id: str | ObjectId | None = None - device_id: str | ObjectId + scan_id: str | None = None + device_id: str signal_name: str data: list[Any] timestamps: list[float] @@ -227,9 +230,9 @@ class SignalData(AccessProfile, MongoBaseModel): class DeviceData(AccessProfile, MongoBaseModel): - scan_id: str | ObjectId | None = None + scan_id: str | None = None name: str - device_config_id: str | ObjectId + device_config_id: str signals: list[SignalData] diff --git a/backend/bec_atlas/router/bec_access_router.py b/backend/bec_atlas/router/bec_access_router.py index ca10d06..3dae769 100644 --- a/backend/bec_atlas/router/bec_access_router.py +++ b/backend/bec_atlas/router/bec_access_router.py @@ -38,7 +38,7 @@ class BECAccessRouter(BaseRouter): user = current_user.email out = self.db.find_one( "bec_access_profiles", - {"deployment_id": ObjectId(deployment_id), "username": user}, + {"deployment_id": deployment_id, "username": user}, BECAccessProfile, user=current_user, ) diff --git a/backend/bec_atlas/router/deployment_access_router.py b/backend/bec_atlas/router/deployment_access_router.py index 0ad53c4..65be432 100644 --- a/backend/bec_atlas/router/deployment_access_router.py +++ b/backend/bec_atlas/router/deployment_access_router.py @@ -153,7 +153,9 @@ class DeploymentAccessRouter(BaseRouter): redis: RedisDatasource = self.datasources.datasources.get("redis") db: MongoDBDatasource = self.datasources.datasources.get("mongodb") profiles = db.find( - "bec_access_profiles", {"deployment_id": ObjectId(deployment_id)}, BECAccessProfile + collection="bec_access_profiles", + query_filter={"deployment_id": deployment_id}, + dtype=BECAccessProfile, ) profiles = [profile.model_dump(exclude_none=True) for profile in profiles] for profile in profiles: diff --git a/backend/bec_atlas/router/scan_router.py b/backend/bec_atlas/router/scan_router.py index e313992..aa65223 100644 --- a/backend/bec_atlas/router/scan_router.py +++ b/backend/bec_atlas/router/scan_router.py @@ -44,6 +44,7 @@ class ScanRouter(BaseRouter): fields: list[str] = Query(default=None), offset: int = 0, limit: int = 100, + sort: str | None = None, current_user: UserInfo = Depends(get_current_user), ) -> list[ScanStatusPartial]: """ @@ -51,6 +52,17 @@ class ScanRouter(BaseRouter): Args: session_id (str): The session id + filter (str): JSON filter for the query, e.g. '{"name": "test"}' + fields (list[str]): List of fields to return, e.g ["name", "description"] + offset (int): Offset for the query + limit (int): Limit for the query + sort (str): Sort order for the query, e.g. '{"name": 1}' for ascending order, + '{"name": -1}' for descending order. Multiple fields can be sorted by + separating them with a comma, e.g. '{"name": 1, "description": -1}' + current_user (UserInfo): The current user + + Returns: + list[ScanStatusPartial]: List of scans """ if fields: @@ -72,16 +84,14 @@ class ScanRouter(BaseRouter): limit=limit, offset=offset, fields=fields, + sort=sort, user=current_user, ) async def scans_with_id( self, scan_id: str, - filter: str | None = None, fields: list[str] = Query(default=None), - offset: int = 0, - limit: int = 100, current_user: UserInfo = Depends(get_current_user), ): """ @@ -90,7 +100,18 @@ class ScanRouter(BaseRouter): Args: scan_id (str): The scan id """ - return self.db.find_one("scans", {"_id": scan_id}, ScanStatusPartial, user=current_user) + if fields: + fields = { + field: 1 + for field in fields + if field in ScanStatusPartial.model_json_schema()["properties"].keys() + } + return self.db.find_one( + collection="scans", + query_filter={"_id": scan_id}, + dtype=ScanStatusPartial, + user=current_user, + ) async def update_scan_user_data( self, diff --git a/backend/bec_atlas/router/session_router.py b/backend/bec_atlas/router/session_router.py new file mode 100644 index 0000000..e2711eb --- /dev/null +++ b/backend/bec_atlas/router/session_router.py @@ -0,0 +1,99 @@ +import json +from typing import Annotated + +from fastapi import APIRouter, Depends, Query +from fastapi.exceptions import HTTPException +from fastapi.security import OAuth2PasswordRequestForm +from pydantic import BaseModel + +from bec_atlas.authentication import create_access_token, get_current_user, verify_password +from bec_atlas.datasources.mongodb.mongodb import MongoDBDatasource +from bec_atlas.model import UserInfo +from bec_atlas.model.model import Session +from bec_atlas.router.base_router import BaseRouter + + +class SessionRouter(BaseRouter): + def __init__(self, prefix="/api/v1", datasources=None): + super().__init__(prefix, datasources) + self.db: MongoDBDatasource = self.datasources.datasources.get("mongodb") + self.router = APIRouter(prefix=prefix) + self.router.add_api_route( + "/sessions", + self.sessions, + methods=["GET"], + description="Get all sessions", + response_model=list[Session], + response_model_exclude_none=True, + ) + self.router.add_api_route( + "/sessions/realm", + self.sessions_by_realm, + methods=["GET"], + description="Get all sessions for a realm", + response_model=list[Session], + response_model_exclude_none=True, + ) + + async def sessions( + self, + filter: str | None = None, + fields: list[str] = Query(default=None), + offset: int = 0, + limit: int = 100, + sort: str | None = None, + current_user: UserInfo = Depends(get_current_user), + ) -> list[Session]: + """ + Get all sessions. + + Args: + filter (str): JSON filter for the query, e.g. '{"name": "test"}' + fields (list[str]): List of fields to return, e.g ["name", "description"] + offset (int): Offset for the query + limit (int): Limit for the query + sort (str): Sort order for the query, e.g. '{"name": 1}' for ascending order, + '{"name": -1}' for descending order. Multiple fields can be sorted by + separating them with a comma, e.g. '{"name": 1, "description": -1}' + current_user (UserInfo): The current user + + Returns: + list[Sessions]: List of sessions + + """ + if fields: + fields = { + field: 1 + for field in fields + if field in Session.model_json_schema()["properties"].keys() + } + return self.db.find( + "sessions", + filter, + Session, + fields=fields, + offset=offset, + limit=limit, + sort=sort, + user=current_user, + ) + + async def sessions_by_realm( + self, + realm_id: str, + filter: str | None = None, + fields: list[str] = Query(default=None), + offset: int = 0, + limit: int = 100, + sort: str | None = None, + current_user: UserInfo = Depends(get_current_user), + ) -> list[Session]: + """ + Get all sessions for a realm. + """ + filters = {"realm_id": realm_id} + if filter: + filter = json.loads(filter) + filters.update(filter) + out = await self.sessions(filter, fields, offset, limit, sort, current_user) + return out