From be420adc49e9b33476ab029450aaefe83ded4ae8 Mon Sep 17 00:00:00 2001 From: t0xa Date: Tue, 2 Sep 2025 14:21:45 +0300 Subject: [PATCH] ivideon: Add some schemes for crowd service --- ivideon/puml/Crowd/cl_analyzer_1.puml | 0 ivideon/puml/Crowd/cl_analyzer_2.puml | 0 ivideon/puml/Crowd/cl_crowd_entity.puml | 0 ivideon/puml/Crowd/cl_entites_structure.puml | 0 ivideon/puml/Crowd/cl_folder.puml | 540 +++++++++++++++++++ ivideon/puml/Crowd/cl_get_all_cameras | 326 +++++++++++ ivideon/puml/Crowd/entities.puml | 58 ++ 7 files changed, 924 insertions(+) create mode 100644 ivideon/puml/Crowd/cl_analyzer_1.puml create mode 100644 ivideon/puml/Crowd/cl_analyzer_2.puml create mode 100644 ivideon/puml/Crowd/cl_crowd_entity.puml create mode 100644 ivideon/puml/Crowd/cl_entites_structure.puml create mode 100644 ivideon/puml/Crowd/cl_folder.puml create mode 100644 ivideon/puml/Crowd/cl_get_all_cameras create mode 100644 ivideon/puml/Crowd/entities.puml diff --git a/ivideon/puml/Crowd/cl_analyzer_1.puml b/ivideon/puml/Crowd/cl_analyzer_1.puml new file mode 100644 index 0000000..e69de29 diff --git a/ivideon/puml/Crowd/cl_analyzer_2.puml b/ivideon/puml/Crowd/cl_analyzer_2.puml new file mode 100644 index 0000000..e69de29 diff --git a/ivideon/puml/Crowd/cl_crowd_entity.puml b/ivideon/puml/Crowd/cl_crowd_entity.puml new file mode 100644 index 0000000..e69de29 diff --git a/ivideon/puml/Crowd/cl_entites_structure.puml b/ivideon/puml/Crowd/cl_entites_structure.puml new file mode 100644 index 0000000..e69de29 diff --git a/ivideon/puml/Crowd/cl_folder.puml b/ivideon/puml/Crowd/cl_folder.puml new file mode 100644 index 0000000..34f6dab --- /dev/null +++ b/ivideon/puml/Crowd/cl_folder.puml @@ -0,0 +1,540 @@ +@startuml Folder System Architecture + + title Система папок/групп в Ivideon + + package "Database" @startuml FolderSystemSimple + + title Система папок в Ivideon + + entity "folders" as folders_db { + * _id : ObjectId + -- + * owner_id : string + * name : string + * parents : array + * objects : array + * root : boolean + } + + entity "permission_grants" as grants_db { + * _id : ObjectId + -- + * object_id : string + * object_type : string + * grantee_id : string + * permissions : array + } + + entity "servers" as servers_db { + * _id : ObjectId + -- + * owner_id : string + * cameras : object + } + + entity "Folder" as folder_class { + + get_objects(type) + + add_object(obj) + + remove_object(obj) + + has_permissions(perm) + } + + entity "FolderTree" as tree_class { + + folders : dict + + find_folders() + + reload() + } + + entity "Camera" as camera_node { + + id : "server:index" + + object_type : "camera" + } + + folders_db ||--o{ folder_class + grants_db ||--o{ folder_class + servers_db ||--o{ camera_node + + tree_class --> folder_class : manages + folder_class --> camera_node : contains + + note right of folders_db + objects[] format: + [ + {object_type: "camera", + object_id: "server:0"}, + {object_type: "folder", + object_id: "subfolder_id"} + ] + end note + + note bottom of tree_class + Usage: + tree = FolderTree(user_id) + folder = tree.folders[folder_id] + cameras = folder.get_objects("camera") + end note + + @enduml +@startuml FolderSystemSimple + + title Система папок в Ivideon + + entity "folders" as folders_db { + * _id : ObjectId + -- + * owner_id : string + * name : string + * parents : array + * objects : array + * root : boolean + } + + entity "permission_grants" as grants_db { + * _id : ObjectId + -- + * object_id : string + * object_type : string + * grantee_id : string + * permissions : array + } + + entity "servers" as servers_db { + * _id : ObjectId + -- + * owner_id : string + * cameras : object + } + + entity "Folder" as folder_class { + + get_objects(type) + + add_object(obj) + + remove_object(obj) + + has_permissions(perm) + } + + entity "FolderTree" as tree_class { + + folders : dict + + find_folders() + + reload() + } + + entity "Camera" as camera_node { + + id : "server:index" + + object_type : "camera" + } + + folders_db ||--o{ folder_class + grants_db ||--o{ folder_class + servers_db ||--o{ camera_node + + tree_class --> folder_class : manages + folder_class --> camera_node : contains + + note right of folders_db + objects[] format: + [ + {object_type: "camera", + object_id: "server:0"}, + {object_type: "folder", + object_id: "subfolder_id"} + ] + end note + + note bottom of tree_class + Usage: + tree = FolderTree(user_id) + folder = tree.folders[folder_id] + cameras = folder.get_objects("camera") + end note + + @enduml +@startuml FolderSystemSimple + + title Система папок в Ivideon + + entity "folders" as folders_db { + * _id : ObjectId + -- + * owner_id : string + * name : string + * parents : array + * objects : array + * root : boolean + } + + entity "permission_grants" as grants_db { + * _id : ObjectId + -- + * object_id : string + * object_type : string + * grantee_id : string + * permissions : array + } + + entity "servers" as servers_db { + * _id : ObjectId + -- + * owner_id : string + * cameras : object + } + + entity "Folder" as folder_class { + + get_objects(type) + + add_object(obj) + + remove_object(obj) + + has_permissions(perm) + } + + entity "FolderTree" as tree_class { + + folders : dict + + find_folders() + + reload() + } + + entity "Camera" as camera_node { + + id : "server:index" + + object_type : "camera" + } + + folders_db ||--o{ folder_class + grants_db ||--o{ folder_class + servers_db ||--o{ camera_node + + tree_class --> folder_class : manages + folder_class --> camera_node : contains + + note right of folders_db + objects[] format: + [ + {object_type: "camera", + object_id: "server:0"}, + {object_type: "folder", + object_id: "subfolder_id"} + ] + end note + + note bottom of tree_class + Usage: + tree = FolderTree(user_id) + folder = tree.folders[folder_id] + cameras = folder.get_objects("camera") + end note + + @enduml +@startuml FolderSystemSimple + + title Система папок в Ivideon + + entity "folders" as folders_db { + * _id : ObjectId + -- + * owner_id : string + * name : string + * parents : array + * objects : array + * root : boolean + } + + entity "permission_grants" as grants_db { + * _id : ObjectId + -- + * object_id : string + * object_type : string + * grantee_id : string + * permissions : array + } + + entity "servers" as servers_db { + * _id : ObjectId + -- + * owner_id : string + * cameras : object + } + + entity "Folder" as folder_class { + + get_objects(type) + + add_object(obj) + + remove_object(obj) + + has_permissions(perm) + } + + entity "FolderTree" as tree_class { + + folders : dict + + find_folders() + + reload() + } + + entity "Camera" as camera_node { + + id : "server:index" + + object_type : "camera" + } + + folders_db ||--o{ folder_class + grants_db ||--o{ folder_class + servers_db ||--o{ camera_node + + tree_class --> folder_class : manages + folder_class --> camera_node : contains + + note right of folders_db + objects[] format: + [ + {object_type: "camera", + object_id: "server:0"}, + {object_type: "folder", + object_id: "subfolder_id"} + ] + end note + + note bottom of tree_class + Usage: + tree = FolderTree(user_id) + folder = tree.folders[folder_id] + cameras = folder.get_objects("camera") + end note + + @enduml +@startuml FolderSystemSimple + + title Система папок в Ivideon + + entity "folders" as folders_db { + * _id : ObjectId + -- + * owner_id : string + * name : string + * parents : array + * objects : array + * root : boolean + } + + entity "permission_grants" as grants_db { + * _id : ObjectId + -- + * object_id : string + * object_type : string + * grantee_id : string + * permissions : array + } + + entity "servers" as servers_db { + * _id : ObjectId + -- + * owner_id : string + * cameras : object + } + + entity "Folder" as folder_class { + + get_objects(type) + + add_object(obj) + + remove_object(obj) + + has_permissions(perm) + } + + entity "FolderTree" as tree_class { + + folders : dict + + find_folders() + + reload() + } + + entity "Camera" as camera_node { + + id : "server:index" + + object_type : "camera" + } + + folders_db ||--o{ folder_class + grants_db ||--o{ folder_class + servers_db ||--o{ camera_node + + tree_class --> folder_class : manages + folder_class --> camera_node : contains + + note right of folders_db + objects[] format: + [ + {object_type: "camera", + object_id: "server:0"}, + {object_type: "folder", + object_id: "subfolder_id"} + ] + end note + + note bottom of tree_class + Usage: + tree = FolderTree(user_id) + folder = tree.folders[folder_id] + cameras = folder.get_objects("camera") + end note + + @enduml +@startuml FolderSystemSimple + + title Система папок в Ivideon + + entity "folders" as folders_db { + * _id : ObjectId + -- + * owner_id : string + * name : string + * parents : array + * objects : array + * root : boolean + } + + entity "permission_grants" as grants_db { + * _id : ObjectId + -- + * object_id : string + * object_type : string + * grantee_id : string + * permissions : array + } + + entity "servers" as servers_db { + * _id : ObjectId + -- + * owner_id : string + * cameras : object + } + + entity "Folder" as folder_class { + + get_objects(type) + + add_object(obj) + + remove_object(obj) + + has_permissions(perm) + } + + entity "FolderTree" as tree_class { + + folders : dict + + find_folders() + + reload() + } + + entity "Camera" as camera_node { + + id : "server:index" + + object_type : "camera" + } + + folders_db ||--o{ folder_class + grants_db ||--o{ folder_class + servers_db ||--o{ camera_node + + tree_class --> folder_class : manages + folder_class --> camera_node : contains + + note right of folders_db + objects[] format: + [ + {object_type: "camera", + object_id: "server:0"}, + {object_type: "folder", + object_id: "subfolder_id"} + ] + end note + + note bottom of tree_class + Usage: + tree = FolderTree(user_id) + folder = tree.folders[folder_id] + cameras = folder.get_objects("camera") + end note + + @enduml +{ + database folders_db as "folders collection" + database grants_db as "permission_grants" + database servers_db as "servers collection" + } + + package "Folder Classes" { + class Folder { + +id: ObjectId + +owner_id: string + +name: string + +parents: List[string] + +objects: List[dict] + +root: boolean + -- + +get_objects(type): List[string] + +add_object(obj) + +remove_object(obj) + +has_permissions(perm): boolean + } + + class FolderTree { + +owner_id: string + +folders: Dict[id, Folder] + +objects: Dict[type, Dict[id, Node]] + +roots: List[Folder] + -- + +find_folders(): List[Folder] + +reload() + } + + class BaseNode { + +id: string + +owner_id: string + +grantee_id: string + +grants: Set[PermissionGrant] + -- + +has_permissions(perm): boolean + +permissions: Tuple[string] + } + } + + package "Permission System" { + class PermissionGrant { + +object_id: string + +object_type: string + +grantee_id: string + +permissions: List[string] + +shared_at: dict + } + } + + package "Node Types" { + class Camera { + +id: "server:index" + +object_type: "camera" + } + } + + ' Relationships + Folder --|> BaseNode + FolderTree --> Folder : manages + Folder --> PermissionGrant : has grants + BaseNode --> PermissionGrant : uses + + Folder --> folders_db : stored in + PermissionGrant --> grants_db : stored in + Camera --> servers_db : stored in + + ' Composition relationships + Folder --> Camera : "contains (objects[])" + Folder --> Folder : "contains subfolders" + + note right of Folder + objects[] содержит: + [ + {object_type: "camera", + object_id: "server:0"}, + {object_type: "folder", + object_id: "subfolder_id"} + ] + end note + + note right of FolderTree + Главная точка доступа: + tree = FolderTree(user_id) + folder = tree.folders[folder_id] + cameras = folder.get_objects("camera") + end note + + note left of PermissionGrant + Права доступа: + - admin (изменение) + - read (просмотр) + - Наследование по иерархии + end note + + @enduml diff --git a/ivideon/puml/Crowd/cl_get_all_cameras b/ivideon/puml/Crowd/cl_get_all_cameras new file mode 100644 index 0000000..f38ec86 --- /dev/null +++ b/ivideon/puml/Crowd/cl_get_all_cameras @@ -0,0 +1,326 @@ +@startuml _get_all_user_cameras Sequence Diagram + +title Последовательность выполнения _get_all_user_cameras + +participant "Caller" as caller +participant "_get_all_user_cameras" as main_func +participant "_get_servers" as get_servers +participant "MongoDB" as mongo +participant "ivideon.servers" as servers_collection + +note over main_func + Входные параметры: + - user_id: int + - requested_cameras: list[str] + (формат: ["server1:0", "server1:1"]) + - service_name: str (например: "crowd") +end note + +caller -> main_func: _get_all_user_cameras(user_id, requested_cameras, service_name) +activate main_func + +main_func -> main_func: cameras = {} + +main_func -> get_servers: _get_servers(requested_cameras) +activate get_servers + +note over get_servers + Извлекает server_ids из camera_ids: + ["server1:0", "server1:1"] + → ["server1", "server1"] + → ["server1"] +end note +@startuml _get_all_user_cameras Activity Diagram + +title Алгоритм работы _get_all_user_cameras + +start + +note right + **Входные параметры:** + • user_id: int + • requested_cameras: list[str] + (формат: ["server1:0", "server1:1"]) + • service_name: str (например: "crowd") +end note + +:Инициализация cameras = {}; + +:Извлечь server_ids из requested_cameras| +note right + ["server1:0", "server1:1"] + → ["server1"] +end note + +:Построить MongoDB запрос: +query = { + 'deleted': {'$ne': True}, + '_id': {'$in': server_ids} +}| + +:Задать проекцию полей: +projection = { + '_id': 1, 'owner_id': 1, 'name': 1, + 'cameras': 1, 'cam_services': 1, + 'info': 1, 'timezone': 1 +}| + +:Выполнить запрос к MongoDB: +servers = db.ivideon().servers.find(query, projection)| + +partition "Обработка серверов" { + :Взять следующий server; + + while (Есть серверы для обработки?) is (да) + :server_id = server['_id']; + :is_shared = server['owner_id'] != user_id; + :server_build_type = server.get('info', {}).get('build_type', ''); + :is_server_embedded = server_build_type.endswith('camera'); + :cam_services = server.get('cam_services', {}); + + partition "Обработка камер сервера" { + :Взять следующую камеру (camera_idx, camera_data); + + while (Есть камеры на сервере?) is (да) + :service_info = cam_services.get(camera_idx, {}) + .get(service_name, {}); + + if (service_info.get('active', False) == True?) then (да) + :camera_id = f'{server_id}:{camera_idx}'; + + if (is_server_embedded?) then (да) + :camera_name = server['name']; + else (нет) + :camera_name = camera_data.get('name'); + endif + + :cameras[camera_id] = { + 'id': camera_id, + 'owner_id': server['owner_id'], + 'server': server_id, + 'name': camera_name, + 'is_shared': is_shared, + 'timezone': server.get('timezone') or + server.get('timezone_default'), + 'is_embedded': is_server_embedded + }; + + else (нет) + note right: Камера пропускается - сервис неактивен + endif + + :Взять следующую камеру (camera_idx, camera_data); + endwhile (нет) + } + + :Взять следующий server; + endwhile (нет) +} + +:return cameras; + +stop + +note left + **Результат:** dict[camera_id, camera_info] + + **Пример:** + { + "507f...439011:0": { + "id": "507f...439011:0", + "owner_id": "user123", + "server": "507f...439011", + "name": "Камера входа", + "is_shared": false, + "timezone": "Europe/Moscow", + "is_embedded": false + } + } +end note + +@enduml +ggVG +get_servers -> get_servers: requested_server_ids = [camera_id.split(':')[0] \\nfor camera_id in requested_camera_ids] + +get_servers -> get_servers: query = {\n 'deleted': {'$ne': True},\n '_id': {'$in': requested_server_ids}\n} + +get_servers -> get_servers: projection = {\n '_id': 1, 'owner_id': 1, 'name': 1,\n 'cameras': 1, 'cam_services': 1,\n 'info': 1, 'timezone': 1\n} + +get_servers -> mongo: db.ivideon().servers.find(query, projection) +activate mongo +mongo -> servers_collection: find documents +activate servers_collection +servers_collection -> mongo: return server documents +deactivate servers_collection +mongo -> get_servers: list[server_documents] +deactivate mongo + +get_servers -> main_func: return servers_list +deactivate get_servers + +loop for each server in servers_list + main_func -> main_func: server@startuml _get_all_user_cameras Activity Diagram + +title Алгоритм работы _get_all_user_cameras + +start + +note right + **Входные параметры:** + • user_id: int + • requested_cameras: list[str] + (формат: ["server1:0", "server1:1"]) + • service_name: str (например: "crowd") +end note + +:Инициализация cameras = {}; + +:Извлечь server_ids из requested_cameras| +note right + ["server1:0", "server1:1"] + → ["server1"] +end note + +:Построить MongoDB запрос: +query = { + 'deleted': {'$ne': True}, + '_id': {'$in': server_ids} +}| + +:Задать проекцию полей: +projection = { + '_id': 1, 'owner_id': 1, 'name': 1, + 'cameras': 1, 'cam_services': 1, + 'info': 1, 'timezone': 1 +}| + +:Выполнить запрос к MongoDB: +servers = db.ivideon().servers.find(query, projection)| + +partition "Обработка серверов" { + :Взять следующий server; + + while (Есть серверы для обработки?) is (да) + :server_id = server['_id']; + :is_shared = server['owner_id'] != user_id; + :server_build_type = server.get('info', {}).get('build_type', ''); + :is_server_embedded = server_build_type.endswith('camera'); + :cam_services = server.get('cam_services', {}); + + partition "Обработка камер сервера" { + :Взять следующую камеру (camera_idx, camera_data); + + while (Есть камеры на сервере?) is (да) + :service_info = cam_services.get(camera_idx, {}) + .get(service_name, {}); + + if (service_info.get('active', False) == True?) then (да) + :camera_id = f'{server_id}:{camera_idx}'; + + if (is_server_embedded?) then (да) + :camera_name = server['name']; + else (нет) + :camera_name = camera_data.get('name'); + endif + + :cameras[camera_id] = { + 'id': camera_id, + 'owner_id': server['owner_id'], + 'server': server_id, + 'name': camera_name, + 'is_shared': is_shared, + 'timezone': server.get('timezone') or + server.get('timezone_default'), + 'is_embedded': is_server_embedded + }; + + else (нет) + note right: Камера пропускается - сервис неактивен + endif + + :Взять следующую камеру (camera_idx, camera_data); + endwhile (нет) + } + + :Взять следующий server; + endwhile (нет) +} + +:return cameras; + +stop + +note left + **Результат:** dict[camera_id, camera_info] + + **Пример:** + { + "507f...439011:0": { + "id": "507f...439011:0", + "owner_id": "user123", + "server": "507f...439011", + "name": "Камера входа", + "is_shared": false, + "timezone": "Europe/Moscow", + "is_embedded": false + } + } +end note + +@enduml +ggVG_id = server['_id'] + main_func -> main_func: is_shared = server['owner_id'] != user_id + main_func -> main_func: server_build_type = server.get('info', {}).get('build_type', '') + main_func -> main_func: is_server_embedded = server_build_type.endswith('camera') + main_func -> main_func: cam_services = server.get('cam_services', {}) + + loop for camera_idx, camera_data in server.cameras.items() + main_func -> main_func: service_info = cam_services.get(camera_idx, {})\\n .get(service_name, {}) + + alt service_info.get('active', False) == True + main_func -> main_func: camera_id = f'{server_id}:{camera_idx}' + + alt is_server_embedded == True + main_func -> main_func: camera_name = server['name'] + else + main_func -> main_func: camera_name = camera_data.get('name') + end + + main_func -> main_func: cameras[camera_id] = {\n 'id': camera_id,\n 'owner_id': server['owner_id'],\n 'server': server_id,\n 'name': camera_name,\n 'is_shared': is_shared,\n 'timezone': server.timezone,\n 'is_embedded': is_server_embedded\n} + + note right + Создается полная информация + о камере для возврата + end note + + else + note right + Камера пропускается: + сервис неактивен + end note + end + end +end + +main_func -> caller: return cameras dict +deactivate main_func + +note over caller + Результат: dict[camera_id, camera_info] + где camera_id = "server_id:camera_index" + + Пример: + { + "507f...439011:0": { + "id": "507f...439011:0", + "owner_id": "user123", + "server": "507f...439011", + "name": "Камера входа", + "is_shared": false, + "timezone": "Europe/Moscow", + "is_embedded": false + } + } +end note + +@enduml diff --git a/ivideon/puml/Crowd/entities.puml b/ivideon/puml/Crowd/entities.puml new file mode 100644 index 0000000..46c8cff --- /dev/null +++ b/ivideon/puml/Crowd/entities.puml @@ -0,0 +1,58 @@ +@startuml +title Верхнеуровневые сущности сервиса Crowd + +card api_concept{ + entity "CrowdReport(APIObject)" as CRA{ + + id str + + owner_id str + + type str + + name str + + status str + + created_at timestamp + + updated_at timestamp + + progress int + + options dict + + create() -> CrowdReport + } +} + +card crowd_service{ + card backend { + } + + card bot_notifier { + } + + card frontend { + card impl { + entity CrowdReport{ + + delete() -> None + + create() -> CrowdReport + } + } + } + + card node { + } + + card protocols{ + } + + card report_builder{ + } + + card utils { + } + +} + +json options_dict { + "cameras": ["cam1", "cam2"], + "folders": ["folder1"], + "zones": ["zone1"] +} + +CrowdReport ..|> CRA +CRA::options -- options_dict + +@enduml \ No newline at end of file