Compare commits
No commits in common. "9ddd85d36af74371a546b5183ad750ccf998b7bd" and "0b9489f110263f69f6c2e347ef25c4f79e3cbee6" have entirely different histories.
9ddd85d36a
...
0b9489f110
@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get Date and Build Number
|
- name: Get Date and Build Number
|
||||||
run: |
|
run: |
|
||||||
git fetch
|
git fetch --tags
|
||||||
git tag
|
git tag
|
||||||
DATE=$(date +'%Y.%m.%d')
|
DATE=$(date +'%Y.%m.%d')
|
||||||
TAG_COUNT=$(git tag -l "${DATE}.*" | wc -l)
|
TAG_COUNT=$(git tag -l "${DATE}.*" | wc -l)
|
||||||
@ -54,7 +54,6 @@ jobs:
|
|||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: version
|
name: version
|
||||||
path: version.txt
|
|
||||||
|
|
||||||
- name: Build docker
|
- name: Build docker
|
||||||
run: |
|
run: |
|
||||||
@ -87,7 +86,6 @@ jobs:
|
|||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: version
|
name: version
|
||||||
path: version.txt
|
|
||||||
|
|
||||||
- name: Build docker
|
- name: Build docker
|
||||||
run: |
|
run: |
|
||||||
@ -119,7 +117,6 @@ jobs:
|
|||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: version
|
name: version
|
||||||
path: version.txt
|
|
||||||
|
|
||||||
- name: Prepare web build
|
- name: Prepare web build
|
||||||
run: |
|
run: |
|
||||||
@ -134,7 +131,7 @@ jobs:
|
|||||||
- name: Build docker
|
- name: Build docker
|
||||||
run: |
|
run: |
|
||||||
cd web
|
cd web
|
||||||
docker build --no-cache --build-arg VERSION=$(cat version.txt) -t git.sh-edraft.de/sh-edraft.de/open-redirect-web:$(cat ../version.txt) .
|
docker build --no-cache -t git.sh-edraft.de/sh-edraft.de/open-redirect-web:$(cat ../version.txt) .
|
||||||
|
|
||||||
- name: Login to registry git.sh-edraft.de
|
- name: Login to registry git.sh-edraft.de
|
||||||
uses: https://github.com/docker/login-action@v1
|
uses: https://github.com/docker/login-action@v1
|
||||||
|
@ -86,7 +86,6 @@ jobs:
|
|||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: version
|
name: version
|
||||||
path: version.txt
|
|
||||||
|
|
||||||
- name: Build docker
|
- name: Build docker
|
||||||
run: |
|
run: |
|
||||||
@ -118,7 +117,6 @@ jobs:
|
|||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: version
|
name: version
|
||||||
path: version.txt
|
|
||||||
|
|
||||||
- name: Prepare web build
|
- name: Prepare web build
|
||||||
run: |
|
run: |
|
||||||
@ -133,7 +131,7 @@ jobs:
|
|||||||
- name: Build docker
|
- name: Build docker
|
||||||
run: |
|
run: |
|
||||||
cd web
|
cd web
|
||||||
docker build --no-cache --build-arg VERSION=$(cat version.txt) -t git.sh-edraft.de/sh-edraft.de/open-redirect-web:$(cat ../version.txt) .
|
docker build --no-cache -t git.sh-edraft.de/sh-edraft.de/open-redirect-web:$(cat ../version.txt) .
|
||||||
|
|
||||||
- name: Login to registry git.sh-edraft.de
|
- name: Login to registry git.sh-edraft.de
|
||||||
uses: https://github.com/docker/login-action@v1
|
uses: https://github.com/docker/login-action@v1
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name: Test API before pr merge
|
name: Test before pr merge
|
||||||
run-name: Test API before pr merge
|
run-name: Test before pr merge
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
|
@ -8,16 +8,32 @@ from starlette.routing import Route as StarletteRoute
|
|||||||
|
|
||||||
from api.errors import unauthorized
|
from api.errors import unauthorized
|
||||||
from api.middleware.request import get_request
|
from api.middleware.request import get_request
|
||||||
from api.route_api_key_extension import RouteApiKeyExtension
|
|
||||||
from api.route_user_extension import RouteUserExtension
|
from api.route_user_extension import RouteUserExtension
|
||||||
from core.environment import Environment
|
from core.environment import Environment
|
||||||
from data.schemas.administration.api_key import ApiKey
|
from data.schemas.administration.api_key import ApiKey
|
||||||
|
from data.schemas.administration.api_key_dao import apiKeyDao
|
||||||
from data.schemas.administration.user import User
|
from data.schemas.administration.user import User
|
||||||
|
|
||||||
|
|
||||||
class Route(RouteUserExtension, RouteApiKeyExtension):
|
class Route(RouteUserExtension):
|
||||||
registered_routes: list[StarletteRoute] = []
|
registered_routes: list[StarletteRoute] = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_api_key(cls, request: Request) -> ApiKey:
|
||||||
|
auth_header = request.headers.get("Authorization", None)
|
||||||
|
api_key = auth_header.split(" ")[1]
|
||||||
|
return await apiKeyDao.find_by_key(api_key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def _verify_api_key(cls, req: Request) -> bool:
|
||||||
|
auth_header = req.headers.get("Authorization", None)
|
||||||
|
if not auth_header or not auth_header.startswith("API-Key "):
|
||||||
|
return False
|
||||||
|
|
||||||
|
api_key = auth_header.split(" ")[1]
|
||||||
|
api_key_from_db = await apiKeyDao.find_by_key(api_key)
|
||||||
|
return api_key_from_db is not None and not api_key_from_db.deleted
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _get_auth_type(
|
async def _get_auth_type(
|
||||||
cls, request: Request, auth_header: str
|
cls, request: Request, auth_header: str
|
||||||
@ -63,7 +79,8 @@ class Route(RouteUserExtension, RouteApiKeyExtension):
|
|||||||
return await cls._get_auth_type(request, auth_header)
|
return await cls._get_auth_type(request, auth_header)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def is_authorized(cls, request: Request) -> bool:
|
async def is_authorized(cls) -> bool:
|
||||||
|
request = get_request()
|
||||||
if request is None:
|
if request is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -102,7 +119,7 @@ class Route(RouteUserExtension, RouteApiKeyExtension):
|
|||||||
return await f(request, *args, **kwargs)
|
return await f(request, *args, **kwargs)
|
||||||
return f(request, *args, **kwargs)
|
return f(request, *args, **kwargs)
|
||||||
|
|
||||||
if not await cls.is_authorized(request):
|
if not await cls.is_authorized():
|
||||||
return unauthorized()
|
return unauthorized()
|
||||||
|
|
||||||
if iscoroutinefunction(f):
|
if iscoroutinefunction(f):
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
from starlette.requests import Request
|
|
||||||
|
|
||||||
from data.schemas.administration.api_key import ApiKey
|
|
||||||
from data.schemas.administration.api_key_dao import apiKeyDao
|
|
||||||
|
|
||||||
|
|
||||||
class RouteApiKeyExtension:
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def get_api_key(cls, request: Request) -> ApiKey:
|
|
||||||
auth_header = request.headers.get("Authorization", None)
|
|
||||||
api_key = auth_header.split(" ")[1]
|
|
||||||
return await apiKeyDao.find_single_by(
|
|
||||||
[{ApiKey.key: api_key}, {ApiKey.deleted: False}]
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def _verify_api_key(cls, req: Request) -> bool:
|
|
||||||
auth_header = req.headers.get("Authorization", None)
|
|
||||||
if not auth_header or not auth_header.startswith("API-Key "):
|
|
||||||
return False
|
|
||||||
|
|
||||||
api_key = auth_header.split(" ")[1]
|
|
||||||
api_key_from_db = await apiKeyDao.find_single_by(
|
|
||||||
[{ApiKey.key: api_key}, {ApiKey.deleted: False}]
|
|
||||||
)
|
|
||||||
return api_key_from_db is not None and not api_key_from_db.deleted
|
|
@ -18,7 +18,6 @@ logger = Logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class RouteUserExtension:
|
class RouteUserExtension:
|
||||||
_cached_users: dict[int, User] = {}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_user_id_from_token(cls, request: Request) -> Optional[str]:
|
def _get_user_id_from_token(cls, request: Request) -> Optional[str]:
|
||||||
@ -63,7 +62,9 @@ class RouteUserExtension:
|
|||||||
if request is None:
|
if request is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return await userDao.find_by_keycloak_id(cls.get_token(request))
|
return await userDao.find_single_by(
|
||||||
|
[{User.keycloak_id: cls.get_token(request)}, {User.deleted: False}]
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_user_or_default(cls) -> Optional[User]:
|
async def get_user_or_default(cls) -> Optional[User]:
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Union, Callable, Any
|
|
||||||
|
|
||||||
from api_graphql.abc.query_abc import QueryABC
|
|
||||||
from core.database.abc.data_access_object_abc import DataAccessObjectABC
|
|
||||||
|
|
||||||
|
|
||||||
class DbHistoryModelQueryABC(QueryABC):
|
|
||||||
|
|
||||||
def __init__(self, name: str = __name__):
|
|
||||||
QueryABC.__init__(self, f"{name}History")
|
|
||||||
|
|
||||||
self.set_field("id", lambda x, *_: x.id)
|
|
||||||
self.set_field("deleted", lambda x, *_: x.deleted)
|
|
||||||
self.set_field("editor", self._resolve_editor)
|
|
||||||
self.set_field("created", lambda x, *_: x.created)
|
|
||||||
self.set_field("updated", lambda x, *_: x.updated)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _resolve_editor(x, *_):
|
|
||||||
editor = await x.editor
|
|
||||||
return editor.username if editor else None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _resolve_foreign_history(
|
|
||||||
updated: datetime,
|
|
||||||
obj_ident: Union[str, int],
|
|
||||||
join_dao: DataAccessObjectABC,
|
|
||||||
foreign_dao: DataAccessObjectABC,
|
|
||||||
foreign_join_key: Callable[[Any], Any],
|
|
||||||
obj_key="id",
|
|
||||||
*_,
|
|
||||||
):
|
|
||||||
foreign_history = sorted(
|
|
||||||
[
|
|
||||||
*await join_dao.find_by(
|
|
||||||
[
|
|
||||||
{obj_key: obj_ident},
|
|
||||||
{"updated": {"lessOrEqual": updated}},
|
|
||||||
]
|
|
||||||
),
|
|
||||||
*await join_dao.get_history(
|
|
||||||
obj_ident,
|
|
||||||
by_key=obj_key,
|
|
||||||
until=updated,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
key=lambda x: x.updated,
|
|
||||||
)
|
|
||||||
|
|
||||||
foreign_ids = set()
|
|
||||||
for foreign in foreign_history:
|
|
||||||
if not foreign.deleted:
|
|
||||||
foreign_ids.add(foreign_join_key(foreign))
|
|
||||||
continue
|
|
||||||
|
|
||||||
foreign_ids.discard(foreign_join_key(foreign))
|
|
||||||
|
|
||||||
return [await foreign_dao.get_by_id(x) for x in sorted(foreign_ids)]
|
|
@ -23,5 +23,5 @@ class DbModelCollectionFilterABC[T](CollectionFilterABC):
|
|||||||
self.add_field("id", IntCollectionFilter)
|
self.add_field("id", IntCollectionFilter)
|
||||||
self.add_field("deleted", BoolCollectionFilter)
|
self.add_field("deleted", BoolCollectionFilter)
|
||||||
self.add_field("editor", IntCollectionFilter)
|
self.add_field("editor", IntCollectionFilter)
|
||||||
self.add_field("created", DateCollectionFilter)
|
self.add_field("createdUtc", DateCollectionFilter)
|
||||||
self.add_field("updated", DateCollectionFilter)
|
self.add_field("updatedUtc", DateCollectionFilter)
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from api_graphql.abc.filter.bool_filter import BoolFilter
|
from api_graphql.abc.filter.bool_filter import BoolFilter
|
||||||
from api_graphql.abc.filter.date_filter import DateFilter
|
|
||||||
from api_graphql.abc.filter.fuzzy_filter import FuzzyFilter
|
|
||||||
from api_graphql.abc.filter.int_filter import IntFilter
|
from api_graphql.abc.filter.int_filter import IntFilter
|
||||||
from api_graphql.abc.filter.string_filter import StringFilter
|
from api_graphql.abc.filter.string_filter import StringFilter
|
||||||
from api_graphql.abc.filter_abc import FilterABC
|
from api_graphql.abc.filter_abc import FilterABC
|
||||||
|
from api_graphql.filter.fuzzy_filter import FuzzyFilter
|
||||||
|
|
||||||
|
|
||||||
class DbModelFilterABC[T](FilterABC[T]):
|
class DbModelFilterABC[T](FilterABC[T]):
|
||||||
@ -19,7 +18,7 @@ class DbModelFilterABC[T](FilterABC[T]):
|
|||||||
self.add_field("id", IntFilter)
|
self.add_field("id", IntFilter)
|
||||||
self.add_field("deleted", BoolFilter)
|
self.add_field("deleted", BoolFilter)
|
||||||
self.add_field("editor", UserFilter)
|
self.add_field("editor", UserFilter)
|
||||||
self.add_field("created", DateFilter)
|
self.add_field("createdUtc", StringFilter, "created")
|
||||||
self.add_field("updated", DateFilter)
|
self.add_field("updatedUtc", StringFilter, "updated")
|
||||||
|
|
||||||
self.add_field("fuzzy", FuzzyFilter)
|
self.add_field("fuzzy", FuzzyFilter)
|
||||||
|
@ -1,54 +1,18 @@
|
|||||||
from copy import deepcopy
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.query_abc import QueryABC
|
from api_graphql.abc.query_abc import QueryABC
|
||||||
from core.database.abc.data_access_object_abc import DataAccessObjectABC
|
from data.schemas.administration.user import User
|
||||||
from core.logger import APILogger
|
|
||||||
|
|
||||||
logger = APILogger("api.api")
|
|
||||||
|
|
||||||
|
|
||||||
class DbModelQueryABC(QueryABC):
|
class DbModelQueryABC(QueryABC):
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, name: str = __name__):
|
||||||
self,
|
|
||||||
name: str = __name__,
|
|
||||||
dao: DataAccessObjectABC = None,
|
|
||||||
with_history: bool = False,
|
|
||||||
):
|
|
||||||
QueryABC.__init__(self, name)
|
QueryABC.__init__(self, name)
|
||||||
|
|
||||||
self._dao: Optional[DataAccessObjectABC] = dao
|
|
||||||
|
|
||||||
self.set_field("id", lambda x, *_: x.id)
|
self.set_field("id", lambda x, *_: x.id)
|
||||||
self.set_field("deleted", lambda x, *_: x.deleted)
|
self.set_field("deleted", lambda x, *_: x.deleted)
|
||||||
self.set_field("editor", lambda x, *_: x.editor)
|
self.set_field("editor", self.__get_editor)
|
||||||
self.set_field("created", lambda x, *_: x.created)
|
self.set_field("createdUtc", lambda x, *_: x.created)
|
||||||
self.set_field("updated", lambda x, *_: x.updated)
|
self.set_field("updatedUtc", lambda x, *_: x.updated)
|
||||||
|
|
||||||
if with_history:
|
@staticmethod
|
||||||
self.set_field("history", self._resolve_history)
|
async def __get_editor(x: User, *_):
|
||||||
|
return await x.editor
|
||||||
self._history_reference_daos: dict[DataAccessObjectABC, str] = {}
|
|
||||||
|
|
||||||
async def _resolve_history(self, x, *_):
|
|
||||||
if self._dao is None:
|
|
||||||
raise Exception("DAO not set for history query")
|
|
||||||
|
|
||||||
history = sorted(
|
|
||||||
[await self._dao.get_by_id(x.id), *await self._dao.get_history(x.id)],
|
|
||||||
key=lambda h: h.updated,
|
|
||||||
reverse=True,
|
|
||||||
)
|
|
||||||
return history
|
|
||||||
|
|
||||||
def set_history_reference_dao(self, dao: DataAccessObjectABC, key: str = None):
|
|
||||||
"""
|
|
||||||
Set the reference DAO for history resolution.
|
|
||||||
:param dao:
|
|
||||||
:param key: The key to use for resolving history.
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if key is None:
|
|
||||||
key = "id"
|
|
||||||
self._history_reference_daos[dao] = key
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.filter_abc import FilterABC
|
|
||||||
|
|
||||||
|
|
||||||
class FuzzyFilter(FilterABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
obj: Optional[dict],
|
|
||||||
):
|
|
||||||
FilterABC.__init__(self, obj)
|
|
||||||
|
|
||||||
self.add_field("fields", list)
|
|
||||||
self.add_field("term", str)
|
|
||||||
self.add_field("threshold", int)
|
|
@ -18,5 +18,3 @@ class IntFilter(FilterABC):
|
|||||||
self.add_field("lessOrEqual", int)
|
self.add_field("lessOrEqual", int)
|
||||||
self.add_field("isNull", int)
|
self.add_field("isNull", int)
|
||||||
self.add_field("isNotNull", int)
|
self.add_field("isNotNull", int)
|
||||||
self.add_field("in", list)
|
|
||||||
self.add_field("notIn", list)
|
|
||||||
|
@ -18,5 +18,3 @@ class StringFilter(FilterABC):
|
|||||||
self.add_field("endsWith", str)
|
self.add_field("endsWith", str)
|
||||||
self.add_field("isNull", str)
|
self.add_field("isNull", str)
|
||||||
self.add_field("isNotNull", str)
|
self.add_field("isNotNull", str)
|
||||||
self.add_field("in", list)
|
|
||||||
self.add_field("notIn", list)
|
|
||||||
|
@ -19,12 +19,10 @@ class FilterABC[T](ABC):
|
|||||||
def add_field(
|
def add_field(
|
||||||
self,
|
self,
|
||||||
field: str,
|
field: str,
|
||||||
filter_type: Union[
|
filter_type: Union[Type["FilterABC"], Type[Union[int, str, bool, datetime]]],
|
||||||
Type["FilterABC"], Type[Union[int, str, bool, datetime, list]]
|
|
||||||
],
|
|
||||||
db_name=None,
|
db_name=None,
|
||||||
):
|
):
|
||||||
if field not in self._obj:
|
if field not in self._obj and db_name not in self._obj:
|
||||||
return
|
return
|
||||||
|
|
||||||
if db_name is None:
|
if db_name is None:
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import Optional, Type, get_origin, get_args
|
from typing import Optional, Type, get_origin, get_args
|
||||||
|
|
||||||
from core.get_value import get_value
|
|
||||||
from core.typing import T
|
from core.typing import T
|
||||||
|
|
||||||
|
|
||||||
@ -13,15 +12,11 @@ class InputABC(ABC):
|
|||||||
ABC.__init__(self)
|
ABC.__init__(self)
|
||||||
self._src = src
|
self._src = src
|
||||||
|
|
||||||
self._options = {}
|
|
||||||
|
|
||||||
def option(
|
def option(
|
||||||
self, field: str, cast_type: Type[T], default=None, required=False
|
self, field: str, cast_type: Type[T], default=None, required=False
|
||||||
) -> Optional[T]:
|
) -> Optional[T]:
|
||||||
if required and field not in self._src:
|
if required and field not in self._src:
|
||||||
raise ValueError(f"{field} is required")
|
raise ValueError(f"{field} is required")
|
||||||
|
|
||||||
self._options[field] = cast_type
|
|
||||||
if field not in self._src:
|
if field not in self._src:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
@ -33,4 +28,4 @@ class InputABC(ABC):
|
|||||||
return cast_type(value)
|
return cast_type(value)
|
||||||
|
|
||||||
def get(self, field: str, default=None) -> Optional[T]:
|
def get(self, field: str, default=None) -> Optional[T]:
|
||||||
return get_value(self._src, field, self._options[field], default)
|
return self._src.get(field, default)
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Type, Union
|
|
||||||
|
|
||||||
from api_graphql.abc.input_abc import InputABC
|
|
||||||
from api_graphql.abc.query_abc import QueryABC
|
from api_graphql.abc.query_abc import QueryABC
|
||||||
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
||||||
from core.database.abc.data_access_object_abc import DataAccessObjectABC
|
|
||||||
from core.database.abc.db_join_model_abc import DbJoinModelABC
|
|
||||||
from core.typing import T
|
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
|
||||||
@ -46,79 +41,3 @@ class MutationABC(QueryABC):
|
|||||||
.with_require_any_permission(require_any_permission)
|
.with_require_any_permission(require_any_permission)
|
||||||
.with_public(public)
|
.with_public(public)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _resolve_assignments(
|
|
||||||
foreign_objs: list[int],
|
|
||||||
resolved_obj: T,
|
|
||||||
reference_key_own: Union[str, property],
|
|
||||||
reference_key_foreign: Union[str, property],
|
|
||||||
source_dao: DataAccessObjectABC[T],
|
|
||||||
join_dao: DataAccessObjectABC[T],
|
|
||||||
join_type: Type[DbJoinModelABC],
|
|
||||||
foreign_dao: DataAccessObjectABC[T],
|
|
||||||
):
|
|
||||||
if foreign_objs is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
reference_key_own_attr = reference_key_own
|
|
||||||
if isinstance(reference_key_own, property):
|
|
||||||
reference_key_own_attr = reference_key_own.fget.__name__
|
|
||||||
|
|
||||||
reference_key_foreign_attr = reference_key_foreign
|
|
||||||
if isinstance(reference_key_foreign, property):
|
|
||||||
reference_key_foreign_attr = reference_key_foreign.fget.__name__
|
|
||||||
|
|
||||||
foreign_list = await join_dao.find_by(
|
|
||||||
[{reference_key_own: resolved_obj.id}, {"deleted": False}]
|
|
||||||
)
|
|
||||||
|
|
||||||
to_delete = (
|
|
||||||
foreign_list
|
|
||||||
if len(foreign_objs) == 0
|
|
||||||
else await join_dao.find_by(
|
|
||||||
[
|
|
||||||
{reference_key_own: resolved_obj.id},
|
|
||||||
{reference_key_foreign: {"notIn": foreign_objs}},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
foreign_ids = [getattr(x, reference_key_foreign_attr) for x in foreign_list]
|
|
||||||
deleted_foreign_ids = [
|
|
||||||
getattr(x, reference_key_foreign_attr)
|
|
||||||
for x in await join_dao.find_by(
|
|
||||||
[{reference_key_own: resolved_obj.id}, {"deleted": True}]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
to_create = [
|
|
||||||
join_type(0, resolved_obj.id, x)
|
|
||||||
for x in foreign_objs
|
|
||||||
if x not in foreign_ids and x not in deleted_foreign_ids
|
|
||||||
]
|
|
||||||
to_restore = [
|
|
||||||
await join_dao.get_single_by(
|
|
||||||
[
|
|
||||||
{reference_key_own: resolved_obj.id},
|
|
||||||
{reference_key_foreign: x},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
for x in foreign_objs
|
|
||||||
if x not in foreign_ids and x in deleted_foreign_ids
|
|
||||||
]
|
|
||||||
|
|
||||||
if len(to_delete) > 0:
|
|
||||||
await join_dao.delete_many(to_delete)
|
|
||||||
|
|
||||||
if len(to_create) > 0:
|
|
||||||
await join_dao.create_many(to_create)
|
|
||||||
|
|
||||||
if len(to_restore) > 0:
|
|
||||||
await join_dao.restore_many(to_restore)
|
|
||||||
|
|
||||||
foreign_changes = [*to_delete, *to_create, *to_restore]
|
|
||||||
if len(foreign_changes) > 0:
|
|
||||||
await source_dao.touch(resolved_obj)
|
|
||||||
await foreign_dao.touch_many_by_id(
|
|
||||||
[getattr(x, reference_key_foreign_attr) for x in foreign_changes]
|
|
||||||
)
|
|
||||||
|
@ -6,12 +6,11 @@ from typing import Callable, Type, get_args, Any, Union
|
|||||||
|
|
||||||
from ariadne import ObjectType, SubscriptionType
|
from ariadne import ObjectType, SubscriptionType
|
||||||
from graphql import GraphQLResolveInfo
|
from graphql import GraphQLResolveInfo
|
||||||
from starlette.requests import Request
|
|
||||||
from typing_extensions import deprecated
|
from typing_extensions import deprecated
|
||||||
|
|
||||||
from api.middleware.request import get_request
|
|
||||||
from api.route import Route
|
from api.route import Route
|
||||||
from api_graphql.abc.collection_filter_abc import CollectionFilterABC
|
from api_graphql.abc.collection_filter_abc import CollectionFilterABC
|
||||||
|
from api_graphql.abc.field_abc import FieldABC
|
||||||
from api_graphql.abc.input_abc import InputABC
|
from api_graphql.abc.input_abc import InputABC
|
||||||
from api_graphql.abc.sort_abc import Sort
|
from api_graphql.abc.sort_abc import Sort
|
||||||
from api_graphql.field.collection_field import CollectionField
|
from api_graphql.field.collection_field import CollectionField
|
||||||
@ -47,8 +46,8 @@ class QueryABC(ObjectType):
|
|||||||
self._subscriptions: dict[str, SubscriptionType] = {}
|
self._subscriptions: dict[str, SubscriptionType] = {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _authorize(request: Request):
|
async def _authorize():
|
||||||
if not await Route.is_authorized(request):
|
if not await Route.is_authorized():
|
||||||
raise UnauthorizedException()
|
raise UnauthorizedException()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -72,6 +71,8 @@ class QueryABC(ObjectType):
|
|||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
info = args[0]
|
||||||
|
|
||||||
if len(permissions) > 0:
|
if len(permissions) > 0:
|
||||||
user = await Route.get_authenticated_user_or_api_key_or_default()
|
user = await Route.get_authenticated_user_or_api_key_or_default()
|
||||||
if user is not None and all(
|
if user is not None and all(
|
||||||
@ -119,9 +120,6 @@ class QueryABC(ObjectType):
|
|||||||
take = None
|
take = None
|
||||||
skip = None
|
skip = None
|
||||||
|
|
||||||
if field.default_filter:
|
|
||||||
filters.append(field.default_filter(*args, **kwargs))
|
|
||||||
|
|
||||||
if field.filter_type and "filter" in kwargs:
|
if field.filter_type and "filter" in kwargs:
|
||||||
in_filters = kwargs["filter"]
|
in_filters = kwargs["filter"]
|
||||||
if not isinstance(in_filters, list):
|
if not isinstance(in_filters, list):
|
||||||
@ -229,7 +227,7 @@ class QueryABC(ObjectType):
|
|||||||
|
|
||||||
async def wrapper(*args, **kwargs):
|
async def wrapper(*args, **kwargs):
|
||||||
if not field.public:
|
if not field.public:
|
||||||
await self._authorize(get_request())
|
await self._authorize()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
field.require_any is None
|
field.require_any is None
|
||||||
|
@ -2,8 +2,8 @@ from abc import abstractmethod
|
|||||||
from asyncio import iscoroutinefunction
|
from asyncio import iscoroutinefunction
|
||||||
|
|
||||||
from ariadne import SubscriptionType
|
from ariadne import SubscriptionType
|
||||||
from graphql import GraphQLResolveInfo
|
|
||||||
|
|
||||||
|
from api.middleware.request import get_request
|
||||||
from api_graphql.abc.query_abc import QueryABC
|
from api_graphql.abc.query_abc import QueryABC
|
||||||
from api_graphql.field.subscription_field_builder import SubscriptionFieldBuilder
|
from api_graphql.field.subscription_field_builder import SubscriptionFieldBuilder
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
@ -20,12 +20,9 @@ class SubscriptionABC(SubscriptionType, QueryABC):
|
|||||||
def subscribe(self, builder: SubscriptionFieldBuilder):
|
def subscribe(self, builder: SubscriptionFieldBuilder):
|
||||||
field = builder.build()
|
field = builder.build()
|
||||||
|
|
||||||
async def wrapper(_, info: GraphQLResolveInfo, *args, **kwargs):
|
async def wrapper(*args, **kwargs):
|
||||||
# rebuild args for resolvers
|
|
||||||
args = [_, info, *args]
|
|
||||||
if not field.public:
|
if not field.public:
|
||||||
r = info.context.get("request")
|
await self._authorize()
|
||||||
await self._authorize(r)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
field.require_any is None
|
field.require_any is None
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
|
||||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
from api_graphql.abc.mutation_abc import MutationABC
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
from api_graphql.abc.query_abc import QueryABC
|
from api_graphql.abc.query_abc import QueryABC
|
||||||
@ -21,12 +20,7 @@ def import_graphql_schema_part(part: str):
|
|||||||
import_graphql_schema_part("queries")
|
import_graphql_schema_part("queries")
|
||||||
import_graphql_schema_part("mutations")
|
import_graphql_schema_part("mutations")
|
||||||
|
|
||||||
sub_query_classes = [
|
sub_query_classes = [DbModelQueryABC, MutationABC, SubscriptionABC]
|
||||||
DbModelQueryABC,
|
|
||||||
DbHistoryModelQueryABC,
|
|
||||||
MutationABC,
|
|
||||||
SubscriptionABC,
|
|
||||||
]
|
|
||||||
query_classes = [
|
query_classes = [
|
||||||
*[y for x in sub_query_classes for y in x.__subclasses__()],
|
*[y for x in sub_query_classes for y in x.__subclasses__()],
|
||||||
*[x for x in QueryABC.__subclasses__() if x not in sub_query_classes],
|
*[x for x in QueryABC.__subclasses__() if x not in sub_query_classes],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Union, Type, Optional, Callable
|
from typing import Union, Type, Optional
|
||||||
|
|
||||||
from api_graphql.abc.collection_filter_abc import CollectionFilterABC
|
from api_graphql.abc.collection_filter_abc import CollectionFilterABC
|
||||||
from api_graphql.abc.field_abc import FieldABC
|
from api_graphql.abc.field_abc import FieldABC
|
||||||
@ -19,7 +19,6 @@ class DaoField(FieldABC):
|
|||||||
public: bool = False,
|
public: bool = False,
|
||||||
dao: DataAccessObjectABC = None,
|
dao: DataAccessObjectABC = None,
|
||||||
filter_type: Type[FilterABC] = None,
|
filter_type: Type[FilterABC] = None,
|
||||||
default_filter: Callable = None,
|
|
||||||
sort_type: Type[T] = None,
|
sort_type: Type[T] = None,
|
||||||
direct_result: bool = False,
|
direct_result: bool = False,
|
||||||
):
|
):
|
||||||
@ -29,7 +28,6 @@ class DaoField(FieldABC):
|
|||||||
self._public = public
|
self._public = public
|
||||||
self._dao = dao
|
self._dao = dao
|
||||||
self._filter_type = filter_type
|
self._filter_type = filter_type
|
||||||
self._default_filter = default_filter
|
|
||||||
self._sort_type = sort_type
|
self._sort_type = sort_type
|
||||||
self._direct_result = direct_result
|
self._direct_result = direct_result
|
||||||
|
|
||||||
@ -43,10 +41,6 @@ class DaoField(FieldABC):
|
|||||||
) -> Optional[Type[FilterABC]]:
|
) -> Optional[Type[FilterABC]]:
|
||||||
return self._filter_type
|
return self._filter_type
|
||||||
|
|
||||||
@property
|
|
||||||
def default_filter(self) -> Optional[Callable]:
|
|
||||||
return self._default_filter
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sort_type(self) -> Optional[Type[T]]:
|
def sort_type(self) -> Optional[Type[T]]:
|
||||||
return self._sort_type
|
return self._sort_type
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Type, Self, Callable
|
from typing import Type, Self
|
||||||
|
|
||||||
from api_graphql.abc.field_builder_abc import FieldBuilderABC
|
from api_graphql.abc.field_builder_abc import FieldBuilderABC
|
||||||
from api_graphql.abc.filter_abc import FilterABC
|
from api_graphql.abc.filter_abc import FilterABC
|
||||||
@ -14,7 +14,6 @@ class DaoFieldBuilder(FieldBuilderABC):
|
|||||||
|
|
||||||
self._dao = None
|
self._dao = None
|
||||||
self._filter_type = None
|
self._filter_type = None
|
||||||
self._default_filter = None
|
|
||||||
self._sort_type = None
|
self._sort_type = None
|
||||||
self._direct_result = False
|
self._direct_result = False
|
||||||
|
|
||||||
@ -28,12 +27,6 @@ class DaoFieldBuilder(FieldBuilderABC):
|
|||||||
self._filter_type = filter_type
|
self._filter_type = filter_type
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def with_default_filter(self, filter: Callable) -> Self:
|
|
||||||
assert filter is not None, "filter cannot be None"
|
|
||||||
assert callable(filter), "filter must be callable"
|
|
||||||
self._default_filter = filter
|
|
||||||
return self
|
|
||||||
|
|
||||||
def with_sort(self, sort_type: Type[T]) -> Self:
|
def with_sort(self, sort_type: Type[T]) -> Self:
|
||||||
assert sort_type is not None, "sort cannot be None"
|
assert sort_type is not None, "sort cannot be None"
|
||||||
self._sort_type = sort_type
|
self._sort_type = sort_type
|
||||||
@ -52,7 +45,6 @@ class DaoFieldBuilder(FieldBuilderABC):
|
|||||||
self._public,
|
self._public,
|
||||||
self._dao,
|
self._dao,
|
||||||
self._filter_type,
|
self._filter_type,
|
||||||
self._default_filter,
|
|
||||||
self._sort_type,
|
self._sort_type,
|
||||||
self._direct_result,
|
self._direct_result,
|
||||||
)
|
)
|
||||||
|
@ -38,9 +38,6 @@ class MutationFieldBuilder(FieldBuilderABC):
|
|||||||
await broadcast.publish(f"{source}", result)
|
await broadcast.publish(f"{source}", result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
self._resolver = resolver_wrapper
|
|
||||||
return self
|
|
||||||
|
|
||||||
def with_change_broadcast(self, source: str):
|
def with_change_broadcast(self, source: str):
|
||||||
assert self._resolver is not None, "resolver cannot be None for broadcast"
|
assert self._resolver is not None, "resolver cannot be None for broadcast"
|
||||||
|
|
||||||
|
@ -4,30 +4,16 @@ type ApiKeyResult {
|
|||||||
nodes: [ApiKey]
|
nodes: [ApiKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiKeyHistory implements DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
identifier: String
|
|
||||||
key: String
|
|
||||||
permissions: [Permission]
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type ApiKey implements DbModel {
|
type ApiKey implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
identifier: String
|
identifier: String
|
||||||
key: String
|
key: String
|
||||||
permissions: [Permission]
|
permissions: [Permission]
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
|
|
||||||
history: [ApiKeyHistory]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input ApiKeySort {
|
input ApiKeySort {
|
||||||
@ -36,18 +22,12 @@ input ApiKeySort {
|
|||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editor: UserSort
|
editor: UserSort
|
||||||
created: SortOrder
|
createdUtc: SortOrder
|
||||||
updated: SortOrder
|
updatedUtc: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ApiKeyFuzzyFields {
|
enum ApiKeyFuzzyFields {
|
||||||
id
|
|
||||||
identifier
|
identifier
|
||||||
|
|
||||||
deleted
|
|
||||||
editor
|
|
||||||
created
|
|
||||||
updated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input ApiKeyFuzzy {
|
input ApiKeyFuzzy {
|
||||||
@ -62,24 +42,24 @@ input ApiKeyFilter {
|
|||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editorId: IntFilter
|
editorId: IntFilter
|
||||||
created: DateFilter
|
createdUtc: DateFilter
|
||||||
updated: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiKeyMutation {
|
type ApiKeyMutation {
|
||||||
create(input: ApiKeyCreateInput!): ApiKey
|
create(input: ApiKeyCreateInput!): ApiKey
|
||||||
update(input: ApiKeyUpdateInput!): ApiKey
|
update(input: ApiKeyUpdateInput!): ApiKey
|
||||||
delete(id: Int!): Boolean
|
delete(identifier: String!): Boolean
|
||||||
restore(id: Int!): Boolean
|
restore(identifier: String!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input ApiKeyCreateInput {
|
input ApiKeyCreateInput {
|
||||||
identifier: String
|
identifier: String
|
||||||
permissions: [Int]
|
permissions: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
input ApiKeyUpdateInput {
|
input ApiKeyUpdateInput {
|
||||||
id: Int!
|
id: ID!
|
||||||
identifier: String
|
identifier: String
|
||||||
permissions: [Int]
|
permissions: [ID]
|
||||||
}
|
}
|
@ -1,21 +1,12 @@
|
|||||||
scalar Upload
|
scalar Upload
|
||||||
|
|
||||||
interface DbModel {
|
interface DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
}
|
|
||||||
|
|
||||||
interface DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SortOrder {
|
enum SortOrder {
|
||||||
@ -53,8 +44,6 @@ input IntFilter {
|
|||||||
|
|
||||||
isNull: Int
|
isNull: Int
|
||||||
isNotNull: Int
|
isNotNull: Int
|
||||||
in: [Int]
|
|
||||||
notIn: [Int]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input BooleanFilter {
|
input BooleanFilter {
|
||||||
@ -69,18 +58,9 @@ input DateFilter {
|
|||||||
equal: String
|
equal: String
|
||||||
notEqual: String
|
notEqual: String
|
||||||
|
|
||||||
greater: String
|
|
||||||
greaterOrEqual: String
|
|
||||||
|
|
||||||
less: String
|
|
||||||
lessOrEqual: String
|
|
||||||
|
|
||||||
contains: String
|
contains: String
|
||||||
notContains: String
|
notContains: String
|
||||||
|
|
||||||
isNull: String
|
isNull: String
|
||||||
isNotNull: String
|
isNotNull: String
|
||||||
|
|
||||||
in: [String]
|
|
||||||
notIn: [String]
|
|
||||||
}
|
}
|
@ -1,12 +0,0 @@
|
|||||||
enum Attendance {
|
|
||||||
absent
|
|
||||||
present
|
|
||||||
delayed
|
|
||||||
canceled
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Payment {
|
|
||||||
not_paid
|
|
||||||
paid
|
|
||||||
refunded
|
|
||||||
}
|
|
@ -4,30 +4,16 @@ type DomainResult {
|
|||||||
nodes: [Domain]
|
nodes: [Domain]
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainHistory implements DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
name: String
|
|
||||||
|
|
||||||
shortUrls: [ShortUrl]
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Domain implements DbModel {
|
type Domain implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
name: String
|
name: String
|
||||||
|
|
||||||
shortUrls: [ShortUrl]
|
shortUrls: [ShortUrl]
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
|
|
||||||
history: [DomainHistory]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input DomainSort {
|
input DomainSort {
|
||||||
@ -36,8 +22,8 @@ input DomainSort {
|
|||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editorId: SortOrder
|
editorId: SortOrder
|
||||||
created: SortOrder
|
createdUtc: SortOrder
|
||||||
updated: SortOrder
|
updatedUtc: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DomainFuzzyFields {
|
enum DomainFuzzyFields {
|
||||||
@ -58,15 +44,15 @@ input DomainFilter {
|
|||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editor: IntFilter
|
editor: IntFilter
|
||||||
created: DateFilter
|
createdUtc: DateFilter
|
||||||
updated: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainMutation {
|
type DomainMutation {
|
||||||
create(input: DomainCreateInput!): Domain
|
create(input: DomainCreateInput!): Domain
|
||||||
update(input: DomainUpdateInput!): Domain
|
update(input: DomainUpdateInput!): Domain
|
||||||
delete(id: Int!): Boolean
|
delete(id: ID!): Boolean
|
||||||
restore(id: Int!): Boolean
|
restore(id: ID!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input DomainCreateInput {
|
input DomainCreateInput {
|
||||||
@ -74,6 +60,6 @@ input DomainCreateInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input DomainUpdateInput {
|
input DomainUpdateInput {
|
||||||
id: Int!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
type FeatureFlag implements DbModel {
|
type FeatureFlag implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
key: String
|
key: String
|
||||||
value: Boolean
|
value: Boolean
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeatureFlagMutation {
|
type FeatureFlagMutation {
|
||||||
|
@ -4,21 +4,8 @@ type GroupResult {
|
|||||||
nodes: [Group]
|
nodes: [Group]
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupHistory implements DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
name: String
|
|
||||||
|
|
||||||
shortUrls: [ShortUrl]
|
|
||||||
roles: [Role]
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Group implements DbModel {
|
type Group implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
name: String
|
name: String
|
||||||
|
|
||||||
shortUrls: [ShortUrl]
|
shortUrls: [ShortUrl]
|
||||||
@ -26,9 +13,8 @@ type Group implements DbModel {
|
|||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
history: [GroupHistory]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input GroupSort {
|
input GroupSort {
|
||||||
@ -37,8 +23,8 @@ input GroupSort {
|
|||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editorId: SortOrder
|
editorId: SortOrder
|
||||||
created: SortOrder
|
createdUtc: SortOrder
|
||||||
updated: SortOrder
|
updatedUtc: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GroupFuzzyFields {
|
enum GroupFuzzyFields {
|
||||||
@ -59,24 +45,24 @@ input GroupFilter {
|
|||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editor: IntFilter
|
editor: IntFilter
|
||||||
created: DateFilter
|
createdUtc: DateFilter
|
||||||
updated: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupMutation {
|
type GroupMutation {
|
||||||
create(input: GroupCreateInput!): Group
|
create(input: GroupCreateInput!): Group
|
||||||
update(input: GroupUpdateInput!): Group
|
update(input: GroupUpdateInput!): Group
|
||||||
delete(id: Int!): Boolean
|
delete(id: ID!): Boolean
|
||||||
restore(id: Int!): Boolean
|
restore(id: ID!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input GroupCreateInput {
|
input GroupCreateInput {
|
||||||
name: String!
|
name: String!
|
||||||
roles: [Int]
|
roles: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
input GroupUpdateInput {
|
input GroupUpdateInput {
|
||||||
id: Int!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
roles: [Int]
|
roles: [ID]
|
||||||
}
|
}
|
@ -4,28 +4,15 @@ type PermissionResult {
|
|||||||
nodes: [Permission]
|
nodes: [Permission]
|
||||||
}
|
}
|
||||||
|
|
||||||
type PermissionHistory implements DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
name: String
|
|
||||||
description: String
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Permission implements DbModel {
|
type Permission implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
name: String
|
name: String
|
||||||
description: String
|
description: String
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
|
|
||||||
history: [PermissionHistory]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input PermissionSort {
|
input PermissionSort {
|
||||||
@ -34,9 +21,9 @@ input PermissionSort {
|
|||||||
description: SortOrder
|
description: SortOrder
|
||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editor: UserSort
|
editorId: SortOrder
|
||||||
created: SortOrder
|
createdUtc: SortOrder
|
||||||
updated: SortOrder
|
updatedUtc: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
input PermissionFilter {
|
input PermissionFilter {
|
||||||
@ -45,13 +32,13 @@ input PermissionFilter {
|
|||||||
description: StringFilter
|
description: StringFilter
|
||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editor: UserFilter
|
editor: IntFilter
|
||||||
created: DateFilter
|
createdUtc: DateFilter
|
||||||
updated: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
input PermissionInput {
|
input PermissionInput {
|
||||||
id: Int
|
id: ID
|
||||||
name: String
|
name: String
|
||||||
description: String
|
description: String
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ type Query {
|
|||||||
user: User
|
user: User
|
||||||
userHasPermission(permission: String!): Boolean
|
userHasPermission(permission: String!): Boolean
|
||||||
userHasAnyPermission(permissions: [String]!): Boolean
|
userHasAnyPermission(permissions: [String]!): Boolean
|
||||||
notExistingUsersFromKeycloak: [KeycloakUser]
|
notExistingUsersFromKeycloak: KeycloakUserResult
|
||||||
|
|
||||||
domains(filter: [DomainFilter], sort: [DomainSort], skip: Int, take: Int): DomainResult
|
domains(filter: [DomainFilter], sort: [DomainSort], skip: Int, take: Int): DomainResult
|
||||||
groups(filter: [GroupFilter], sort: [GroupSort], skip: Int, take: Int): GroupResult
|
groups(filter: [GroupFilter], sort: [GroupSort], skip: Int, take: Int): GroupResult
|
||||||
|
@ -4,21 +4,8 @@ type RoleResult {
|
|||||||
nodes: [Role]
|
nodes: [Role]
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoleHistory implements DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
name: String
|
|
||||||
description: String
|
|
||||||
|
|
||||||
permissions: [Permission]
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Role implements DbModel {
|
type Role implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
name: String
|
name: String
|
||||||
description: String
|
description: String
|
||||||
permissions: [Permission]
|
permissions: [Permission]
|
||||||
@ -26,10 +13,8 @@ type Role implements DbModel {
|
|||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
|
|
||||||
history: [RoleHistory]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input RoleSort {
|
input RoleSort {
|
||||||
@ -39,19 +24,13 @@ input RoleSort {
|
|||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editor: UserSort
|
editor: UserSort
|
||||||
created: SortOrder
|
createdUtc: SortOrder
|
||||||
updated: SortOrder
|
updatedUtc: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoleFuzzyFields {
|
enum RoleFuzzyFields {
|
||||||
id
|
|
||||||
name
|
name
|
||||||
description
|
description
|
||||||
|
|
||||||
deleted
|
|
||||||
editor
|
|
||||||
created
|
|
||||||
updated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input RoleFuzzy {
|
input RoleFuzzy {
|
||||||
@ -69,26 +48,26 @@ input RoleFilter {
|
|||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editor_id: IntFilter
|
editor_id: IntFilter
|
||||||
created: DateFilter
|
createdUtc: DateFilter
|
||||||
updated: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoleMutation {
|
type RoleMutation {
|
||||||
create(input: RoleCreateInput!): Role
|
create(input: RoleCreateInput!): Role
|
||||||
update(input: RoleUpdateInput!): Role
|
update(input: RoleUpdateInput!): Role
|
||||||
delete(id: Int!): Boolean
|
delete(id: ID!): Boolean
|
||||||
restore(id: Int!): Boolean
|
restore(id: ID!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input RoleCreateInput {
|
input RoleCreateInput {
|
||||||
name: String!
|
name: String!
|
||||||
description: String
|
description: String
|
||||||
permissions: [Int]
|
permissions: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
input RoleUpdateInput {
|
input RoleUpdateInput {
|
||||||
id: Int!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
description: String
|
description: String
|
||||||
permissions: [Int]
|
permissions: [ID]
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
type Setting implements DbModel {
|
type Setting implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
key: String
|
key: String
|
||||||
value: String
|
value: String
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingMutation {
|
type SettingMutation {
|
||||||
|
@ -4,25 +4,8 @@ type ShortUrlResult {
|
|||||||
nodes: [ShortUrl]
|
nodes: [ShortUrl]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShortUrlHistory implements DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
shortUrl: String
|
|
||||||
targetUrl: String
|
|
||||||
description: String
|
|
||||||
visits: Int
|
|
||||||
loadingScreen: Boolean
|
|
||||||
|
|
||||||
group: Group
|
|
||||||
domain: Domain
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShortUrl implements DbModel {
|
type ShortUrl implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
shortUrl: String
|
shortUrl: String
|
||||||
targetUrl: String
|
targetUrl: String
|
||||||
description: String
|
description: String
|
||||||
@ -33,9 +16,8 @@ type ShortUrl implements DbModel {
|
|||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
history: [ShortUrlHistory]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input ShortUrlSort {
|
input ShortUrlSort {
|
||||||
@ -46,8 +28,8 @@ input ShortUrlSort {
|
|||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editorId: SortOrder
|
editorId: SortOrder
|
||||||
created: SortOrder
|
createdUtc: SortOrder
|
||||||
updated: SortOrder
|
updatedUtc: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ShortUrlFuzzyFields {
|
enum ShortUrlFuzzyFields {
|
||||||
@ -75,33 +57,33 @@ input ShortUrlFilter {
|
|||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editor: IntFilter
|
editor: IntFilter
|
||||||
created: DateFilter
|
createdUtc: DateFilter
|
||||||
updated: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShortUrlMutation {
|
type ShortUrlMutation {
|
||||||
create(input: ShortUrlCreateInput!): ShortUrl
|
create(input: ShortUrlCreateInput!): ShortUrl
|
||||||
update(input: ShortUrlUpdateInput!): ShortUrl
|
update(input: ShortUrlUpdateInput!): ShortUrl
|
||||||
delete(id: Int!): Boolean
|
delete(id: ID!): Boolean
|
||||||
restore(id: Int!): Boolean
|
restore(id: ID!): Boolean
|
||||||
trackVisit(id: Int!, agent: String): Boolean
|
trackVisit(id: ID!, agent: String): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input ShortUrlCreateInput {
|
input ShortUrlCreateInput {
|
||||||
shortUrl: String!
|
shortUrl: String!
|
||||||
targetUrl: String!
|
targetUrl: String!
|
||||||
description: String
|
description: String
|
||||||
groupId: Int
|
groupId: ID
|
||||||
domainId: Int
|
domainId: ID
|
||||||
loadingScreen: Boolean
|
loadingScreen: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input ShortUrlUpdateInput {
|
input ShortUrlUpdateInput {
|
||||||
id: Int!
|
id: ID!
|
||||||
shortUrl: String
|
shortUrl: String
|
||||||
targetUrl: String
|
targetUrl: String
|
||||||
description: String
|
description: String
|
||||||
groupId: Int
|
groupId: ID
|
||||||
domainId: Int
|
domainId: ID
|
||||||
loadingScreen: Boolean
|
loadingScreen: Boolean
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ type Subscription {
|
|||||||
settingChange: SubscriptionChange
|
settingChange: SubscriptionChange
|
||||||
userChange: SubscriptionChange
|
userChange: SubscriptionChange
|
||||||
userSettingChange: SubscriptionChange
|
userSettingChange: SubscriptionChange
|
||||||
userLogout: SubscriptionChange
|
|
||||||
|
|
||||||
domainChange: SubscriptionChange
|
domainChange: SubscriptionChange
|
||||||
groupChange: SubscriptionChange
|
groupChange: SubscriptionChange
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
type KeycloakUserResult {
|
||||||
|
totalCount: Int
|
||||||
|
count: Int
|
||||||
|
nodes: [KeycloakUser]
|
||||||
|
}
|
||||||
|
|
||||||
type KeycloakUser {
|
type KeycloakUser {
|
||||||
keycloakId: String
|
keycloakId: String
|
||||||
username: String
|
username: String
|
||||||
@ -9,22 +15,8 @@ type UserResult {
|
|||||||
nodes: [User]
|
nodes: [User]
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserHistory implements DbHistoryModel {
|
|
||||||
id: Int
|
|
||||||
keycloakId: String
|
|
||||||
username: String
|
|
||||||
email: String
|
|
||||||
|
|
||||||
roles: [Role]
|
|
||||||
|
|
||||||
deleted: Boolean
|
|
||||||
editor: String
|
|
||||||
created: String
|
|
||||||
updated: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type User implements DbModel {
|
type User implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
keycloakId: String
|
keycloakId: String
|
||||||
username: String
|
username: String
|
||||||
email: String
|
email: String
|
||||||
@ -32,10 +24,8 @@ type User implements DbModel {
|
|||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
|
|
||||||
history: [UserHistory]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input UserSort {
|
input UserSort {
|
||||||
@ -46,20 +36,14 @@ input UserSort {
|
|||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editor: UserSort
|
editor: UserSort
|
||||||
created: SortOrder
|
createdUtc: SortOrder
|
||||||
updated: SortOrder
|
updatedUtc: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UserFuzzyFields {
|
enum UserFuzzyFields {
|
||||||
id
|
|
||||||
keycloakId
|
keycloakId
|
||||||
username
|
username
|
||||||
email
|
email
|
||||||
|
|
||||||
deleted
|
|
||||||
editor
|
|
||||||
created
|
|
||||||
updated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input UserFuzzy {
|
input UserFuzzy {
|
||||||
@ -78,23 +62,23 @@ input UserFilter {
|
|||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editor: UserFilter
|
editor: UserFilter
|
||||||
created: DateFilter
|
createdUtc: DateFilter
|
||||||
updated: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserMutation {
|
type UserMutation {
|
||||||
create(input: UserCreateInput!): User
|
create(input: UserCreateInput!): User
|
||||||
update(input: UserUpdateInput!): User
|
update(input: UserUpdateInput!): User
|
||||||
delete(id: Int!): Boolean
|
delete(id: ID!): Boolean
|
||||||
restore(id: Int!): Boolean
|
restore(id: ID!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input UserCreateInput {
|
input UserCreateInput {
|
||||||
keycloakId: String
|
keycloakId: String
|
||||||
roles: [Int]
|
roles: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
input UserUpdateInput {
|
input UserUpdateInput {
|
||||||
id: Int
|
id: ID
|
||||||
roles: [Int]
|
roles: [ID]
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
type UserSetting implements DbModel {
|
type UserSetting implements DbModel {
|
||||||
id: Int
|
id: ID
|
||||||
key: String
|
key: String
|
||||||
value: String
|
value: String
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
created: String
|
createdUtc: String
|
||||||
updated: String
|
updatedUtc: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSettingMutation {
|
type UserSettingMutation {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from api_graphql.abc.mutation_abc import MutationABC
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
from api_graphql.input.api_key_create_input import ApiKeyCreateInput
|
from api_graphql.input.api_key_create_input import ApiKeyCreateInput
|
||||||
from api_graphql.input.api_key_update_input import ApiKeyUpdateInput
|
from api_graphql.input.api_key_update_input import ApiKeyUpdateInput
|
||||||
@ -6,7 +8,6 @@ from data.schemas.administration.api_key import ApiKey
|
|||||||
from data.schemas.administration.api_key_dao import apiKeyDao
|
from data.schemas.administration.api_key_dao import apiKeyDao
|
||||||
from data.schemas.permission.api_key_permission import ApiKeyPermission
|
from data.schemas.permission.api_key_permission import ApiKeyPermission
|
||||||
from data.schemas.permission.api_key_permission_dao import apiKeyPermissionDao
|
from data.schemas.permission.api_key_permission_dao import apiKeyPermissionDao
|
||||||
from data.schemas.permission.permission_dao import permissionDao
|
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
logger = APILogger(__name__)
|
logger = APILogger(__name__)
|
||||||
@ -43,28 +44,77 @@ class APIKeyMutation(MutationABC):
|
|||||||
async def resolve_create(obj: ApiKeyCreateInput, *_):
|
async def resolve_create(obj: ApiKeyCreateInput, *_):
|
||||||
logger.debug(f"create api key: {obj.__dict__}")
|
logger.debug(f"create api key: {obj.__dict__}")
|
||||||
|
|
||||||
api_key = ApiKey.new(obj.identifier)
|
api_key = ApiKey(
|
||||||
|
0,
|
||||||
|
obj.identifier,
|
||||||
|
str(uuid4()),
|
||||||
|
)
|
||||||
await apiKeyDao.create(api_key)
|
await apiKeyDao.create(api_key)
|
||||||
api_key = await apiKeyDao.get_single_by([{ApiKey.identifier: obj.identifier}])
|
api_key = await apiKeyDao.get_by_identifier(api_key.identifier)
|
||||||
await apiKeyPermissionDao.create_many(
|
await apiKeyPermissionDao.create_many(
|
||||||
[ApiKeyPermission(0, api_key.id, x) for x in obj.permissions]
|
[ApiKeyPermission(0, api_key.id, x) for x in obj.permissions]
|
||||||
)
|
)
|
||||||
return api_key
|
return api_key
|
||||||
|
|
||||||
async def resolve_update(self, obj: ApiKeyUpdateInput, *_):
|
@staticmethod
|
||||||
|
async def resolve_update(obj: ApiKeyUpdateInput, *_):
|
||||||
logger.debug(f"update api key: {input}")
|
logger.debug(f"update api key: {input}")
|
||||||
api_key = await apiKeyDao.get_by_id(obj.id)
|
api_key = await apiKeyDao.get_by_id(obj.id)
|
||||||
|
|
||||||
await self._resolve_assignments(
|
if obj.permissions is not None:
|
||||||
obj.get("permissions", []),
|
permissions = [
|
||||||
api_key,
|
x for x in await apiKeyPermissionDao.get_by_role_id(api_key.id)
|
||||||
ApiKeyPermission.api_key_id,
|
]
|
||||||
ApiKeyPermission.permission_id,
|
|
||||||
apiKeyDao,
|
to_delete = (
|
||||||
apiKeyPermissionDao,
|
permissions
|
||||||
ApiKeyPermission,
|
if len(obj.permissions) == 0
|
||||||
permissionDao,
|
else await apiKeyPermissionDao.find_by(
|
||||||
|
[
|
||||||
|
{ApiKeyPermission.api_key_id: api_key.id},
|
||||||
|
{
|
||||||
|
ApiKeyPermission.permission_id: {
|
||||||
|
"notIn": obj.get("permissions", [])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
permission_ids = [x.permission_id for x in permissions]
|
||||||
|
deleted_permission_ids = [
|
||||||
|
x.permission_id
|
||||||
|
for x in await apiKeyPermissionDao.find_by(
|
||||||
|
[
|
||||||
|
{ApiKeyPermission.api_key_id: api_key.id},
|
||||||
|
{ApiKeyPermission.deleted: True},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
to_create = [
|
||||||
|
ApiKeyPermission(0, api_key.id, x)
|
||||||
|
for x in obj.permissions
|
||||||
|
if x not in permission_ids and x not in deleted_permission_ids
|
||||||
|
]
|
||||||
|
to_restore = [
|
||||||
|
await apiKeyPermissionDao.get_single_by(
|
||||||
|
[
|
||||||
|
{ApiKeyPermission.api_key_id: api_key.id},
|
||||||
|
{ApiKeyPermission.permission_id: x},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for x in obj.permissions
|
||||||
|
if x not in permission_ids and x in deleted_permission_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(to_delete) > 0:
|
||||||
|
await apiKeyPermissionDao.delete_many(to_delete)
|
||||||
|
|
||||||
|
if len(to_create) > 0:
|
||||||
|
await apiKeyPermissionDao.create_many(to_create)
|
||||||
|
|
||||||
|
if len(to_restore) > 0:
|
||||||
|
await apiKeyPermissionDao.restore_many(to_restore)
|
||||||
|
|
||||||
return api_key
|
return api_key
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ from api_graphql.abc.mutation_abc import MutationABC
|
|||||||
from api_graphql.input.role_create_input import RoleCreateInput
|
from api_graphql.input.role_create_input import RoleCreateInput
|
||||||
from api_graphql.input.role_update_input import RoleUpdateInput
|
from api_graphql.input.role_update_input import RoleUpdateInput
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
from data.schemas.permission.permission_dao import permissionDao
|
|
||||||
from data.schemas.permission.role import Role
|
from data.schemas.permission.role import Role
|
||||||
from data.schemas.permission.role_dao import roleDao
|
from data.schemas.permission.role_dao import roleDao
|
||||||
from data.schemas.permission.role_permission import RolePermission
|
from data.schemas.permission.role_permission import RolePermission
|
||||||
@ -55,23 +54,63 @@ class RoleMutation(MutationABC):
|
|||||||
|
|
||||||
return role
|
return role
|
||||||
|
|
||||||
async def resolve_update(self, obj: RoleUpdateInput, *_):
|
@staticmethod
|
||||||
|
async def resolve_update(obj: RoleUpdateInput, *_):
|
||||||
logger.debug(f"update role: {obj.__dict__}")
|
logger.debug(f"update role: {obj.__dict__}")
|
||||||
role = await roleDao.get_by_id(obj.id)
|
role = await roleDao.get_by_id(obj.id)
|
||||||
role.name = obj.get("name", role.name)
|
role.name = obj.get("name", role.name)
|
||||||
role.description = obj.get("description", role.description)
|
role.description = obj.get("description", role.description)
|
||||||
await roleDao.update(role)
|
await roleDao.update(role)
|
||||||
|
|
||||||
await self._resolve_assignments(
|
if obj.permissions is not None:
|
||||||
obj.get("permissions", []),
|
permissions = [x for x in await rolePermissionDao.get_by_role_id(role.id)]
|
||||||
role,
|
|
||||||
RolePermission.role_id,
|
to_delete = (
|
||||||
RolePermission.permission_id,
|
permissions
|
||||||
roleDao,
|
if len(obj.permissions) == 0
|
||||||
rolePermissionDao,
|
else await rolePermissionDao.find_by(
|
||||||
RolePermission,
|
[
|
||||||
permissionDao,
|
{RolePermission.role_id: role.id},
|
||||||
|
{
|
||||||
|
RolePermission.permission_id: {
|
||||||
|
"notIn": obj.get("permissions", [])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
permission_ids = [x.permission_id for x in permissions]
|
||||||
|
deleted_permission_ids = [
|
||||||
|
x.permission_id
|
||||||
|
for x in await rolePermissionDao.find_by(
|
||||||
|
[{RolePermission.role_id: role.id}, {RolePermission.deleted: True}]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
to_create = [
|
||||||
|
RolePermission(0, role.id, x)
|
||||||
|
for x in obj.permissions
|
||||||
|
if x not in permission_ids and x not in deleted_permission_ids
|
||||||
|
]
|
||||||
|
to_restore = [
|
||||||
|
await rolePermissionDao.get_single_by(
|
||||||
|
[
|
||||||
|
{RolePermission.role_id: role.id},
|
||||||
|
{RolePermission.permission_id: x},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for x in obj.permissions
|
||||||
|
if x not in permission_ids and x in deleted_permission_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(to_delete) > 0:
|
||||||
|
await rolePermissionDao.delete_many(to_delete)
|
||||||
|
|
||||||
|
if len(to_create) > 0:
|
||||||
|
await rolePermissionDao.create_many(to_create)
|
||||||
|
|
||||||
|
if len(to_restore) > 0:
|
||||||
|
await rolePermissionDao.restore_many(to_restore)
|
||||||
|
|
||||||
return role
|
return role
|
||||||
|
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
from api.auth.keycloak_client import Keycloak
|
from api.auth.keycloak_client import Keycloak
|
||||||
from api.broadcast import broadcast
|
|
||||||
from api.route import Route
|
|
||||||
from api_graphql.abc.mutation_abc import MutationABC
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
from api_graphql.input.user_create_input import UserCreateInput
|
from api_graphql.input.user_create_input import UserCreateInput
|
||||||
from api_graphql.input.user_update_input import UserUpdateInput
|
from api_graphql.input.user_update_input import UserUpdateInput
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
from data.schemas.administration.user import User
|
from data.schemas.administration.user import User
|
||||||
from data.schemas.administration.user_dao import userDao
|
from data.schemas.administration.user_dao import userDao
|
||||||
from data.schemas.permission.role_dao import roleDao
|
|
||||||
from data.schemas.permission.role_user import RoleUser
|
from data.schemas.permission.role_user import RoleUser
|
||||||
from data.schemas.permission.role_user_dao import roleUserDao
|
from data.schemas.permission.role_user_dao import roleUserDao
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
@ -52,26 +49,62 @@ class UserMutation(MutationABC):
|
|||||||
raise ValueError(f"Keycloak user with id {obj.keycloak_id} does not exist")
|
raise ValueError(f"Keycloak user with id {obj.keycloak_id} does not exist")
|
||||||
|
|
||||||
user = User(0, obj.keycloak_id)
|
user = User(0, obj.keycloak_id)
|
||||||
user_id = await userDao.create(user)
|
await userDao.create(user)
|
||||||
user = await userDao.get_by_id(user_id)
|
user = await userDao.get_by_keycloak_id(user.keycloak_id)
|
||||||
await roleUserDao.create_many([RoleUser(0, user.id, x) for x in set(obj.roles)])
|
await roleUserDao.create_many([RoleUser(0, user.id, x) for x in obj.roles])
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
async def resolve_update(self, obj: UserUpdateInput, *_):
|
@staticmethod
|
||||||
|
async def resolve_update(obj: UserUpdateInput, *_):
|
||||||
logger.debug(f"update user: {obj.__dict__}")
|
logger.debug(f"update user: {obj.__dict__}")
|
||||||
user = await userDao.get_by_id(obj.id)
|
user = await userDao.get_by_id(obj.id)
|
||||||
|
|
||||||
await self._resolve_assignments(
|
if obj.roles is not None:
|
||||||
obj.get("roles", []),
|
roles = await roleUserDao.get_by_user_id(user.id)
|
||||||
user,
|
|
||||||
RoleUser.user_id,
|
to_delete = (
|
||||||
RoleUser.role_id,
|
roles
|
||||||
userDao,
|
if len(obj.roles) == 0
|
||||||
roleUserDao,
|
else await roleUserDao.find_by(
|
||||||
RoleUser,
|
[
|
||||||
roleDao,
|
{RoleUser.user_id: user.id},
|
||||||
|
{RoleUser.role_id: {"notIn": obj.get("roles", [])}},
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
role_ids = [x.role_id for x in roles]
|
||||||
|
deleted_role_ids = [
|
||||||
|
x.role_id
|
||||||
|
for x in await roleUserDao.find_by(
|
||||||
|
[{RoleUser.user_id: user.id}, {RoleUser.deleted: True}]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
to_create = [
|
||||||
|
RoleUser(0, x, user.id)
|
||||||
|
for x in obj.roles
|
||||||
|
if x not in role_ids and x not in deleted_role_ids
|
||||||
|
]
|
||||||
|
to_restore = [
|
||||||
|
await roleUserDao.get_single_by(
|
||||||
|
[
|
||||||
|
{RoleUser.user_id: user.id},
|
||||||
|
{RoleUser.role_id: x},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for x in obj.roles
|
||||||
|
if x not in role_ids and x in deleted_role_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(to_delete) > 0:
|
||||||
|
await roleUserDao.delete_many(to_delete)
|
||||||
|
|
||||||
|
if len(to_create) > 0:
|
||||||
|
await roleUserDao.create_many(to_create)
|
||||||
|
|
||||||
|
if len(to_restore) > 0:
|
||||||
|
await roleUserDao.restore_many(to_restore)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@ -80,13 +113,6 @@ class UserMutation(MutationABC):
|
|||||||
logger.debug(f"delete user: {id}")
|
logger.debug(f"delete user: {id}")
|
||||||
user = await userDao.get_by_id(id)
|
user = await userDao.get_by_id(id)
|
||||||
await userDao.delete(user)
|
await userDao.delete(user)
|
||||||
try:
|
|
||||||
active_user = await Route.get_user_or_default()
|
|
||||||
if active_user is not None and active_user.id == user.id:
|
|
||||||
await broadcast.publish("userLogout", user.id)
|
|
||||||
Keycloak.admin.user_logout(user_id=user.keycloak_id)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to logout user from Keycloak", e)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
|
||||||
from data.schemas.permission.api_key_permission_dao import apiKeyPermissionDao
|
|
||||||
from data.schemas.permission.permission_dao import permissionDao
|
|
||||||
|
|
||||||
|
|
||||||
class ApiKeyHistoryQuery(DbHistoryModelQueryABC):
|
|
||||||
def __init__(self):
|
|
||||||
DbHistoryModelQueryABC.__init__(self, "ApiKey")
|
|
||||||
|
|
||||||
self.set_field("identifier", lambda x, *_: x.identifier)
|
|
||||||
self.set_field("key", lambda x, *_: x.key)
|
|
||||||
self.set_field(
|
|
||||||
"permissions",
|
|
||||||
lambda x, *_: self._resolve_foreign_history(
|
|
||||||
x.updated,
|
|
||||||
x.id,
|
|
||||||
apiKeyPermissionDao,
|
|
||||||
permissionDao,
|
|
||||||
lambda y: y.permission_id,
|
|
||||||
obj_key="apikeyid",
|
|
||||||
),
|
|
||||||
)
|
|
@ -1,14 +1,9 @@
|
|||||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
from data.schemas.administration.api_key_dao import apiKeyDao
|
|
||||||
from data.schemas.permission.role_permission_dao import rolePermissionDao
|
|
||||||
|
|
||||||
|
|
||||||
class ApiKeyQuery(DbModelQueryABC):
|
class ApiKeyQuery(DbModelQueryABC):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DbModelQueryABC.__init__(self, "ApiKey", apiKeyDao, with_history=True)
|
DbModelQueryABC.__init__(self, "ApiKey")
|
||||||
|
|
||||||
self.set_field("identifier", lambda x, *_: x.identifier)
|
self.set_field("identifier", lambda x, *_: x.identifier)
|
||||||
self.set_field("key", lambda x, *_: x.key)
|
self.set_field("key", lambda x, *_: x.key)
|
||||||
self.set_field("permissions", lambda x, *_: x.permissions)
|
|
||||||
|
|
||||||
self.set_history_reference_dao(rolePermissionDao, "apikeyid")
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
|
||||||
from data.schemas.public.domain import Domain
|
|
||||||
from data.schemas.public.short_url import ShortUrl
|
|
||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
|
||||||
|
|
||||||
|
|
||||||
class DomainHistoryQuery(DbHistoryModelQueryABC):
|
|
||||||
def __init__(self):
|
|
||||||
DbHistoryModelQueryABC.__init__(self, "Domain")
|
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
|
||||||
self.set_field("shortUrls", self._get_urls)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _get_urls(domain: Domain, *_):
|
|
||||||
return await shortUrlDao.find_by(
|
|
||||||
[
|
|
||||||
{ShortUrl.domain_id: domain.id},
|
|
||||||
{ShortUrl.deleted: False},
|
|
||||||
{"updated": {"lessOrEqual": domain.updated}},
|
|
||||||
]
|
|
||||||
)
|
|
@ -1,6 +1,5 @@
|
|||||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
from data.schemas.public.domain import Domain
|
from data.schemas.public.domain import Domain
|
||||||
from data.schemas.public.domain_dao import domainDao
|
|
||||||
from data.schemas.public.group import Group
|
from data.schemas.public.group import Group
|
||||||
from data.schemas.public.short_url import ShortUrl
|
from data.schemas.public.short_url import ShortUrl
|
||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
@ -8,13 +7,11 @@ from data.schemas.public.short_url_dao import shortUrlDao
|
|||||||
|
|
||||||
class DomainQuery(DbModelQueryABC):
|
class DomainQuery(DbModelQueryABC):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DbModelQueryABC.__init__(self, "Domain", domainDao, with_history=True)
|
DbModelQueryABC.__init__(self, "Domain")
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
self.set_field("shortUrls", self._get_urls)
|
self.set_field("shortUrls", self._get_urls)
|
||||||
|
|
||||||
self.set_history_reference_dao(shortUrlDao, "domainid")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _get_urls(domain: Domain, *_):
|
async def _get_urls(domain: Domain, *_):
|
||||||
return await shortUrlDao.find_by({ShortUrl.domain_id: domain.id})
|
return await shortUrlDao.find_by({ShortUrl.domain_id: domain.id})
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
|
||||||
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
|
||||||
from api_graphql.require_any_resolvers import group_by_assignment_resolver
|
|
||||||
from data.schemas.public.group import Group
|
|
||||||
from data.schemas.public.group_dao import groupDao
|
|
||||||
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
|
||||||
from data.schemas.public.short_url import ShortUrl
|
|
||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
|
||||||
from service.permission.permissions_enum import Permissions
|
|
||||||
|
|
||||||
|
|
||||||
class GroupHistoryQuery(DbHistoryModelQueryABC):
|
|
||||||
def __init__(self):
|
|
||||||
DbHistoryModelQueryABC.__init__(self, "Group")
|
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
|
||||||
self.field(
|
|
||||||
ResolverFieldBuilder("shortUrls")
|
|
||||||
.with_resolver(self._get_urls)
|
|
||||||
.with_require_any(
|
|
||||||
[
|
|
||||||
Permissions.groups,
|
|
||||||
],
|
|
||||||
[group_by_assignment_resolver],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.set_field(
|
|
||||||
"roles",
|
|
||||||
lambda x, *_: self._resolve_foreign_history(
|
|
||||||
x.updated,
|
|
||||||
x.id,
|
|
||||||
groupRoleAssignmentDao,
|
|
||||||
groupDao,
|
|
||||||
lambda y: y.role_id,
|
|
||||||
obj_key="groupid",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _get_urls(group: Group, *_):
|
|
||||||
return await shortUrlDao.find_by(
|
|
||||||
[
|
|
||||||
{ShortUrl.group_id: group.id},
|
|
||||||
{ShortUrl.deleted: False},
|
|
||||||
{"updated": {"lessOrEqual": group.updated}},
|
|
||||||
]
|
|
||||||
)
|
|
@ -3,7 +3,6 @@ from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
|||||||
from api_graphql.require_any_resolvers import group_by_assignment_resolver
|
from api_graphql.require_any_resolvers import group_by_assignment_resolver
|
||||||
from data.schemas.public.group import Group
|
from data.schemas.public.group import Group
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
|
||||||
from data.schemas.public.short_url import ShortUrl
|
from data.schemas.public.short_url import ShortUrl
|
||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
@ -11,7 +10,7 @@ from service.permission.permissions_enum import Permissions
|
|||||||
|
|
||||||
class GroupQuery(DbModelQueryABC):
|
class GroupQuery(DbModelQueryABC):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DbModelQueryABC.__init__(self, "Group", groupDao, with_history=True)
|
DbModelQueryABC.__init__(self, "Group")
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
self.field(
|
self.field(
|
||||||
@ -26,9 +25,6 @@ class GroupQuery(DbModelQueryABC):
|
|||||||
)
|
)
|
||||||
self.set_field("roles", self._get_roles)
|
self.set_field("roles", self._get_roles)
|
||||||
|
|
||||||
self.set_history_reference_dao(shortUrlDao, "groupid")
|
|
||||||
self.set_history_reference_dao(groupRoleAssignmentDao, "groupid")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _get_urls(group: Group, *_):
|
async def _get_urls(group: Group, *_):
|
||||||
return await shortUrlDao.find_by({ShortUrl.group_id: group.id})
|
return await shortUrlDao.find_by({ShortUrl.group_id: group.id})
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
|
||||||
from data.schemas.administration.user_dao import userDao
|
|
||||||
from data.schemas.permission.permission_dao import permissionDao
|
|
||||||
from data.schemas.permission.role_permission_dao import rolePermissionDao
|
|
||||||
from data.schemas.permission.role_user_dao import roleUserDao
|
|
||||||
|
|
||||||
|
|
||||||
class RoleHistoryQuery(DbHistoryModelQueryABC):
|
|
||||||
def __init__(self):
|
|
||||||
DbHistoryModelQueryABC.__init__(self, "Role")
|
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
|
||||||
self.set_field("description", lambda x, *_: x.description)
|
|
||||||
self.set_field(
|
|
||||||
"permissions",
|
|
||||||
lambda x, *_: self._resolve_foreign_history(
|
|
||||||
x.updated,
|
|
||||||
x.id,
|
|
||||||
rolePermissionDao,
|
|
||||||
permissionDao,
|
|
||||||
lambda y: y.permission_id,
|
|
||||||
obj_key="roleid",
|
|
||||||
),
|
|
||||||
)
|
|
@ -1,17 +1,11 @@
|
|||||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
from data.schemas.permission.role_dao import roleDao
|
|
||||||
from data.schemas.permission.role_permission_dao import rolePermissionDao
|
|
||||||
from data.schemas.permission.role_user_dao import roleUserDao
|
|
||||||
|
|
||||||
|
|
||||||
class RoleQuery(DbModelQueryABC):
|
class RoleQuery(DbModelQueryABC):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DbModelQueryABC.__init__(self, "Role", roleDao, with_history=True)
|
DbModelQueryABC.__init__(self, "Role")
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
self.set_field("description", lambda x, *_: x.description)
|
self.set_field("description", lambda x, *_: x.description)
|
||||||
self.set_field("permissions", lambda x, *_: x.permissions)
|
self.set_field("permissions", lambda x, *_: x.permissions)
|
||||||
self.set_field("users", lambda x, *_: x.users)
|
self.set_field("users", lambda x, *_: x.users)
|
||||||
|
|
||||||
self.set_history_reference_dao(rolePermissionDao, "roleid")
|
|
||||||
self.set_history_reference_dao(roleUserDao, "roleid")
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
|
||||||
|
|
||||||
|
|
||||||
class ShortUrlQuery(DbHistoryModelQueryABC):
|
|
||||||
def __init__(self):
|
|
||||||
DbHistoryModelQueryABC.__init__(self, "ShortUrl")
|
|
||||||
|
|
||||||
self.set_field("shortUrl", lambda x, *_: x.short_url)
|
|
||||||
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
|
||||||
self.set_field("description", lambda x, *_: x.description)
|
|
||||||
self.set_field("group", lambda x, *_: x.group)
|
|
||||||
self.set_field("domain", lambda x, *_: x.domain)
|
|
||||||
self.set_field("visits", lambda x, *_: x.visit_count)
|
|
||||||
self.set_field("loadingScreen", lambda x, *_: x.loading_screen)
|
|
@ -1,10 +1,9 @@
|
|||||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
|
||||||
|
|
||||||
|
|
||||||
class ShortUrlQuery(DbModelQueryABC):
|
class ShortUrlQuery(DbModelQueryABC):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DbModelQueryABC.__init__(self, "ShortUrl", shortUrlDao, with_history=True)
|
DbModelQueryABC.__init__(self, "ShortUrl")
|
||||||
|
|
||||||
self.set_field("shortUrl", lambda x, *_: x.short_url)
|
self.set_field("shortUrl", lambda x, *_: x.short_url)
|
||||||
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
|
||||||
from data.schemas.permission.role_dao import roleDao
|
|
||||||
from data.schemas.permission.role_user_dao import roleUserDao
|
|
||||||
|
|
||||||
|
|
||||||
class UserHistoryQuery(DbHistoryModelQueryABC):
|
|
||||||
def __init__(self):
|
|
||||||
DbHistoryModelQueryABC.__init__(self, "User")
|
|
||||||
|
|
||||||
self.set_field("keycloakId", lambda x, *_: x.keycloak_id)
|
|
||||||
self.set_field("username", lambda x, *_: x.username)
|
|
||||||
self.set_field("email", lambda x, *_: x.email)
|
|
||||||
self.set_field(
|
|
||||||
"roles",
|
|
||||||
lambda x, *_: self._resolve_foreign_history(
|
|
||||||
x.updated,
|
|
||||||
x.id,
|
|
||||||
roleUserDao,
|
|
||||||
roleDao,
|
|
||||||
lambda y: y.role_id,
|
|
||||||
obj_key="userid",
|
|
||||||
),
|
|
||||||
)
|
|
@ -1,15 +1,11 @@
|
|||||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
from data.schemas.administration.user_dao import userDao
|
|
||||||
from data.schemas.permission.role_user_dao import roleUserDao
|
|
||||||
|
|
||||||
|
|
||||||
class UserQuery(DbModelQueryABC):
|
class UserQuery(DbModelQueryABC):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DbModelQueryABC.__init__(self, "User", userDao, with_history=True)
|
DbModelQueryABC.__init__(self, "User")
|
||||||
|
|
||||||
self.set_field("keycloakId", lambda x, *_: x.keycloak_id)
|
self.set_field("keycloakId", lambda x, *_: x.keycloak_id)
|
||||||
self.set_field("username", lambda x, *_: x.username)
|
self.set_field("username", lambda x, *_: x.username)
|
||||||
self.set_field("email", lambda x, *_: x.email)
|
self.set_field("email", lambda x, *_: x.email)
|
||||||
self.set_field("roles", lambda x, *_: x.roles)
|
self.set_field("roles", lambda x, *_: x.roles)
|
||||||
|
|
||||||
self.set_history_reference_dao(roleUserDao, "userid")
|
|
||||||
|
@ -193,7 +193,7 @@ class Query(QueryABC):
|
|||||||
|
|
||||||
if "key" in kwargs:
|
if "key" in kwargs:
|
||||||
return await userSettingsDao.find_by(
|
return await userSettingsDao.find_by(
|
||||||
[{UserSetting.user_id: user.id}, {UserSetting.key: kwargs["key"]}]
|
{UserSetting.user_id: user.id, UserSetting.key: kwargs["key"]}
|
||||||
)
|
)
|
||||||
return await userSettingsDao.find_by({UserSetting.user_id: user.id})
|
return await userSettingsDao.find_by({UserSetting.user_id: user.id})
|
||||||
|
|
||||||
|
@ -49,12 +49,6 @@ class Subscription(SubscriptionABC):
|
|||||||
.with_public(True)
|
.with_public(True)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.subscribe(
|
|
||||||
SubscriptionFieldBuilder("userLogout")
|
|
||||||
.with_resolver(lambda message, *_: message.message)
|
|
||||||
.with_public(True)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.subscribe(
|
self.subscribe(
|
||||||
SubscriptionFieldBuilder("domainChange")
|
SubscriptionFieldBuilder("domainChange")
|
||||||
.with_resolver(lambda message, *_: message.message)
|
.with_resolver(lambda message, *_: message.message)
|
||||||
|
@ -7,17 +7,15 @@ from typing import Generic, Optional, Union, TypeVar, Any, Type
|
|||||||
from core.const import DATETIME_FORMAT
|
from core.const import DATETIME_FORMAT
|
||||||
from core.database.abc.db_model_abc import DbModelABC
|
from core.database.abc.db_model_abc import DbModelABC
|
||||||
from core.database.database import Database
|
from core.database.database import Database
|
||||||
from core.database.external_data_temp_table_builder import ExternalDataTempTableBuilder
|
|
||||||
from core.get_value import get_value
|
from core.get_value import get_value
|
||||||
from core.logger import DBLogger
|
from core.logger import DBLogger
|
||||||
from core.string import camel_to_snake
|
from core.string import camel_to_snake
|
||||||
from core.typing import T, Attribute, AttributeFilters, AttributeSorts, Id
|
from core.typing import T, Attribute, AttributeFilters, AttributeSorts
|
||||||
|
|
||||||
T_DBM = TypeVar("T_DBM", bound=DbModelABC)
|
T_DBM = TypeVar("T_DBM", bound=DbModelABC)
|
||||||
|
|
||||||
|
|
||||||
class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||||
_external_fields: dict[str, ExternalDataTempTableBuilder] = {}
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __init__(self, source: str, model_type: Type[T_DBM], table_name: str):
|
def __init__(self, source: str, model_type: Type[T_DBM], table_name: str):
|
||||||
@ -32,7 +30,6 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
self.__db_names: dict[str, str] = {}
|
self.__db_names: dict[str, str] = {}
|
||||||
self.__foreign_tables: dict[str, str] = {}
|
self.__foreign_tables: dict[str, str] = {}
|
||||||
self.__foreign_table_keys: dict[str, str] = {}
|
|
||||||
|
|
||||||
self.__date_attributes: set[str] = set()
|
self.__date_attributes: set[str] = set()
|
||||||
self.__ignored_attributes: set[str] = set()
|
self.__ignored_attributes: set[str] = set()
|
||||||
@ -51,7 +48,6 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
db_name: str = None,
|
db_name: str = None,
|
||||||
ignore=False,
|
ignore=False,
|
||||||
primary_key=False,
|
primary_key=False,
|
||||||
aliases: list[str] = None,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add an attribute for db and object mapping to the data access object
|
Add an attribute for db and object mapping to the data access object
|
||||||
@ -60,7 +56,6 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
:param str db_name: Name of the field in the database, if None the attribute lowered attr_name without "_" is used
|
:param str db_name: Name of the field in the database, if None the attribute lowered attr_name without "_" is used
|
||||||
:param bool ignore: Defines if field is ignored for create and update (for e.g. auto increment fields or created/updated fields)
|
:param bool ignore: Defines if field is ignored for create and update (for e.g. auto increment fields or created/updated fields)
|
||||||
:param bool primary_key: Defines if field is the primary key
|
:param bool primary_key: Defines if field is the primary key
|
||||||
:param list[str] aliases: List of aliases for the attribute name
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if isinstance(attr_name, property):
|
if isinstance(attr_name, property):
|
||||||
@ -74,20 +69,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
db_name = attr_name.lower().replace("_", "")
|
db_name = attr_name.lower().replace("_", "")
|
||||||
|
|
||||||
self.__db_names[attr_name] = db_name
|
self.__db_names[attr_name] = db_name
|
||||||
self.__db_names[db_name] = db_name
|
|
||||||
|
|
||||||
if aliases is not None:
|
|
||||||
for alias in aliases:
|
|
||||||
if alias in self.__db_names:
|
|
||||||
raise ValueError(f"Alias {alias} already exists")
|
|
||||||
self.__db_names[alias] = db_name
|
|
||||||
|
|
||||||
if primary_key:
|
if primary_key:
|
||||||
self.__primary_key = db_name
|
self.__primary_key = db_name
|
||||||
self.__primary_key_type = attr_type
|
self.__primary_key_type = attr_type
|
||||||
|
|
||||||
if attr_type in [datetime, datetime.datetime]:
|
if attr_type in [datetime, datetime.datetime]:
|
||||||
self.__date_attributes.add(attr_name)
|
|
||||||
self.__date_attributes.add(db_name)
|
self.__date_attributes.add(db_name)
|
||||||
|
|
||||||
def reference(
|
def reference(
|
||||||
@ -99,12 +85,13 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add a reference to another table for the given attribute
|
Add a reference to another table for the given attribute
|
||||||
:param Attribute attr: Name of the attribute in the object
|
|
||||||
:param str primary_attr: Name of the primary key in the foreign object
|
:param str primary_attr: Name of the primary key in the foreign object
|
||||||
:param str foreign_attr: Name of the foreign key in the object
|
:param str foreign_attr: Name of the foreign key in the object
|
||||||
:param str table_name: Name of the table to reference
|
:param str table_name: Name of the table to reference
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
if table_name == self._table_name:
|
||||||
|
return
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
attr = attr.fget.__name__
|
attr = attr.fget.__name__
|
||||||
|
|
||||||
@ -118,18 +105,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
foreign_attr = foreign_attr.lower().replace("_", "")
|
foreign_attr = foreign_attr.lower().replace("_", "")
|
||||||
|
|
||||||
self.__foreign_table_keys[attr] = foreign_attr
|
|
||||||
if table_name == self._table_name:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.__joins[foreign_attr] = (
|
self.__joins[foreign_attr] = (
|
||||||
f"LEFT JOIN {table_name} ON {table_name}.{primary_attr} = {self._table_name}.{foreign_attr}"
|
f"LEFT JOIN {table_name} ON {table_name}.{primary_attr} = {self._table_name}.{foreign_attr}"
|
||||||
)
|
)
|
||||||
self.__foreign_tables[attr] = table_name
|
self.__foreign_tables[attr] = table_name
|
||||||
|
|
||||||
def use_external_fields(self, builder: ExternalDataTempTableBuilder):
|
|
||||||
self._external_fields[builder.table_name] = builder
|
|
||||||
|
|
||||||
def to_object(self, result: dict) -> T_DBM:
|
def to_object(self, result: dict) -> T_DBM:
|
||||||
"""
|
"""
|
||||||
Convert a result from the database to an object
|
Convert a result from the database to an object
|
||||||
@ -156,53 +136,16 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
query += f" {self.__joins[join]}"
|
query += f" {self.__joins[join]}"
|
||||||
|
|
||||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
||||||
conditions, external_table_deps = await self._build_conditions(filters)
|
query += f" WHERE {self._build_conditions(filters)}"
|
||||||
query = await self._handle_query_external_temp_tables(
|
|
||||||
query, external_table_deps, ignore_fields=True
|
|
||||||
)
|
|
||||||
query += f" WHERE {conditions};"
|
|
||||||
|
|
||||||
result = await self._db.select_map(query)
|
result = await self._db.select_map(query)
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
return 0
|
return 0
|
||||||
return result[0]["count"]
|
return result[0]["count"]
|
||||||
|
|
||||||
async def get_history(
|
|
||||||
self,
|
|
||||||
entry_id: int,
|
|
||||||
by_key: str = None,
|
|
||||||
when: datetime = None,
|
|
||||||
until: datetime = None,
|
|
||||||
without_deleted=False,
|
|
||||||
) -> list[T_DBM]:
|
|
||||||
query = f"SELECT {self._table_name}_history.* FROM {self._table_name}_history"
|
|
||||||
for join in self.__joins:
|
|
||||||
query += f" {self.__joins[join].replace(self._table_name, f'{self._table_name}_history')}"
|
|
||||||
|
|
||||||
query += f" WHERE {f'{self._table_name}_history.{self.__primary_key}' if by_key is None else f'{self._table_name}_history.{by_key}'} = {entry_id}"
|
|
||||||
|
|
||||||
if self._default_filter_condition is not None:
|
|
||||||
query += f" AND {self._default_filter_condition}"
|
|
||||||
|
|
||||||
if without_deleted:
|
|
||||||
query += f" AND {self._table_name}_history.deleted = false"
|
|
||||||
|
|
||||||
if when is not None:
|
|
||||||
query += f" AND {self._attr_from_date_to_char(f'{self._table_name}_history.updated')} = '{when.strftime(DATETIME_FORMAT)}'"
|
|
||||||
|
|
||||||
if until is not None:
|
|
||||||
query += f" AND {self._attr_from_date_to_char(f'{self._table_name}_history.updated')} <= '{until.strftime(DATETIME_FORMAT)}'"
|
|
||||||
|
|
||||||
query += f" ORDER BY {self._table_name}_history.updated DESC;"
|
|
||||||
|
|
||||||
result = await self._db.select_map(query)
|
|
||||||
if result is None:
|
|
||||||
return []
|
|
||||||
return [self.to_object(x) for x in result]
|
|
||||||
|
|
||||||
async def get_all(self) -> list[T_DBM]:
|
async def get_all(self) -> list[T_DBM]:
|
||||||
result = await self._db.select_map(
|
result = await self._db.select_map(
|
||||||
f"SELECT * FROM {self._table_name}{f" WHERE {self._default_filter_condition}" if self._default_filter_condition is not None else ''} ORDER BY {self.__primary_key};"
|
f"SELECT * FROM {self._table_name}{f" WHERE {self._default_filter_condition}" if self._default_filter_condition is not None else ''}"
|
||||||
)
|
)
|
||||||
if result is None:
|
if result is None:
|
||||||
return []
|
return []
|
||||||
@ -242,7 +185,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
:raises ValueError: When no result is found
|
:raises ValueError: When no result is found
|
||||||
"""
|
"""
|
||||||
result = await self._db.select_map(
|
result = await self._db.select_map(
|
||||||
await self._build_conditional_query(filters, sorts, take, skip)
|
self._build_conditional_query(filters, sorts, take, skip)
|
||||||
)
|
)
|
||||||
if not result or len(result) == 0:
|
if not result or len(result) == 0:
|
||||||
raise ValueError("No result found")
|
raise ValueError("No result found")
|
||||||
@ -291,7 +234,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
:rtype: list[Optional[T_DBM]]
|
:rtype: list[Optional[T_DBM]]
|
||||||
"""
|
"""
|
||||||
result = await self._db.select_map(
|
result = await self._db.select_map(
|
||||||
await self._build_conditional_query(filters, sorts, take, skip)
|
self._build_conditional_query(filters, sorts, take, skip)
|
||||||
)
|
)
|
||||||
if not result or len(result) == 0:
|
if not result or len(result) == 0:
|
||||||
return []
|
return []
|
||||||
@ -322,35 +265,6 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
raise ValueError("More than one result found")
|
raise ValueError("More than one result found")
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
async def touch(self, obj: T_DBM):
|
|
||||||
"""
|
|
||||||
Touch the entry to update the last updated date
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
await self._db.execute(
|
|
||||||
f"""
|
|
||||||
UPDATE {self._table_name}
|
|
||||||
SET updated = NOW()
|
|
||||||
WHERE {self.__primary_key} = {self._get_primary_key_value_sql(obj)};
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
async def touch_many_by_id(self, ids: list[Id]):
|
|
||||||
"""
|
|
||||||
Touch the entries to update the last updated date
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if len(ids) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
await self._db.execute(
|
|
||||||
f"""
|
|
||||||
UPDATE {self._table_name}
|
|
||||||
SET updated = NOW()
|
|
||||||
WHERE {self.__primary_key} IN ({", ".join([str(x) for x in ids])});
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _build_create_statement(self, obj: T_DBM, skip_editor=False) -> str:
|
async def _build_create_statement(self, obj: T_DBM, skip_editor=False) -> str:
|
||||||
allowed_fields = [
|
allowed_fields = [
|
||||||
x for x in self.__attributes.keys() if x not in self.__ignored_attributes
|
x for x in self.__attributes.keys() if x not in self.__ignored_attributes
|
||||||
@ -510,7 +424,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
return "NULL"
|
return "NULL"
|
||||||
|
|
||||||
if isinstance(value, Enum):
|
if isinstance(value, Enum):
|
||||||
return f"'{value.value}'"
|
return str(value.value)
|
||||||
|
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
return "true" if value else "false"
|
return "true" if value else "false"
|
||||||
@ -547,136 +461,77 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
return cast_type(value)
|
return cast_type(value)
|
||||||
|
|
||||||
async def _handle_query_external_temp_tables(
|
def _build_conditional_query(
|
||||||
self, query: str, external_table_deps: list[str], ignore_fields=False
|
|
||||||
) -> str:
|
|
||||||
for dep in external_table_deps:
|
|
||||||
temp_table = self._external_fields[dep]
|
|
||||||
temp_table_sql = await temp_table.build()
|
|
||||||
|
|
||||||
if not ignore_fields:
|
|
||||||
query = query.replace(
|
|
||||||
" FROM",
|
|
||||||
f", {','.join([f'{temp_table.table_name}.{x}' for x in temp_table.fields.keys() if x not in self.__db_names])} FROM",
|
|
||||||
)
|
|
||||||
|
|
||||||
query = f"{temp_table_sql}\n{query}"
|
|
||||||
query += f" LEFT JOIN {temp_table.table_name} ON {temp_table.join_ref_table}.{self.__primary_key} = {temp_table.table_name}.{temp_table.primary_key}"
|
|
||||||
|
|
||||||
return query
|
|
||||||
|
|
||||||
async def _build_conditional_query(
|
|
||||||
self,
|
self,
|
||||||
filters: AttributeFilters = None,
|
filters: AttributeFilters = None,
|
||||||
sorts: AttributeSorts = None,
|
sorts: AttributeSorts = None,
|
||||||
take: int = None,
|
take: int = None,
|
||||||
skip: int = None,
|
skip: int = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
filter_conditions = []
|
|
||||||
sort_conditions = []
|
|
||||||
|
|
||||||
external_table_deps = []
|
|
||||||
query = f"SELECT {self._table_name}.* FROM {self._table_name}"
|
query = f"SELECT {self._table_name}.* FROM {self._table_name}"
|
||||||
|
|
||||||
for join in self.__joins:
|
for join in self.__joins:
|
||||||
query += f" {self.__joins[join]}"
|
query += f" {self.__joins[join]}"
|
||||||
|
|
||||||
# Collect dependencies from filters
|
|
||||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
||||||
filter_conditions, filter_deps = await self._build_conditions(filters)
|
query += f" WHERE {self._build_conditions(filters)}"
|
||||||
external_table_deps.extend(filter_deps)
|
|
||||||
|
|
||||||
# Collect dependencies from sorts
|
|
||||||
if sorts is not None and (not isinstance(sorts, list) or len(sorts) > 0):
|
if sorts is not None and (not isinstance(sorts, list) or len(sorts) > 0):
|
||||||
sort_conditions, sort_deps = self._build_order_by(sorts)
|
query += f" ORDER BY {self._build_order_by(sorts)}"
|
||||||
external_table_deps.extend(sort_deps)
|
|
||||||
|
|
||||||
# Handle external table dependencies before WHERE and ORDER BY
|
|
||||||
if external_table_deps:
|
|
||||||
query = await self._handle_query_external_temp_tables(
|
|
||||||
query, external_table_deps
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add WHERE clause
|
|
||||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
|
||||||
query += f" WHERE {filter_conditions}"
|
|
||||||
|
|
||||||
# Add ORDER BY clause
|
|
||||||
if sorts is not None and (not isinstance(sorts, list) or len(sorts) > 0):
|
|
||||||
query += f" ORDER BY {sort_conditions}"
|
|
||||||
|
|
||||||
if take is not None:
|
if take is not None:
|
||||||
query += f" LIMIT {take}"
|
query += f" LIMIT {take}"
|
||||||
|
|
||||||
if skip is not None:
|
if skip is not None:
|
||||||
query += f" OFFSET {skip}"
|
query += f" OFFSET {skip}"
|
||||||
|
|
||||||
if not query.endswith(";"):
|
|
||||||
query += ";"
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def _get_external_field_key(self, field_name: str) -> Optional[str]:
|
def _build_conditions(self, filters: AttributeFilters) -> str:
|
||||||
"""
|
|
||||||
Returns the key to get the external field if found, otherwise None.
|
|
||||||
:param str field_name: The name of the field to search for.
|
|
||||||
:return: The key if found, otherwise None.
|
|
||||||
:rtype: Optional[str]
|
|
||||||
"""
|
|
||||||
for key, builder in self._external_fields.items():
|
|
||||||
if field_name in builder.fields and field_name not in self.__db_names:
|
|
||||||
return key
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def _build_conditions(self, filters: AttributeFilters) -> (str, list[str]):
|
|
||||||
"""
|
"""
|
||||||
Build SQL conditions from the given filters
|
Build SQL conditions from the given filters
|
||||||
:param filters:
|
:param filters:
|
||||||
:return: SQL conditions & External field table dependencies
|
:return:
|
||||||
"""
|
"""
|
||||||
external_field_table_deps = []
|
|
||||||
if not isinstance(filters, list):
|
if not isinstance(filters, list):
|
||||||
filters = [filters]
|
filters = [filters]
|
||||||
|
|
||||||
conditions = []
|
conditions = []
|
||||||
for f in filters:
|
for f in filters:
|
||||||
f_conditions = []
|
|
||||||
|
|
||||||
for attr, values in f.items():
|
for attr, values in f.items():
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
attr = attr.fget.__name__
|
attr = attr.fget.__name__
|
||||||
|
|
||||||
if attr in self.__foreign_tables:
|
if attr in self.__foreign_tables:
|
||||||
foreign_table = self.__foreign_tables[attr]
|
foreign_table = self.__foreign_tables[attr]
|
||||||
cons, eftd = self._build_foreign_conditions(foreign_table, values)
|
conditions.extend(
|
||||||
if eftd:
|
self._build_foreign_conditions(foreign_table, values)
|
||||||
external_field_table_deps.extend(eftd)
|
)
|
||||||
|
continue
|
||||||
f_conditions.extend(cons)
|
|
||||||
continue
|
if attr == "fuzzy":
|
||||||
|
conditions.append(
|
||||||
if attr == "fuzzy":
|
" OR ".join(
|
||||||
self._handle_fuzzy_filter_conditions(
|
self._build_fuzzy_conditions(
|
||||||
f_conditions, external_field_table_deps, values
|
[
|
||||||
|
(
|
||||||
|
self.__db_names[x]
|
||||||
|
if x in self.__db_names
|
||||||
|
else self.__db_names[camel_to_snake(x)]
|
||||||
|
)
|
||||||
|
for x in get_value(values, "fields", list[str])
|
||||||
|
],
|
||||||
|
get_value(values, "term", str),
|
||||||
|
get_value(values, "threshold", int, 5),
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
external_fields_table_name = self._get_external_field_key(attr)
|
|
||||||
if external_fields_table_name is not None:
|
|
||||||
external_fields_table = self._external_fields[
|
|
||||||
external_fields_table_name
|
|
||||||
]
|
|
||||||
db_name = f"{external_fields_table.table_name}.{attr}"
|
|
||||||
external_field_table_deps.append(external_fields_table.table_name)
|
|
||||||
elif (
|
|
||||||
isinstance(values, dict) or isinstance(values, list)
|
|
||||||
) and not attr in self.__foreign_tables:
|
|
||||||
db_name = f"{self._table_name}.{self.__db_names[attr]}"
|
|
||||||
else:
|
|
||||||
db_name = self.__db_names[attr]
|
db_name = self.__db_names[attr]
|
||||||
|
|
||||||
if isinstance(values, dict):
|
if isinstance(values, dict):
|
||||||
for operator, value in values.items():
|
for operator, value in values.items():
|
||||||
f_conditions.append(
|
conditions.append(
|
||||||
self._build_condition(f"{db_name}", operator, value)
|
self._build_condition(
|
||||||
|
f"{self._table_name}.{db_name}", operator, value
|
||||||
|
)
|
||||||
)
|
)
|
||||||
elif isinstance(values, list):
|
elif isinstance(values, list):
|
||||||
sub_conditions = []
|
sub_conditions = []
|
||||||
@ -684,42 +539,38 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
for operator, val in value.items():
|
for operator, val in value.items():
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
self._build_condition(f"{db_name}", operator, val)
|
self._build_condition(
|
||||||
|
f"{self._table_name}.{db_name}", operator, val
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
self._get_value_validation_sql(db_name, value)
|
self._get_value_validation_sql(db_name, value)
|
||||||
)
|
)
|
||||||
f_conditions.append(f"({' OR '.join(sub_conditions)})")
|
conditions.append(f"({' OR '.join(sub_conditions)})")
|
||||||
else:
|
else:
|
||||||
f_conditions.append(self._get_value_validation_sql(db_name, values))
|
conditions.append(self._get_value_validation_sql(db_name, values))
|
||||||
|
|
||||||
conditions.append(f"({' OR '.join(f_conditions)})")
|
return " AND ".join(conditions)
|
||||||
|
|
||||||
return " AND ".join(conditions), external_field_table_deps
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _build_fuzzy_conditions(
|
def _build_fuzzy_conditions(
|
||||||
fields: list[str], term: str, threshold: int = 10
|
self, fields: list[str], term: str, threshold: int = 10
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
conditions = []
|
conditions = []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
conditions.append(
|
conditions.append(
|
||||||
f"levenshtein({field}::TEXT, '{term}') <= {threshold}"
|
f"levenshtein({field}, '{term}') <= {threshold}"
|
||||||
) # Adjust the threshold as needed
|
) # Adjust the threshold as needed
|
||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
def _build_foreign_conditions(
|
def _build_foreign_conditions(self, table: str, values: dict) -> list[str]:
|
||||||
self, table: str, values: dict
|
|
||||||
) -> (list[str], list[str]):
|
|
||||||
"""
|
"""
|
||||||
Build SQL conditions for foreign key references
|
Build SQL conditions for foreign key references
|
||||||
:param table: Foreign table name
|
:param table: Foreign table name
|
||||||
:param values: Filter values
|
:param values: Filter values
|
||||||
:return: List of conditions, List of external field tables
|
:return: List of conditions
|
||||||
"""
|
"""
|
||||||
external_field_table_deps = []
|
|
||||||
conditions = []
|
conditions = []
|
||||||
for attr, sub_values in values.items():
|
for attr, sub_values in values.items():
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
@ -727,43 +578,25 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
if attr in self.__foreign_tables:
|
if attr in self.__foreign_tables:
|
||||||
foreign_table = self.__foreign_tables[attr]
|
foreign_table = self.__foreign_tables[attr]
|
||||||
sub_conditions, eftd = self._build_foreign_conditions(
|
conditions.extend(
|
||||||
foreign_table, sub_values
|
self._build_foreign_conditions(foreign_table, sub_values)
|
||||||
)
|
|
||||||
if len(eftd) > 0:
|
|
||||||
external_field_table_deps.extend(eftd)
|
|
||||||
|
|
||||||
conditions.extend(sub_conditions)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if attr == "fuzzy":
|
|
||||||
self._handle_fuzzy_filter_conditions(
|
|
||||||
conditions, external_field_table_deps, sub_values
|
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
external_fields_table_name = self._get_external_field_key(attr)
|
|
||||||
if external_fields_table_name is not None:
|
|
||||||
external_fields_table = self._external_fields[
|
|
||||||
external_fields_table_name
|
|
||||||
]
|
|
||||||
db_name = f"{external_fields_table.table_name}.{attr}"
|
|
||||||
external_field_table_deps.append(external_fields_table.table_name)
|
|
||||||
else:
|
|
||||||
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
||||||
|
|
||||||
if isinstance(sub_values, dict):
|
if isinstance(sub_values, dict):
|
||||||
for operator, value in sub_values.items():
|
for operator, value in sub_values.items():
|
||||||
conditions.append(
|
conditions.append(
|
||||||
f"{self._build_condition(db_name, operator, value)}"
|
f"({self._build_condition(db_name, operator, value)} OR {self._build_condition(db_name, "isNull", None)})")
|
||||||
)
|
|
||||||
elif isinstance(sub_values, list):
|
elif isinstance(sub_values, list):
|
||||||
sub_conditions = []
|
sub_conditions = []
|
||||||
for value in sub_values:
|
for value in sub_values:
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
for operator, val in value.items():
|
for operator, val in value.items():
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
f"{self._build_condition(db_name, operator, val)}"
|
f"({self._build_condition(db_name, operator, val)} OR {self._build_condition(db_name, "isNull", None)})"
|
||||||
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sub_conditions.append(
|
sub_conditions.append(
|
||||||
@ -773,55 +606,14 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
else:
|
else:
|
||||||
conditions.append(self._get_value_validation_sql(db_name, sub_values))
|
conditions.append(self._get_value_validation_sql(db_name, sub_values))
|
||||||
|
|
||||||
return conditions, external_field_table_deps
|
return conditions
|
||||||
|
|
||||||
def _handle_fuzzy_filter_conditions(
|
|
||||||
self, conditions, external_field_table_deps, sub_values
|
|
||||||
):
|
|
||||||
fuzzy_fields = get_value(sub_values, "fields", list[str])
|
|
||||||
fuzzy_fields_db_names = []
|
|
||||||
for fuzzy_field in fuzzy_fields:
|
|
||||||
external_fields_table_name = self._get_external_field_key(fuzzy_field)
|
|
||||||
if external_fields_table_name is not None:
|
|
||||||
external_fields_table = self._external_fields[
|
|
||||||
external_fields_table_name
|
|
||||||
]
|
|
||||||
fuzzy_fields_db_names.append(
|
|
||||||
f"{external_fields_table.table_name}.{fuzzy_field}"
|
|
||||||
)
|
|
||||||
external_field_table_deps.append(external_fields_table.table_name)
|
|
||||||
elif fuzzy_field in self.__db_names:
|
|
||||||
fuzzy_fields_db_names.append(
|
|
||||||
f"{self._table_name}.{self.__db_names[fuzzy_field]}"
|
|
||||||
)
|
|
||||||
elif fuzzy_field in self.__foreign_tables:
|
|
||||||
fuzzy_fields_db_names.append(
|
|
||||||
f"{self._table_name}.{self.__foreign_table_keys[fuzzy_field]}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
fuzzy_fields_db_names.append(
|
|
||||||
self.__db_names[camel_to_snake(fuzzy_field)]
|
|
||||||
)
|
|
||||||
conditions.append(
|
|
||||||
f"({' OR '.join(
|
|
||||||
self._build_fuzzy_conditions(
|
|
||||||
[x for x in fuzzy_fields_db_names],
|
|
||||||
get_value(sub_values, "term", str),
|
|
||||||
get_value(sub_values, "threshold", int, 5),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_value_validation_sql(self, field: str, value: Any):
|
def _get_value_validation_sql(self, field: str, value: Any):
|
||||||
value = self._get_value_sql(value)
|
value = self._get_value_sql(value)
|
||||||
field_selector = f"{self._table_name}.{field}"
|
|
||||||
if field in self.__foreign_tables:
|
|
||||||
field_selector = self.__db_names[field]
|
|
||||||
|
|
||||||
if value == "NULL":
|
if value == "NULL":
|
||||||
return f"{field_selector} IS NULL"
|
return f"{self._table_name}.{field} IS NULL"
|
||||||
return f"{field_selector} = {value}"
|
return f"{self._table_name}.{field} = {value}"
|
||||||
|
|
||||||
def _build_condition(self, db_name: str, operator: str, value: Any) -> str:
|
def _build_condition(self, db_name: str, operator: str, value: Any) -> str:
|
||||||
"""
|
"""
|
||||||
@ -831,10 +623,8 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
:param value:
|
:param value:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
attr = db_name.split(".")[-1]
|
if db_name in self.__date_attributes:
|
||||||
|
db_name = f"TO_CHAR({db_name}, 'DD.MM.YYYY HH24:MI:SS.US')"
|
||||||
if attr in self.__date_attributes:
|
|
||||||
db_name = self._attr_from_date_to_char(db_name)
|
|
||||||
|
|
||||||
sql_value = self._get_value_sql(value)
|
sql_value = self._get_value_sql(value)
|
||||||
if operator == "equal":
|
if operator == "equal":
|
||||||
@ -870,17 +660,12 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported operator: {operator}")
|
raise ValueError(f"Unsupported operator: {operator}")
|
||||||
|
|
||||||
@staticmethod
|
def _build_order_by(self, sorts: AttributeSorts) -> str:
|
||||||
def _attr_from_date_to_char(attr: str) -> str:
|
|
||||||
return f"TO_CHAR({attr}, 'YYYY-MM-DD HH24:MI:SS.US TZ')"
|
|
||||||
|
|
||||||
def _build_order_by(self, sorts: AttributeSorts) -> (str, list[str]):
|
|
||||||
"""
|
"""
|
||||||
Build SQL order by clause from the given sorts
|
Build SQL order by clause from the given sorts
|
||||||
:param sorts:
|
:param sorts:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
external_field_table_deps = []
|
|
||||||
if not isinstance(sorts, list):
|
if not isinstance(sorts, list):
|
||||||
sorts = [sorts]
|
sorts = [sorts]
|
||||||
|
|
||||||
@ -892,38 +677,35 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
if attr in self.__foreign_tables:
|
if attr in self.__foreign_tables:
|
||||||
foreign_table = self.__foreign_tables[attr]
|
foreign_table = self.__foreign_tables[attr]
|
||||||
f_sorts, eftd = self._build_foreign_order_by(
|
sort_clauses.extend(
|
||||||
foreign_table, direction
|
self._build_foreign_order_by(foreign_table, direction)
|
||||||
)
|
)
|
||||||
if eftd:
|
|
||||||
external_field_table_deps.extend(eftd)
|
|
||||||
|
|
||||||
sort_clauses.extend(f_sorts)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
external_fields_table_name = self._get_external_field_key(attr)
|
match attr:
|
||||||
if external_fields_table_name is not None:
|
case "createdUtc":
|
||||||
external_fields_table = self._external_fields[
|
attr = "created"
|
||||||
external_fields_table_name
|
case "updatedUtc":
|
||||||
]
|
attr = "updated"
|
||||||
db_name = f"{external_fields_table.table_name}.{attr}"
|
|
||||||
external_field_table_deps.append(external_fields_table.table_name)
|
if attr.endswith("Utc") and attr.split("Utc")[0].lower() in [
|
||||||
else:
|
"created",
|
||||||
|
"updated",
|
||||||
|
]:
|
||||||
|
attr = attr.replace("Utc", "")
|
||||||
|
|
||||||
db_name = self.__db_names[attr]
|
db_name = self.__db_names[attr]
|
||||||
sort_clauses.append(f"{db_name} {direction.upper()}")
|
sort_clauses.append(f"{db_name} {direction.upper()}")
|
||||||
|
|
||||||
return ", ".join(sort_clauses), external_field_table_deps
|
return ", ".join(sort_clauses)
|
||||||
|
|
||||||
def _build_foreign_order_by(
|
def _build_foreign_order_by(self, table: str, direction: str) -> list[str]:
|
||||||
self, table: str, direction: dict
|
|
||||||
) -> (list[str], list[str]):
|
|
||||||
"""
|
"""
|
||||||
Build SQL order by clause for foreign key references
|
Build SQL order by clause for foreign key references
|
||||||
:param table: Foreign table name
|
:param table: Foreign table name
|
||||||
:param direction: Sort direction
|
:param direction: Sort direction
|
||||||
:return: List of order by clauses
|
:return: List of order by clauses
|
||||||
"""
|
"""
|
||||||
external_field_table_deps = []
|
|
||||||
sort_clauses = []
|
sort_clauses = []
|
||||||
for attr, sub_direction in direction.items():
|
for attr, sub_direction in direction.items():
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
@ -931,25 +713,15 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
|
|
||||||
if attr in self.__foreign_tables:
|
if attr in self.__foreign_tables:
|
||||||
foreign_table = self.__foreign_tables[attr]
|
foreign_table = self.__foreign_tables[attr]
|
||||||
f_sorts, eftd = self._build_foreign_order_by(foreign_table, direction)
|
sort_clauses.extend(
|
||||||
if eftd:
|
self._build_foreign_order_by(foreign_table, sub_direction)
|
||||||
external_field_table_deps.extend(eftd)
|
)
|
||||||
|
|
||||||
sort_clauses.extend(f_sorts)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
external_fields_table_name = self._get_external_field_key(attr)
|
|
||||||
if external_fields_table_name is not None:
|
|
||||||
external_fields_table = self._external_fields[
|
|
||||||
external_fields_table_name
|
|
||||||
]
|
|
||||||
db_name = f"{external_fields_table.table_name}.{attr}"
|
|
||||||
external_field_table_deps.append(external_fields_table.table_name)
|
|
||||||
else:
|
|
||||||
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
||||||
sort_clauses.append(f"{db_name} {sub_direction.upper()}")
|
sort_clauses.append(f"{db_name} {sub_direction.upper()}")
|
||||||
|
|
||||||
return sort_clauses, external_field_table_deps
|
return sort_clauses
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _get_editor_id(obj: T_DBM):
|
async def _get_editor_id(obj: T_DBM):
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from core.database.abc.db_model_abc import DbModelABC
|
|
||||||
from core.typing import Id, SerialId
|
|
||||||
|
|
||||||
|
|
||||||
class DbJoinModelABC(DbModelABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
id: Id,
|
|
||||||
source_id: Id,
|
|
||||||
foreign_id: Id,
|
|
||||||
deleted: bool = False,
|
|
||||||
editor_id: Optional[SerialId] = None,
|
|
||||||
created: Optional[datetime] = None,
|
|
||||||
updated: Optional[datetime] = None,
|
|
||||||
):
|
|
||||||
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
|
@ -54,7 +54,7 @@ class DbModelABC(ABC):
|
|||||||
|
|
||||||
from data.schemas.administration.user_dao import userDao
|
from data.schemas.administration.user_dao import userDao
|
||||||
|
|
||||||
return await userDao.find_single_by({"id": self._editor_id})
|
return await userDao.get_by_id(self._editor_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created(self) -> datetime:
|
def created(self) -> datetime:
|
||||||
|
@ -15,5 +15,5 @@ class DbModelDaoABC[T_DBM](DataAccessObjectABC[T_DBM]):
|
|||||||
self.attribute(DbModelABC.id, int, ignore=True)
|
self.attribute(DbModelABC.id, int, ignore=True)
|
||||||
self.attribute(DbModelABC.deleted, bool)
|
self.attribute(DbModelABC.deleted, bool)
|
||||||
self.attribute(DbModelABC.editor_id, int, ignore=True)
|
self.attribute(DbModelABC.editor_id, int, ignore=True)
|
||||||
self.attribute(DbModelABC.created, datetime, "created", ignore=True)
|
self.attribute(DbModelABC.created, datetime, "createdutc", ignore=True)
|
||||||
self.attribute(DbModelABC.updated, datetime, "updated", ignore=True)
|
self.attribute(DbModelABC.updated, datetime, "updatedutc", ignore=True)
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
import textwrap
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalDataTempTableBuilder:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._table_name = None
|
|
||||||
self._fields: dict[str, str] = {}
|
|
||||||
self._primary_key = "id"
|
|
||||||
self._join_ref_table = None
|
|
||||||
self._value_getter = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def table_name(self) -> str:
|
|
||||||
return self._table_name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fields(self) -> dict[str, str]:
|
|
||||||
return self._fields
|
|
||||||
|
|
||||||
@property
|
|
||||||
def primary_key(self) -> str:
|
|
||||||
return self._primary_key
|
|
||||||
|
|
||||||
@property
|
|
||||||
def join_ref_table(self) -> str:
|
|
||||||
return self._join_ref_table
|
|
||||||
|
|
||||||
def with_table_name(self, table_name: str) -> "ExternalDataTempTableBuilder":
|
|
||||||
self._join_ref_table = table_name
|
|
||||||
|
|
||||||
if "." in table_name:
|
|
||||||
table_name = table_name.split(".")[-1]
|
|
||||||
|
|
||||||
if not table_name.endswith("_temp"):
|
|
||||||
table_name = f"{table_name}_temp"
|
|
||||||
|
|
||||||
self._table_name = table_name
|
|
||||||
return self
|
|
||||||
|
|
||||||
def with_field(
|
|
||||||
self, name: str, sql_type: str, primary=False
|
|
||||||
) -> "ExternalDataTempTableBuilder":
|
|
||||||
if primary:
|
|
||||||
sql_type += " PRIMARY KEY"
|
|
||||||
self._primary_key = name
|
|
||||||
self._fields[name] = sql_type
|
|
||||||
return self
|
|
||||||
|
|
||||||
def with_value_getter(
|
|
||||||
self, value_getter: Callable
|
|
||||||
) -> "ExternalDataTempTableBuilder":
|
|
||||||
self._value_getter = value_getter
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def build(self) -> str:
|
|
||||||
assert self._table_name is not None, "Table name is required"
|
|
||||||
assert self._value_getter is not None, "Value getter is required"
|
|
||||||
|
|
||||||
values_str = ", ".join([f"{value}" for value in await self._value_getter()])
|
|
||||||
|
|
||||||
return textwrap.dedent(
|
|
||||||
f"""
|
|
||||||
DROP TABLE IF EXISTS {self._table_name};
|
|
||||||
CREATE TEMP TABLE {self._table_name} (
|
|
||||||
{", ".join([f"{k} {v}" for k, v in self._fields.items()])}
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO {self._table_name} VALUES {values_str};
|
|
||||||
"""
|
|
||||||
)
|
|
@ -29,11 +29,6 @@ def get_value(
|
|||||||
value,
|
value,
|
||||||
cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__,
|
cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__,
|
||||||
):
|
):
|
||||||
# Handle list[int] case explicitly
|
|
||||||
if hasattr(cast_type, "__origin__") and cast_type.__origin__ == list:
|
|
||||||
subtype = cast_type.__args__[0] if hasattr(cast_type, "__args__") else None
|
|
||||||
if subtype is not None:
|
|
||||||
return [subtype(item) for item in value]
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -40,7 +40,7 @@ class ApiKey(DbModelABC):
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
await x.permission
|
await x.permission
|
||||||
for x in await apiKeyPermissionDao.find_by_api_key_id(self.id)
|
for x in await apiKeyPermissionDao.get_by_api_key_id(self.id)
|
||||||
]
|
]
|
||||||
|
|
||||||
async def has_permission(self, permission: Permissions) -> bool:
|
async def has_permission(self, permission: Permissions) -> bool:
|
||||||
|
@ -15,23 +15,23 @@ class ApiKeyPermissionDao(DbModelDaoABC[ApiKeyPermission]):
|
|||||||
self.attribute(ApiKeyPermission.api_key_id, int)
|
self.attribute(ApiKeyPermission.api_key_id, int)
|
||||||
self.attribute(ApiKeyPermission.permission_id, int)
|
self.attribute(ApiKeyPermission.permission_id, int)
|
||||||
|
|
||||||
async def find_by_api_key_id(
|
async def get_by_api_key_id(
|
||||||
self, api_key_id: int, with_deleted=False
|
self, api_key_id: int, with_deleted=False
|
||||||
) -> list[ApiKeyPermission]:
|
) -> list[ApiKeyPermission]:
|
||||||
f = [{ApiKeyPermission.api_key_id: api_key_id}]
|
f = [{ApiKeyPermission.api_key_id: api_key_id}]
|
||||||
if not with_deleted:
|
if not with_deleted:
|
||||||
f.append({ApiKeyPermission.deleted: False})
|
f.append({ApiKeyPermission.deleted: False})
|
||||||
|
|
||||||
return await self.find_by(f)
|
return await self.get_by(f)
|
||||||
|
|
||||||
async def find_by_permission_id(
|
async def get_by_permission_id(
|
||||||
self, permission_id: int, with_deleted=False
|
self, permission_id: int, with_deleted=False
|
||||||
) -> list[ApiKeyPermission]:
|
) -> list[ApiKeyPermission]:
|
||||||
f = [{ApiKeyPermission.permission_id: permission_id}]
|
f = [{ApiKeyPermission.permission_id: permission_id}]
|
||||||
if not with_deleted:
|
if not with_deleted:
|
||||||
f.append({ApiKeyPermission.deleted: False})
|
f.append({ApiKeyPermission.deleted: False})
|
||||||
|
|
||||||
return await self.find_by(f)
|
return await self.get_by(f)
|
||||||
|
|
||||||
|
|
||||||
apiKeyPermissionDao = ApiKeyPermissionDao()
|
apiKeyPermissionDao = ApiKeyPermissionDao()
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
DROP EXTENSION IF EXISTS fuzzystrmatch;
|
|
||||||
CREATE EXTENSION fuzzystrmatch SCHEMA public;
|
|
@ -1,133 +0,0 @@
|
|||||||
ALTER TABLE system._executed_migrations
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE system.files
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE system.files_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.short_url_visits
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.short_url_visits_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE system.feature_flags_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.user_settings_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE administration.users
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE administration.users_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.groups
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.groups_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.short_urls
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE administration.api_keys
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE administration.api_keys_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.domains
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.domains_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.short_urls_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE system.settings
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.group_role_assignments
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.group_role_assignments_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE system.settings_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE public.user_settings
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.permissions
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.permissions_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.roles
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.roles_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.role_permissions
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.role_permissions_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.role_users
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.role_users_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.api_key_permissions
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE permission.api_key_permissions_history
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
ALTER TABLE system.feature_flags
|
|
||||||
RENAME COLUMN createdutc TO created;
|
|
||||||
|
|
||||||
ALTER TABLE system._executed_migrations
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE system.files
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE system.files_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.short_url_visits
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.short_url_visits_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE system.feature_flags_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.user_settings_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE administration.users
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE administration.users_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.groups
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.groups_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.short_urls
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE administration.api_keys
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE administration.api_keys_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.domains
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.domains_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.short_urls_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE system.settings
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.group_role_assignments
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.group_role_assignments_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE system.settings_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE public.user_settings
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.permissions
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.permissions_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.roles
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.roles_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.role_permissions
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.role_permissions_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.role_users
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.role_users_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.api_key_permissions
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE permission.api_key_permissions_history
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
||||||
ALTER TABLE system.feature_flags
|
|
||||||
RENAME COLUMN updatedutc TO updated;
|
|
@ -1,37 +0,0 @@
|
|||||||
CREATE OR REPLACE FUNCTION public.history_trigger_function()
|
|
||||||
RETURNS TRIGGER AS
|
|
||||||
$$
|
|
||||||
DECLARE
|
|
||||||
schema_name TEXT;
|
|
||||||
history_table_name TEXT;
|
|
||||||
BEGIN
|
|
||||||
-- Construct the name of the history table based on the current table
|
|
||||||
schema_name := TG_TABLE_SCHEMA;
|
|
||||||
history_table_name := TG_TABLE_NAME || '_history';
|
|
||||||
|
|
||||||
IF (TG_OP = 'INSERT') THEN
|
|
||||||
RETURN NEW;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Insert the old row into the history table on UPDATE or DELETE
|
|
||||||
IF (TG_OP = 'UPDATE' OR TG_OP = 'DELETE') THEN
|
|
||||||
EXECUTE format(
|
|
||||||
'INSERT INTO %I.%I SELECT ($1).*',
|
|
||||||
schema_name,
|
|
||||||
history_table_name
|
|
||||||
)
|
|
||||||
USING OLD;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- For UPDATE, update the UpdatedUtc column and return the new row
|
|
||||||
IF (TG_OP = 'UPDATE') THEN
|
|
||||||
NEW.updated := NOW(); -- Update the UpdatedUtc column
|
|
||||||
RETURN NEW;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- For DELETE, return OLD to allow the deletion
|
|
||||||
IF (TG_OP = 'DELETE') THEN
|
|
||||||
RETURN OLD;
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
@ -1,23 +0,0 @@
|
|||||||
ALTER TABLE permission.role_permissions
|
|
||||||
ADD CONSTRAINT unique_role_permission
|
|
||||||
UNIQUE (roleid, permissionid);
|
|
||||||
|
|
||||||
ALTER TABLE permission.api_key_permissions
|
|
||||||
ADD CONSTRAINT unique_api_key_permission
|
|
||||||
UNIQUE (apikeyid, permissionid);
|
|
||||||
|
|
||||||
ALTER TABLE permission.role_users
|
|
||||||
ADD CONSTRAINT unique_role_user
|
|
||||||
UNIQUE (roleid, userid);
|
|
||||||
|
|
||||||
ALTER TABLE public.user_settings
|
|
||||||
ADD CONSTRAINT unique_user_setting
|
|
||||||
UNIQUE (userid, key);
|
|
||||||
|
|
||||||
ALTER TABLE system.settings
|
|
||||||
ADD CONSTRAINT unique_system_setting
|
|
||||||
UNIQUE (key);
|
|
||||||
|
|
||||||
ALTER TABLE system.feature_flags
|
|
||||||
ADD CONSTRAINT unique_feature_flag
|
|
||||||
UNIQUE (key);
|
|
@ -71,7 +71,7 @@ class PermissionSeeder(DataSeederABC):
|
|||||||
if admin_api_key is None:
|
if admin_api_key is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
admin_permissions = await apiKeyPermissionDao.find_by_api_key_id(
|
admin_permissions = await apiKeyPermissionDao.get_by_api_key_id(
|
||||||
admin_api_key.id, with_deleted=True
|
admin_api_key.id, with_deleted=True
|
||||||
)
|
)
|
||||||
to_assign = [
|
to_assign = [
|
||||||
|
@ -111,11 +111,7 @@ def _find_short_url_by_path(path: str) -> Optional[dict]:
|
|||||||
if "errors" in data:
|
if "errors" in data:
|
||||||
logger.warning(f"Failed to find short url by path {path} -> {data["errors"]}")
|
logger.warning(f"Failed to find short url by path {path} -> {data["errors"]}")
|
||||||
|
|
||||||
if (
|
if "data" not in data or "shortUrls" not in data["data"] or "nodes" not in data["data"]["shortUrls"]:
|
||||||
"data" not in data
|
|
||||||
or "shortUrls" not in data["data"]
|
|
||||||
or "nodes" not in data["data"]["shortUrls"]
|
|
||||||
):
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
data = data["data"]["shortUrls"]["nodes"]
|
data = data["data"]["shortUrls"]["nodes"]
|
||||||
|
@ -4,8 +4,4 @@ RUN rm -rf /usr/share/nginx/html/*
|
|||||||
COPY ./dist/open-redirect/browser/ /usr/share/nginx/html
|
COPY ./dist/open-redirect/browser/ /usr/share/nginx/html
|
||||||
RUN apk update
|
RUN apk update
|
||||||
RUN apk add bash
|
RUN apk add bash
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
RUN echo "{\"version\": \"${VERSION}\"}" > /usr/share/nginx/html/assets/version.json
|
|
||||||
|
|
||||||
CMD /bin/bash -c "envsubst '\$CONTAINER_NAME' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/nginx.conf; nginx -g 'daemon off;'"
|
CMD /bin/bash -c "envsubst '\$CONTAINER_NAME' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/nginx.conf; nginx -g 'daemon off;'"
|
||||||
|
@ -9,10 +9,7 @@
|
|||||||
"misprintCoefficient": "0.9",
|
"misprintCoefficient": "0.9",
|
||||||
"ignoredKeys": [
|
"ignoredKeys": [
|
||||||
"permissions.*",
|
"permissions.*",
|
||||||
"permission_descriptions.*",
|
"permission_descriptions.*"
|
||||||
"event.participants.attendance_states.*",
|
|
||||||
"event.participants.payment_*",
|
|
||||||
"primeng.*"
|
|
||||||
],
|
],
|
||||||
"ignoredMisprintKeys": [],
|
"ignoredMisprintKeys": [],
|
||||||
"customRegExpToFindKeys": [
|
"customRegExpToFindKeys": [
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
<main [class]="theme">
|
<main *ngIf="isLoggedIn && !hideUI; else home" [class]="theme">
|
||||||
<div
|
|
||||||
class="warning bg3 flex justify-center p-1.5"
|
|
||||||
*ngIf="showTechnicalDemoBanner">
|
|
||||||
{{ 'technical_demo_banner' | translate }}
|
|
||||||
</div>
|
|
||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
|
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<aside *ngIf="showSidebar">
|
<aside *ngIf="showSidebar">
|
||||||
<app-sidebar></app-sidebar>
|
<app-sidebar></app-sidebar>
|
||||||
</aside>
|
</aside>
|
||||||
<section class="component" *ngIf="loadedGuiSettings">
|
<section class="component">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@ -35,3 +30,7 @@
|
|||||||
</p-confirmDialog>
|
</p-confirmDialog>
|
||||||
</main>
|
</main>
|
||||||
<app-spinner></app-spinner>
|
<app-spinner></app-spinner>
|
||||||
|
|
||||||
|
<ng-template #home>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</ng-template>
|
@ -2,8 +2,8 @@ import { Component, OnDestroy } from '@angular/core';
|
|||||||
import { SidebarService } from 'src/app/service/sidebar.service';
|
import { SidebarService } from 'src/app/service/sidebar.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { AuthService } from 'src/app/service/auth.service';
|
||||||
import { GuiService } from 'src/app/service/gui.service';
|
import { GuiService } from 'src/app/service/gui.service';
|
||||||
import { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -11,35 +11,36 @@ import { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
|||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss',
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnDestroy {
|
export class AppComponent implements OnDestroy {
|
||||||
|
theme = 'open-redirect';
|
||||||
showSidebar = false;
|
showSidebar = false;
|
||||||
theme = 'lan-maestro';
|
hideUI = false;
|
||||||
showTechnicalDemoBanner = false;
|
isLoggedIn = false;
|
||||||
|
|
||||||
loadedGuiSettings = false;
|
|
||||||
unsubscribe$ = new Subject<void>();
|
unsubscribe$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sidebar: SidebarService,
|
private sidebar: SidebarService,
|
||||||
private gui: GuiService,
|
private auth: AuthService,
|
||||||
private features: FeatureFlagService
|
private gui: GuiService
|
||||||
) {
|
) {
|
||||||
this.features.get('TechnicalDemoBanner').then(showTechnicalDemoBanner => {
|
this.auth.loadUser();
|
||||||
this.showTechnicalDemoBanner = showTechnicalDemoBanner;
|
|
||||||
|
this.auth.user$.pipe(takeUntil(this.unsubscribe$)).subscribe(user => {
|
||||||
|
this.isLoggedIn = user !== null && user !== undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sidebar.visible$
|
this.sidebar.visible$
|
||||||
.pipe(takeUntil(this.unsubscribe$))
|
.pipe(takeUntil(this.unsubscribe$))
|
||||||
.subscribe(visible => {
|
.subscribe(visible => {
|
||||||
this.showSidebar = visible;
|
this.showSidebar = visible;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.gui.hideGui$.pipe(takeUntil(this.unsubscribe$)).subscribe(hide => {
|
||||||
|
this.hideUI = hide;
|
||||||
|
});
|
||||||
|
|
||||||
this.gui.theme$.pipe(takeUntil(this.unsubscribe$)).subscribe(theme => {
|
this.gui.theme$.pipe(takeUntil(this.unsubscribe$)).subscribe(theme => {
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
});
|
});
|
||||||
this.gui.loadedGuiSettings$
|
|
||||||
.pipe(takeUntil(this.unsubscribe$))
|
|
||||||
.subscribe(loaded => {
|
|
||||||
this.loadedGuiSettings = loaded;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
|
||||||
APP_INITIALIZER,
|
|
||||||
ApplicationRef,
|
|
||||||
DoBootstrap,
|
|
||||||
ErrorHandler,
|
|
||||||
Injector,
|
|
||||||
NgModule,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
@ -30,8 +23,6 @@ import { SidebarComponent } from './components/sidebar/sidebar.component';
|
|||||||
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
||||||
import { ConfigService } from 'src/app/service/config.service';
|
import { ConfigService } from 'src/app/service/config.service';
|
||||||
import { ServerUnavailableComponent } from 'src/app/components/error/server-unavailable/server-unavailable.component';
|
import { ServerUnavailableComponent } from 'src/app/components/error/server-unavailable/server-unavailable.component';
|
||||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Logger.enableProductionMode();
|
Logger.enableProductionMode();
|
||||||
@ -104,20 +95,6 @@ export function appInitializerFactory(
|
|||||||
useClass: ErrorHandlingService,
|
useClass: ErrorHandlingService,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class AppModule implements DoBootstrap {
|
export class AppModule {}
|
||||||
constructor(private injector: Injector) {}
|
|
||||||
|
|
||||||
async ngDoBootstrap(appRef: ApplicationRef) {
|
|
||||||
const spinner = this.injector.get(SpinnerService);
|
|
||||||
spinner.show();
|
|
||||||
|
|
||||||
const auth = this.injector.get(AuthService);
|
|
||||||
const user = await auth.loadUser();
|
|
||||||
if (!user) {
|
|
||||||
await auth.login();
|
|
||||||
}
|
|
||||||
|
|
||||||
appRef.bootstrap(AppComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ErrorComponentBase } from 'src/app/core/base/error-component-base';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-not-found',
|
selector: 'app-not-found',
|
||||||
templateUrl: './not-found.component.html',
|
templateUrl: './not-found.component.html',
|
||||||
styleUrls: ['./not-found.component.scss'],
|
styleUrls: ['./not-found.component.scss'],
|
||||||
})
|
})
|
||||||
export class NotFoundComponent extends ErrorComponentBase {}
|
export class NotFoundComponent {}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ErrorComponentBase } from 'src/app/core/base/error-component-base';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-server-unavailable',
|
selector: 'app-server-unavailable',
|
||||||
templateUrl: './server-unavailable.component.html',
|
templateUrl: './server-unavailable.component.html',
|
||||||
styleUrls: ['./server-unavailable.component.scss'],
|
styleUrls: ['./server-unavailable.component.scss'],
|
||||||
})
|
})
|
||||||
export class ServerUnavailableComponent extends ErrorComponentBase {
|
export class ServerUnavailableComponent {
|
||||||
constructor(private router: Router) {
|
constructor(private router: Router) {}
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async retryConnection() {
|
async retryConnection() {
|
||||||
await this.router.navigate(['/']);
|
await this.router.navigate(['/']);
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
<footer class="flex justify-between pl-1 pr-1">
|
<footer>
|
||||||
<div class="hidden md:block">
|
|
||||||
<span>web: {{ webVersion }}</span>
|
|
||||||
<span class="divider"> | </span>
|
|
||||||
<span>api: {{ apiVersion }}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a [href]="termsUrl">{{ 'footer.terms' | translate }}</a>
|
<a [href]="termsUrl">{{ 'footer.terms' | translate }}</a>
|
||||||
<span class="divider"> | </span>
|
<span class="divider"> | </span>
|
||||||
<a [href]="privacyUrl">{{ 'footer.privacy' | translate }}</a>
|
<a [href]="privacyUrl">{{ 'footer.privacy' | translate }}</a>
|
||||||
<span class="divider"> | </span>
|
<span class="divider"> | </span>
|
||||||
<a [href]="imprintUrl">{{ 'footer.imprint' | translate }}</a>
|
<a [href]="imprintUrl">{{ 'footer.imprint' | translate }}</a>
|
||||||
</div>
|
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
@import "../../../styles/constants.scss";
|
||||||
|
|
||||||
|
footer {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 25px;
|
||||||
|
padding: 0 5px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +1,23 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
|
||||||
import { FooterComponent } from 'src/app/components/footer/footer.component';
|
import { FooterComponent } from "src/app/components/footer/footer.component";
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from "@ngx-translate/core";
|
||||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
import { BrowserModule } from "@angular/platform-browser";
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
|
||||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { of } from 'rxjs';
|
|
||||||
|
|
||||||
describe('FooterComponent', () => {
|
describe("FooterComponent", () => {
|
||||||
let component: FooterComponent;
|
let component: FooterComponent;
|
||||||
let fixture: ComponentFixture<FooterComponent>;
|
let fixture: ComponentFixture<FooterComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [FooterComponent],
|
declarations: [FooterComponent],
|
||||||
imports: [SharedModule, TranslateModule.forRoot()],
|
imports: [
|
||||||
providers: [
|
BrowserModule,
|
||||||
AuthService,
|
BrowserAnimationsModule,
|
||||||
KeycloakService,
|
SharedModule,
|
||||||
ErrorHandlingService,
|
TranslateModule.forRoot(),
|
||||||
ToastService,
|
|
||||||
MessageService,
|
|
||||||
ConfirmationService,
|
|
||||||
{
|
|
||||||
provide: ActivatedRoute,
|
|
||||||
useValue: {
|
|
||||||
snapshot: { params: of({}) },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
@ -42,7 +28,7 @@ describe('FooterComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it("should create", () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ConfigService } from 'src/app/service/config.service';
|
import { ConfigService } from 'src/app/service/config.service';
|
||||||
import { VersionService } from 'src/app/service/version.service';
|
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-footer',
|
selector: 'app-footer',
|
||||||
@ -9,21 +7,7 @@ import { ToastService } from 'src/app/service/toast.service';
|
|||||||
styleUrls: ['./footer.component.scss'],
|
styleUrls: ['./footer.component.scss'],
|
||||||
})
|
})
|
||||||
export class FooterComponent {
|
export class FooterComponent {
|
||||||
webVersion = '0.0.0';
|
constructor(private config: ConfigService) {}
|
||||||
apiVersion = '0.0.0';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private toast: ToastService,
|
|
||||||
private config: ConfigService,
|
|
||||||
private version: VersionService
|
|
||||||
) {
|
|
||||||
this.version.getApiVersion().subscribe(version => {
|
|
||||||
this.apiVersion = version;
|
|
||||||
});
|
|
||||||
this.version.getWebVersion().subscribe(version => {
|
|
||||||
this.webVersion = version.version;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get termsUrl(): string {
|
get termsUrl(): string {
|
||||||
return this.config.settings.termsUrl;
|
return this.config.settings.termsUrl;
|
||||||
|
@ -2,25 +2,22 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<p-button
|
<p-button
|
||||||
type="button"
|
*ngIf="user"
|
||||||
icon="pi pi-bars"
|
icon="pi pi-bars"
|
||||||
class="btn icon-btn p-button-text"
|
class="btn icon-btn p-button-text"
|
||||||
(onClick)="toggleSidebar()"></p-button>
|
(onClick)="toggleSidebar()"
|
||||||
|
></p-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<!-- <img src="/assets/images/logo.svg" alt="logo"/>-->
|
<!-- <img src="/assets/images/logo.svg" alt="logo"/>-->
|
||||||
</div>
|
</div>
|
||||||
<div class="app-name">
|
<div class="app-name">
|
||||||
<h1>LAN-Maestro</h1>
|
<h1>Open-redirect</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-center w-1/3" *ngIf="menu.length > 0">
|
|
||||||
<app-menu-bar class="w-full" [elements]="menu"></app-menu-bar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<div class="flex items-center justify-center" *ngIf="themeList.length > 0">
|
<div class="flex items-center justify-center">
|
||||||
<p-button
|
<p-button
|
||||||
type="button"
|
type="button"
|
||||||
icon="pi pi-palette"
|
icon="pi pi-palette"
|
||||||
@ -32,7 +29,7 @@
|
|||||||
[model]="themeList"
|
[model]="themeList"
|
||||||
class="lang-menu"></p-menu>
|
class="lang-menu"></p-menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center" *ngIf="langList.length > 0">
|
<div class="flex items-center justify-center">
|
||||||
<p-button
|
<p-button
|
||||||
type="button"
|
type="button"
|
||||||
icon="pi pi-globe"
|
icon="pi pi-globe"
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
import { HeaderComponent } from 'src/app/components/header/header.component';
|
import { HeaderComponent } from "src/app/components/header/header.component";
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from "@ngx-translate/core";
|
||||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
import { ConfirmationService, MessageService } from "primeng/api";
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { of } from 'rxjs';
|
import { of } from "rxjs";
|
||||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
import { ErrorHandlingService } from "src/app/service/error-handling.service";
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
import { AuthService } from "src/app/service/auth.service";
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
import { KeycloakService } from "keycloak-angular";
|
||||||
|
|
||||||
describe('HeaderComponent', () => {
|
describe("HeaderComponent", () => {
|
||||||
let component: HeaderComponent;
|
let component: HeaderComponent;
|
||||||
let fixture: ComponentFixture<HeaderComponent>;
|
let fixture: ComponentFixture<HeaderComponent>;
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ describe('HeaderComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it("should create", () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -48,12 +48,12 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.auth.user$.pipe(takeUntil(this.unsubscribe$)).subscribe(async user => {
|
this.auth.user$.pipe(takeUntil(this.unsubscribe$)).subscribe(async user => {
|
||||||
|
this.user = user;
|
||||||
await this.initMenuLists();
|
await this.initMenuLists();
|
||||||
|
if (user) {
|
||||||
await this.loadTheme();
|
await this.loadTheme();
|
||||||
await this.loadLang();
|
await this.loadLang();
|
||||||
|
}
|
||||||
this.user = user;
|
|
||||||
this.guiService.loadedGuiSettings$.next(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.themeList = this.config.settings.themes.map(theme => {
|
this.themeList = this.config.settings.themes.map(theme => {
|
||||||
@ -87,7 +87,27 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initMenuList() {
|
async initMenuList() {
|
||||||
this.menu = [];
|
this.menu = [
|
||||||
|
{
|
||||||
|
label: 'common.news',
|
||||||
|
routerLink: ['/'],
|
||||||
|
icon: 'pi pi-home',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'header.menu.about',
|
||||||
|
routerLink: ['/about'],
|
||||||
|
icon: 'pi pi-info',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.auth.user$.value) {
|
||||||
|
this.menu.push({
|
||||||
|
label: 'header.menu.admin',
|
||||||
|
routerLink: ['/admin'],
|
||||||
|
icon: 'pi pi-cog',
|
||||||
|
visible: await this.auth.isAdmin(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async initLangMenuList() {
|
async initLangMenuList() {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<p>home works!</p>
|
|
@ -1,6 +1,15 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { HomeComponent } from './home.component';
|
import { HomeComponent } from './home.component';
|
||||||
|
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from 'src/app/service/auth.service';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
||||||
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
describe('HomeComponent', () => {
|
describe('HomeComponent', () => {
|
||||||
let component: HomeComponent;
|
let component: HomeComponent;
|
||||||
@ -9,6 +18,21 @@ describe('HomeComponent', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [HomeComponent],
|
declarations: [HomeComponent],
|
||||||
|
imports: [SharedModule, TranslateModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
KeycloakService,
|
||||||
|
ErrorHandlingService,
|
||||||
|
ToastService,
|
||||||
|
MessageService,
|
||||||
|
ConfirmationService,
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
snapshot: { params: of({}) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HomeComponent);
|
fixture = TestBed.createComponent(HomeComponent);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
@ -7,7 +7,9 @@ import { SpinnerService } from 'src/app/service/spinner.service';
|
|||||||
styleUrl: './home.component.scss',
|
styleUrl: './home.component.scss',
|
||||||
})
|
})
|
||||||
export class HomeComponent {
|
export class HomeComponent {
|
||||||
constructor(private spinner: SpinnerService) {
|
constructor(private keycloak: KeycloakService) {
|
||||||
this.spinner.hide();
|
if (!this.keycloak.isLoggedIn()) {
|
||||||
|
this.keycloak.login().then(() => {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
|
||||||
import { SidebarComponent } from './sidebar.component';
|
import { SidebarComponent } from "./sidebar.component";
|
||||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from "@ngx-translate/core";
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
import { AuthService } from "src/app/service/auth.service";
|
||||||
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
import { ErrorHandlingService } from "src/app/service/error-handling.service";
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
import { ConfirmationService, MessageService } from "primeng/api";
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { of } from 'rxjs';
|
import { of } from "rxjs";
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
import { KeycloakService } from "keycloak-angular";
|
||||||
|
|
||||||
describe('SidebarComponent', () => {
|
describe("SidebarComponent", () => {
|
||||||
let component: SidebarComponent;
|
let component: SidebarComponent;
|
||||||
let fixture: ComponentFixture<SidebarComponent>;
|
let fixture: ComponentFixture<SidebarComponent>;
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ describe('SidebarComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it("should create", () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Component, OnDestroy } from '@angular/core';
|
import { Component, OnDestroy } from "@angular/core";
|
||||||
import { MenuElement } from 'src/app/model/view/menu-element';
|
import { MenuElement } from "src/app/model/view/menu-element";
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from "rxjs";
|
||||||
import { SidebarService } from 'src/app/service/sidebar.service';
|
import { SidebarService } from "src/app/service/sidebar.service";
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from "rxjs/operators";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidebar',
|
selector: "app-sidebar",
|
||||||
templateUrl: './sidebar.component.html',
|
templateUrl: "./sidebar.component.html",
|
||||||
styleUrl: './sidebar.component.scss',
|
styleUrl: "./sidebar.component.scss",
|
||||||
})
|
})
|
||||||
export class SidebarComponent implements OnDestroy {
|
export class SidebarComponent implements OnDestroy {
|
||||||
elements: MenuElement[] = [];
|
elements: MenuElement[] = [];
|
||||||
@ -17,7 +17,7 @@ export class SidebarComponent implements OnDestroy {
|
|||||||
constructor(private sidebar: SidebarService) {
|
constructor(private sidebar: SidebarService) {
|
||||||
this.sidebar.elements$
|
this.sidebar.elements$
|
||||||
.pipe(takeUntil(this.unsubscribe$))
|
.pipe(takeUntil(this.unsubscribe$))
|
||||||
.subscribe(elements => {
|
.subscribe((elements) => {
|
||||||
this.elements = elements;
|
this.elements = elements;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
|
||||||
import { SpinnerComponent } from 'src/app/components/spinner/spinner.component';
|
import { SpinnerComponent } from "src/app/components/spinner/spinner.component";
|
||||||
|
|
||||||
describe('SpinnerComponent', () => {
|
describe("SpinnerComponent", () => {
|
||||||
let component: SpinnerComponent;
|
let component: SpinnerComponent;
|
||||||
let fixture: ComponentFixture<SpinnerComponent>;
|
let fixture: ComponentFixture<SpinnerComponent>;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ describe('SpinnerComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it("should create", () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
import { SpinnerService } from "src/app/service/spinner.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-spinner',
|
selector: "app-spinner",
|
||||||
templateUrl: './spinner.component.html',
|
templateUrl: "./spinner.component.html",
|
||||||
styleUrls: ['./spinner.component.scss'],
|
styleUrls: ["./spinner.component.scss"],
|
||||||
})
|
})
|
||||||
export class SpinnerComponent {
|
export class SpinnerComponent {
|
||||||
showSpinnerState = false;
|
showSpinnerState = false;
|
||||||
|
|
||||||
constructor(public spinnerService: SpinnerService) {
|
constructor(public spinnerService: SpinnerService) {
|
||||||
this.spinnerService.showSpinnerState$.subscribe(value => {
|
this.spinnerService.showSpinnerState$.subscribe((value) => {
|
||||||
this.showSpinnerState = value;
|
this.showSpinnerState = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
|
||||||
|
|
||||||
export class ErrorComponentBase {
|
|
||||||
constructor() {
|
|
||||||
const spinner = inject(SpinnerService);
|
|
||||||
spinner.hide();
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,8 +26,8 @@ export abstract class FormPageBase<
|
|||||||
protected filterService = inject(FilterService);
|
protected filterService = inject(FilterService);
|
||||||
protected dataService = inject(PageDataService) as S;
|
protected dataService = inject(PageDataService) as S;
|
||||||
|
|
||||||
protected constructor(idKey: string = 'id') {
|
protected constructor() {
|
||||||
const id = this.route.snapshot.params[idKey];
|
const id = this.route.snapshot.params['id'];
|
||||||
this.validateRoute(id);
|
this.validateRoute(id);
|
||||||
|
|
||||||
this.buildForm();
|
this.buildForm();
|
||||||
|
@ -115,6 +115,7 @@ export abstract class PageBase<
|
|||||||
.onChange()
|
.onChange()
|
||||||
.pipe(takeUntil(this.unsubscribe$))
|
.pipe(takeUntil(this.unsubscribe$))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
|
logger.debug('Reload data');
|
||||||
this.load(true);
|
this.load(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { MutationResult } from 'apollo-angular';
|
|
||||||
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
|
||||||
import { Sort } from 'src/app/model/graphql/filter/sort.model';
|
|
||||||
import { QueryResult } from 'src/app/model/entities/query-result';
|
|
||||||
import { DbModel } from 'src/app/model/entities/db-model';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export abstract class PageWithHistoryDataService<T> {
|
|
||||||
abstract load(
|
|
||||||
filter?: Filter[],
|
|
||||||
sort?: Sort[],
|
|
||||||
skip?: number,
|
|
||||||
take?: number
|
|
||||||
): Observable<QueryResult<T>>;
|
|
||||||
|
|
||||||
abstract loadHistory(id: number, options?: object): Observable<DbModel[]>;
|
|
||||||
|
|
||||||
abstract onChange(): Observable<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Create<T, C> {
|
|
||||||
create(object: C): Observable<T | undefined> | Observable<MutationResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Update<T, U> {
|
|
||||||
update(object: U): Observable<T | undefined> | Observable<MutationResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Delete<T> {
|
|
||||||
delete(
|
|
||||||
object: T
|
|
||||||
): Observable<T | undefined | boolean> | Observable<MutationResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Restore<T> {
|
|
||||||
restore(
|
|
||||||
object: T
|
|
||||||
): Observable<T | undefined | boolean> | Observable<MutationResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoadHistory<T> {
|
|
||||||
loadHistory(
|
|
||||||
id: number
|
|
||||||
): Observable<T | undefined | boolean> | Observable<DbModel[] | undefined>;
|
|
||||||
}
|
|
@ -14,7 +14,6 @@ export const ID_COLUMN = {
|
|||||||
translationKey: 'common.id',
|
translationKey: 'common.id',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
|
||||||
value: (row: { id?: number }) => row.id,
|
value: (row: { id?: number }) => row.id,
|
||||||
class: 'max-w-24',
|
class: 'max-w-24',
|
||||||
};
|
};
|
||||||
@ -24,7 +23,6 @@ export const NAME_COLUMN = {
|
|||||||
translationKey: 'common.name',
|
translationKey: 'common.name',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
|
||||||
value: (row: { name?: string }) => row.name,
|
value: (row: { name?: string }) => row.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,7 +31,6 @@ export const DESCRIPTION_COLUMN = {
|
|||||||
translationKey: 'common.description',
|
translationKey: 'common.description',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
|
||||||
value: (row: { description?: string }) => row.description,
|
value: (row: { description?: string }) => row.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,49 +38,35 @@ export const DELETED_COLUMN = {
|
|||||||
name: 'deleted',
|
name: 'deleted',
|
||||||
translationKey: 'common.deleted',
|
translationKey: 'common.deleted',
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
filterable: false,
|
filterable: true,
|
||||||
sortable: true,
|
|
||||||
value: (row: DbModel) => row.deleted,
|
value: (row: DbModel) => row.deleted,
|
||||||
visible: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EDITOR_COLUMN = {
|
export const EDITOR_COLUMN = {
|
||||||
name: 'editor',
|
name: 'editor',
|
||||||
translationKey: 'common.editor',
|
translationKey: 'common.editor',
|
||||||
type: 'text',
|
|
||||||
filterable: true,
|
|
||||||
value: (row: DbModel) => row.editor?.username,
|
value: (row: DbModel) => row.editor?.username,
|
||||||
filterSelector: (mode: string, value: unknown) => {
|
|
||||||
return { editor: { username: { [mode]: value } } };
|
|
||||||
},
|
|
||||||
class: 'max-w-32',
|
|
||||||
visible: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CREATED_UTC_COLUMN = {
|
export const CREATED_UTC_COLUMN = {
|
||||||
name: 'created',
|
name: 'createdUtc',
|
||||||
translationKey: 'common.created',
|
translationKey: 'common.created',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
value: (row: DbModel) => row.createdUtc,
|
||||||
value: (row: DbModel) => row.created,
|
|
||||||
class: 'max-w-32',
|
class: 'max-w-32',
|
||||||
visible: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UPDATED_UTC_COLUMN = {
|
export const UPDATED_UTC_COLUMN = {
|
||||||
name: 'updated',
|
name: 'updatedUtc',
|
||||||
translationKey: 'common.updated',
|
translationKey: 'common.updated',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
value: (row: DbModel) => row.updatedUtc,
|
||||||
value: (row: DbModel) => row.updated,
|
|
||||||
class: 'max-w-32',
|
class: 'max-w-32',
|
||||||
visible: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DB_MODEL_COLUMNS = [
|
export const DB_MODEL_COLUMNS = [
|
||||||
DELETED_COLUMN,
|
|
||||||
EDITOR_COLUMN,
|
EDITOR_COLUMN,
|
||||||
CREATED_UTC_COLUMN,
|
CREATED_UTC_COLUMN,
|
||||||
UPDATED_UTC_COLUMN,
|
UPDATED_UTC_COLUMN,
|
||||||
|
@ -1,39 +1,22 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import { CanActivate, Router } from '@angular/router';
|
import { CanActivate } from "@angular/router";
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
import { KeycloakService } from "keycloak-angular";
|
||||||
import { Logger } from 'src/app/service/logger.service';
|
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
|
||||||
|
|
||||||
const logger = new Logger('AuthGuard');
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
constructor(
|
constructor(private keycloak: KeycloakService) {}
|
||||||
private keycloak: KeycloakService,
|
|
||||||
private auth: AuthService,
|
|
||||||
private router: Router
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async canActivate(): Promise<boolean> {
|
async canActivate(): Promise<boolean> {
|
||||||
try {
|
|
||||||
if (!this.keycloak.isLoggedIn()) {
|
|
||||||
logger.debug('User not logged in, redirecting to login page');
|
|
||||||
await this.auth.login();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.keycloak.isTokenExpired()) {
|
if (this.keycloak.isTokenExpired()) {
|
||||||
logger.debug('Token expired, updating token');
|
|
||||||
await this.keycloak.updateToken();
|
await this.keycloak.updateToken();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
logger.error('Error during authentication', err);
|
if (!this.keycloak.isLoggedIn()) {
|
||||||
await this.router.navigate(['/']);
|
await this.keycloak.login();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('Check is user logged in');
|
|
||||||
return this.keycloak.isLoggedIn();
|
return this.keycloak.isLoggedIn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import { ActivatedRouteSnapshot, Router } from '@angular/router';
|
import { ActivatedRouteSnapshot, Router } from "@angular/router";
|
||||||
import { Logger } from 'src/app/service/logger.service';
|
import { Logger } from "src/app/service/logger.service";
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
import { AuthService } from "src/app/service/auth.service";
|
||||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum";
|
||||||
|
|
||||||
const log = new Logger('PermissionGuard');
|
const log = new Logger("PermissionGuard");
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
export class PermissionGuard {
|
export class PermissionGuard {
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private auth: AuthService
|
private auth: AuthService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||||
const permissions = route.data['permissions'] as PermissionsEnum[];
|
const permissions = route.data["permissions"] as PermissionsEnum[];
|
||||||
|
|
||||||
if (!permissions || permissions.length === 0) {
|
if (!permissions || permissions.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
@ -26,11 +26,11 @@ export class PermissionGuard {
|
|||||||
|
|
||||||
const validate = await this.auth.hasAnyPermissionLazy(permissions);
|
const validate = await this.auth.hasAnyPermissionLazy(permissions);
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
log.debug('Permission denied', permissions);
|
log.debug("Permission denied", permissions);
|
||||||
this.toast.warn('common.warning', 'error.permission_denied');
|
this.toast.warn("common.warning", "error.permission_denied");
|
||||||
this.router.navigate(['/']).then();
|
this.router.navigate(["/"]).then();
|
||||||
}
|
}
|
||||||
log.debug('Permission granted', permissions);
|
log.debug("Permission granted", permissions);
|
||||||
return validate;
|
return validate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,55 +2,27 @@ import { HttpInterceptorFn } from '@angular/common/http';
|
|||||||
import { KeycloakService } from 'keycloak-angular';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { inject } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import { from, switchMap } from 'rxjs';
|
import { from, switchMap } from 'rxjs';
|
||||||
import { ConfigService } from 'src/app/service/config.service';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
|
||||||
|
|
||||||
export const tokenInterceptor: HttpInterceptorFn = (req, next) => {
|
export const tokenInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
const config = inject(ConfigService);
|
|
||||||
if (
|
|
||||||
!config.settings.api.url ||
|
|
||||||
!req.url.startsWith(config.settings.api.url)
|
|
||||||
) {
|
|
||||||
return next(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
const keycloak = inject(KeycloakService);
|
const keycloak = inject(KeycloakService);
|
||||||
|
|
||||||
if (!keycloak.isLoggedIn()) {
|
if (!keycloak.isLoggedIn()) {
|
||||||
return next(req);
|
return next(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keycloak.isTokenExpired()) {
|
||||||
|
keycloak.updateToken().then();
|
||||||
|
}
|
||||||
|
|
||||||
return from(keycloak.getToken()).pipe(
|
return from(keycloak.getToken()).pipe(
|
||||||
switchMap(token => {
|
switchMap(token => {
|
||||||
if (!token) {
|
const modifiedReq = token
|
||||||
return next(req);
|
? req.clone({
|
||||||
}
|
|
||||||
|
|
||||||
if (!keycloak.isTokenExpired()) {
|
|
||||||
return next(
|
|
||||||
req.clone({
|
|
||||||
headers: req.headers.set('Authorization', `Bearer ${token}`),
|
headers: req.headers.set('Authorization', `Bearer ${token}`),
|
||||||
})
|
})
|
||||||
);
|
: req;
|
||||||
}
|
|
||||||
|
|
||||||
return from(keycloak.updateToken(30)).pipe(
|
return next(modifiedReq);
|
||||||
switchMap(() => {
|
|
||||||
return keycloak.getToken();
|
|
||||||
}),
|
|
||||||
switchMap(newToken => {
|
|
||||||
return next(
|
|
||||||
req.clone({
|
|
||||||
headers: req.headers.set('Authorization', `Bearer ${newToken}`),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
catchError(() => {
|
|
||||||
const auth = inject(AuthService);
|
|
||||||
auth.logout().then();
|
|
||||||
return next(req);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,6 @@ export enum PermissionsEnum {
|
|||||||
// Administration
|
// Administration
|
||||||
administrator = 'administrator',
|
administrator = 'administrator',
|
||||||
|
|
||||||
// Settings
|
|
||||||
settings = 'settings',
|
|
||||||
settingsUpdate = 'settings.update',
|
|
||||||
|
|
||||||
apiKeys = 'api_keys',
|
apiKeys = 'api_keys',
|
||||||
apiKeysCreate = 'api_keys.create',
|
apiKeysCreate = 'api_keys.create',
|
||||||
apiKeysUpdate = 'api_keys.update',
|
apiKeysUpdate = 'api_keys.update',
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { Role } from 'src/app/model/entities/role';
|
import { Role } from "src/app/model/entities/role";
|
||||||
import { DbModelWithHistory } from 'src/app/model/entities/db-model';
|
import { DbModel } from "src/app/model/entities/db-model";
|
||||||
|
|
||||||
export interface NotExistingUser {
|
export interface NotExistingUser {
|
||||||
keycloakId: string;
|
keycloakId: string;
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User extends DbModelWithHistory {
|
export interface User extends DbModel {
|
||||||
id: number;
|
id: number;
|
||||||
keycloakId: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
roles: Role[];
|
roles: Role[];
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { DbModelWithHistory } from 'src/app/model/entities/db-model';
|
import { DbModel } from "src/app/model/entities/db-model";
|
||||||
import { Permission } from 'src/app/model/entities/role';
|
import { Permission } from "src/app/model/entities/role";
|
||||||
|
|
||||||
export interface ApiKey extends DbModelWithHistory {
|
export interface ApiKey extends DbModel {
|
||||||
identifier?: string;
|
identifier?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
permissions?: Permission[];
|
permissions?: Permission[];
|
||||||
|
@ -1,19 +1,9 @@
|
|||||||
import { User } from 'src/app/model/auth/user';
|
import { User } from "src/app/model/auth/user";
|
||||||
|
|
||||||
export interface DbModelWithHistory {
|
|
||||||
id?: number;
|
|
||||||
editor?: User;
|
|
||||||
deleted?: boolean;
|
|
||||||
created?: Date;
|
|
||||||
updated?: Date;
|
|
||||||
|
|
||||||
history?: DbModel[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DbModel {
|
export interface DbModel {
|
||||||
id?: number;
|
id?: number;
|
||||||
editor?: User;
|
editor?: User;
|
||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
created?: Date;
|
createdUtc?: Date;
|
||||||
updated?: Date;
|
updatedUtc?: Date;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user