diff --git a/webapp/src/components/FabBreadcrumb.vue b/webapp/src/components/FabBreadcrumb.vue new file mode 100644 index 0000000..e57051e --- /dev/null +++ b/webapp/src/components/FabBreadcrumb.vue @@ -0,0 +1,64 @@ + + + diff --git a/webapp/src/layout/AppHeader.vue b/webapp/src/layout/AppHeader.vue index d786015..f93b4d3 100644 --- a/webapp/src/layout/AppHeader.vue +++ b/webapp/src/layout/AppHeader.vue @@ -2,9 +2,11 @@
-
+
import { onMounted, ref } from "vue"; import { useAuthStore } from "@/stores/authStore"; +import { useModelStore } from "@/stores/modelStore"; +import FabBreadcrumb from "@/components/FabBreadcrumb.vue"; +import { useRouter } from "vue-router"; const userTheme = ref("light"); const authStore = useAuthStore(); +const modelStore = useModelStore(); +const router = useRouter(); onMounted(() => { const initUserTheme = getTheme() || getMediaPreference(); @@ -143,6 +150,14 @@ const getMediaPreference = () => { return "light"; } }; + +const routerLinker = (x: Array) => { + if (x.length == 1) { + router.push(`/${x[0]}`); + } else { + router.push(`/${x[0]}/${x[1]}`); + } +};
+
Context bar
+ +
+ +
+
+ + + diff --git a/webapp/src/views/ModelDetailsView.vue b/webapp/src/views/ModelDetailsView.vue index 90a8250..48741d7 100644 --- a/webapp/src/views/ModelDetailsView.vue +++ b/webapp/src/views/ModelDetailsView.vue @@ -6,6 +6,10 @@ id="content-container" class="flex h-full w-full flex-col bg-gray-300 dark:bg-gray-700" > + {{ modelStore.model_instance_parent_chain }} +
+
+ Modelname: {{ model_name }}
id: {{ id }}

@@ -54,6 +58,7 @@ const model_name = computed(() => { onMounted(async () => { await modelStore.fetch_instance(model_name.value, id.value); await modelStore.fetch_instance_children(model_name.value, id.value); + await modelStore.fetch_parent_chain(model_name.value, id.value); }); watch( @@ -63,6 +68,7 @@ watch( console.log("watch", id.value, route.params.id); await modelStore.fetch_instance(model_name.value, id.value); await modelStore.fetch_instance_children(model_name.value, id.value); + await modelStore.fetch_parent_chain(model_name.value, id.value); } } ); diff --git a/webapp/src/views/ModelView.vue b/webapp/src/views/ModelView.vue index 7301c17..1aca92d 100644 --- a/webapp/src/views/ModelView.vue +++ b/webapp/src/views/ModelView.vue @@ -73,6 +73,7 @@ const model_name = computed(() => { onMounted(async () => { console.log("modelview-mount", model_name.value); await modelStore.fetch_all_instances(model_name.value); + modelStore.model_instance_parent_chain = []; }); watch( diff --git a/webapp/vite.config.ts b/webapp/vite.config.ts index 2df7a33..9dd0e5a 100644 --- a/webapp/vite.config.ts +++ b/webapp/vite.config.ts @@ -16,4 +16,7 @@ export default defineConfig({ globals: true, setupFiles: ["./tests/setup.js"], }, + server: { + port: 8800, + }, }); diff --git a/worst_crm/api_router.py b/worst_crm/api_router.py index a91e880..b722caf 100644 --- a/worst_crm/api_router.py +++ b/worst_crm/api_router.py @@ -9,7 +9,7 @@ import datetime as dt -class APIRouter(APIRouter): +class WorstRouter(APIRouter): def __init__( self, model_name: str, @@ -46,7 +46,16 @@ async def get_all_children( id: UUID, ) -> dict | None: return svc.get_all_children(model_name, id) - + + @self.get( + "/{id}/parent_chain", + dependencies=[Security(dep.get_current_user)], + ) + async def get_parent_chain( + id: UUID, + ) -> list | None: + return svc.get_parent_chain(model_name, id) + @self.get( "/{id}/{children_model_name}", dependencies=[Security(dep.get_current_user)], @@ -56,6 +65,8 @@ async def get_all_children_for_model( children_model_name: str, ) -> list | None: return svc.get_all_children_for_model(model_name, id, children_model_name) + + @self.post( "", diff --git a/worst_crm/db.py b/worst_crm/db.py index ccd3e45..f097dcc 100644 --- a/worst_crm/db.py +++ b/worst_crm/db.py @@ -584,6 +584,31 @@ def get_all_children_for_model( ) +def get_parent_chain( + model_name: str, + id: UUID, +) -> list | None: + chain = [] + + p = execute_stmt( + f""" + SELECT parent_type, parent_id::STRING, name + FROM {model_name} + WHERE id = %s + """, + (id,), + ) + + if p[0]: + chain.extend(get_parent_chain(p[0], p[1])) + chain.append(p) + + else: + chain.append(p) + + return chain + + def create( model_name: str, model_instance: Type[BaseFields] ) -> Type[BaseFields] | None: @@ -681,7 +706,7 @@ def execute_stmt( returning_model: Type[BaseFields] = None, is_list: bool = False, returning_rs: bool = True, -) -> Type[BaseFields] | list[Type[BaseFields]] | None: +) -> Type[BaseFields] | list[Type[BaseFields]] | list[tuple] | None: with pool.connection() as conn: # convert a set to a psycopg list conn.adapters.register_dumper(set, ListDumper) diff --git a/worst_crm/main.py b/worst_crm/main.py index f528124..076e3a6 100644 --- a/worst_crm/main.py +++ b/worst_crm/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, Depends, HTTPException, Query, status +from fastapi import FastAPI, Depends, HTTPException, Query, status, APIRouter from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.security import OAuth2PasswordRequestForm @@ -7,7 +7,7 @@ from typing import Annotated from worst_crm import db from worst_crm import service as svc -from worst_crm.api_router import APIRouter +from worst_crm.api_router import WorstRouter from worst_crm.models import ( UserInDB, Token, @@ -27,7 +27,7 @@ app = FastAPI( title="WorstCRM API", version="0.1.0", - docs_url="/api", + docs_url="/docs", openapi_url="/worst_crm.openapi.json", ) @@ -57,7 +57,7 @@ async def home() -> FileResponse: @app.get("/healthcheck") async def healthcheck() -> dict: - return {} + return {"hello": "mona"} @app.get("/me", dependencies=[Depends(dep.get_current_user)]) @@ -110,15 +110,15 @@ async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> T # add routers dynamically for k, v in pyd_models.items(): - router = APIRouter( - model_name=k, - default_model=v["default"], - overview_model=v["overview"], - update_model=v["update"], + app.include_router( + WorstRouter( + model_name=k, + default_model=v["default"], + overview_model=v["overview"], + update_model=v["update"], + ) ) - app.include_router(router) - # ADMIN app.include_router(admin.router) @@ -146,19 +146,3 @@ def watch_it(watch_epoch: int): # periodically check if a restart is needed threading.Thread(target=watch_it, args=(watch_epoch,), daemon=True).start() - - -# from psycopg_pool import ConnectionPool - -# DB_URL = os.getenv("DB_URL") - -# if not DB_URL: -# raise EnvironmentError("DB_URL env variable not found!") - - -# # the pool starts connecting immediately. -# pool = ConnectionPool(DB_URL, kwargs={"autocommit": True}) - - -# def get_pool(): -# return pool diff --git a/worst_crm/service.py b/worst_crm/service.py index f141dfb..c8d47f9 100644 --- a/worst_crm/service.py +++ b/worst_crm/service.py @@ -31,6 +31,21 @@ def get_all_children_for_model( return db.get_all_children_for_model(model_name, id, children_model_name) +def get_parent_chain( + model_name: str, + id: UUID, +) -> list | None: + raw_list = db.get_parent_chain(model_name, id) + + # [ [ null, null, "acc3" ], [ "account", "3fa85f64-5717-4562-b3fc-2c963f66afa4", "prog3-acc3" ] ] + + l = [] + for i in range(len(raw_list) - 1): + l.append([raw_list[i + 1][0], raw_list[i + 1][1], raw_list[i][2]]) + + return l + + def create( model_name: str, user_id: str,