347 lines
11 KiB
Python
347 lines
11 KiB
Python
# ---------------------- 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
|
||
|