# ---------------------- modularity from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, Request from app.core.map_index_reader import MapIndexReader from app.core.response_helper import ResponseHelper from app.routes.v1.models import ( SearchRequest, InsertRequest, UpdateByQueryRequest, DeleteByQueryRequest, ) from app.routes.tree.base_models import ( treeSearchRequest, treeInsertRequest, treeUpdateRequest, ) from typing import Any, Dict, List, Optional from app.routes.v1.elastic import ( search, get_by_id, insert, update, delete, ) import uuid, requests, time, traceback # ---------------------- global-params router = APIRouter(tags=["tree"]) # ---------------------- base-Func def get_elastic_helper(request: Request): helper = getattr(request.app.state, "elastic_helper", None) if helper is None: raise RuntimeError("Elasticsearch helper not initialized") return helper # ---------------------- router-Func # ----------- GET @router.get("/ping") async def helloworld(request: Request): return {"message": " tree base hello-world"} def sort_children(node): node["children"].sort(key=lambda x: x["child_order"]) for child in node["children"]: sort_children(child) # خروجی بصورت تو در تو بر می گرداند ، یعنی بچه ها داخل فیلد پدر درج میشود @router.post("/{type_name}/get") async def search_tree(type_name: str, payload: treeSearchRequest, request: Request): # print(f"type_name {type_name}", f"payload {payload}", sep="\n") tcode = payload.tcode version_key = payload.version_key parent_id = payload.parent_id nested = payload.nested all_item = payload.all_item query = payload.query if parent_id == "0": parent_id = 0 must = [ {"term": {"version_key": version_key}}, {"term": {"tcode": tcode}}, ] if not all_item : must.append({"term": {"parent_id": parent_id}}) query = { "size": 10000, "_source": [ "id", "title", "parent_id", "child_order", "time_create", "tcode", "version_key", "full_path", "content", ], "query": { "bool": { "must": must } }, "sort": [{"parent_id": "asc"}, {"child_order": "asc"}], } if not all_item: query["query"]["bool"]["must"].append({"term": {"parent_id": parent_id}}) if tcode != "": query["query"]["bool"]["must"].append({"term": {"tcode": tcode}}) reader = MapIndexReader(type_name) # mj_plan helper = getattr(request.app.state, "elastic_helper", None) if helper is None: raise RuntimeError("Elasticsearch helper not initialized") # "قانون تشکل‌های مدنی و احزاب" try: index_name = reader.get_index_name() es_res = await helper.search(index_name, query) # return es_res es_res = dict(es_res) items = [] for item in es_res["hits"]["hits"] : item_new = { "id": item["_id"], "title": item["_source"]["title"], "version_key": item["_source"]["version_key"], "parent_id": item["_source"]["parent_id"], "child_order": item["_source"]["child_order"], "full_path": item["_source"]["full_path"], "tcode": item["_source"]["tcode"], "children": [], } if "content" in item["_source"] : item_new["content"] = item["_source"]["content"] items.append(item_new) if not nested: return items # 2️⃣ ساخت دیکشنری lookup بر اساس id برای دسترسی سریع lookup = {item["id"]: item for item in items} # 3️⃣ ساخت ساختار درختی root_nodes = [] p = 0 c = 0 for item in items: parent_id = item["parent_id"] if parent_id == 0: # ریشه‌ها root_nodes.append(item) # print(f'parent {p}') p += 1 else: # print(f'parent_id {parent_id}') parent = lookup.get(parent_id) if parent: parent["children"].append(item) # print(f'children {c}') c += 1 for root in root_nodes: sort_children(root) # print(f'es_res {result}', ) return root_nodes except Exception as exc: # noqa: BLE001 raise HTTPException(status_code=404, detail=str(traceback.print_exc())) @router.get("/{type_name}/get/{id}") async def get_tree(type_name: str, id: str, request: Request): doc_id = id response = await get_by_id(type_name, doc_id, request ) return response @router.post("/{type_name}/insert") async def insert_tree(type_name: str, payload:treeInsertRequest, request: Request): reader = MapIndexReader(type_name) helper = get_elastic_helper(request) index_name = reader.get_index_name() if not payload.id or payload.id == None or payload.id == "None" : payload.id = 'mp_' + uuid.uuid4().hex[:8] if not payload.parent_id or payload.parent_id == None: payload.parent_id = "0" if not payload.child_order or payload.child_order == -1 : payload.child_order = get_max_child_order(helper, index_name, payload ) else : update_next_child_order(helper, index_name, payload ) #????? if not payload.full_path : payload.full_path = '' payload_new = InsertRequest.model_construct() payload_new.id = payload.id payload_new.document = payload.model_dump(exclude_none=True) response = await insert(type_name, payload_new, request ) return response @router.post("/{type_name}/update/{id}") async def update_tree(type_name: str, id: str, payload:treeUpdateRequest, request: Request): # print("################################update_tree ", id) reader = MapIndexReader(type_name) helper = get_elastic_helper(request) index_name = reader.get_index_name() # print("################################update_tree ", payload) if payload.child_order and payload.child_order == -1 : payload.child_order = get_max_child_order(helper, index_name, payload ) payload_new = InsertRequest.model_construct() payload_new.id = id payload_new.document = payload.model_dump(exclude_none=True) # print("####################################### payload_new ", payload_new) response = await update(type_name, id, payload_new, request ) return response return "" @router.post("/{type_name}/delete/{id}") async def delete_tree(type_name: str, id: str, request: Request): response = await delete(type_name, id, request ) return response @router.post("/{type_name}/move_prev/{to_id}/{id}") async def move_prev_tree(type_name: str, id: str, to_id: str, request: Request): reader = MapIndexReader(type_name) helper = get_elastic_helper(request) index_name = reader.get_index_name() # node1 = await helper.get_by_id(index_name, id) node2 = await helper.get_by_id(index_name, to_id) #?????? if id == to_id : message = " error id " raise HTTPException(status_code=404, detail=message) if not node2 : message = "not found id " raise HTTPException(status_code=404, detail=message) payload=node2["_source"] child_order = get_max_child_order(helper, index_name, payload ) payload_new = InsertRequest() payload_new.id = id payload_new.document = { "parent_id" : to_id, "child_order" : child_order } response = await update(type_name, id, payload_new, request ) return response @router.post("/{type_name}/move_in/{to_id}/{id}") async def move_in_tree(type_name: str, id: str, to_id: str, request: Request): reader = MapIndexReader(type_name) helper = get_elastic_helper(request) index_name = reader.get_index_name() # node1 = await helper.get_by_id(index_name, id) node2 = await helper.get_by_id(index_name, to_id) #?????? if id == to_id : message = " error id " raise HTTPException(status_code=404, detail=message) if not node2 : message = "not found id " raise HTTPException(status_code=404, detail=message) # child_order1 = node1["_source"]["child_order"] child_order2 = node2["_source"]["child_order"] # parent_id1 = node1["_source"]["parent_id"] parent_id2 = node2["_source"]["parent_id"] payload=node2["_source"] update_next_child_order(helper, index_name, payload ) payload_new = InsertRequest.model_construct() payload_new.id = id payload_new.document = { "parent_id" : parent_id2, "child_order" : child_order2 } response = await update(type_name, id, payload_new, request ) return response async def get_max_child_order(helper, index_name, payload:treeInsertRequest) : must = [ {"term": {"version_key": payload.version_key}}, {"term": {"tcode": payload.tcode}}, {"term": {"parent_id": payload.parent_id}} ] query = { "size": 1, "_source": ["child_order"], "query": { "bool": { "must": must } }, "sort": [{"child_order": "asc"}], } es_res = await helper.search(index_name, query) child_order = 0 if len(es_res["hits"]["hits"]) > 0 : child_order = es_res["hits"]["hits"][0]["_source"]["child_order"] return child_order + 1 async def update_next_child_order(helper, index_name, payload:treeInsertRequest) : must = [ {"term": {"version_key": payload.version_key}}, {"term": {"tcode": payload.tcode}}, {"term": {"parent_id": payload.parent_id}}, {"range": {"child_order": { "gte": payload.child_order}}} ] query = { "query": { "bool": { "must": must } }, "script": { "source": "ctx._source.child_order = ctx._source.child_order + 1; ", "lang": "painless" } } es_res = await helper.update_by_query(index_name, query, True) return es_res