ivideon: Add some schemes for crowd service

This commit is contained in:
t0xa 2025-09-02 14:21:45 +03:00
parent 4c9f2a8267
commit be420adc49
7 changed files with 924 additions and 0 deletions

View file

View file

View file

View file

@ -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

View file

@ -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

View file

@ -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