parent
f87b60f128
commit
b72916a8d9
@ -76,8 +76,8 @@ class RouteUserExtension:
|
||||
flat_list = []
|
||||
for group in groups:
|
||||
flat_list.append(group)
|
||||
if 'subGroups' in group and group['subGroups']:
|
||||
flat_list.extend(cls._flatten_groups(group['subGroups']))
|
||||
if "subGroups" in group and group["subGroups"]:
|
||||
flat_list.extend(cls._flatten_groups(group["subGroups"]))
|
||||
return flat_list
|
||||
|
||||
@classmethod
|
||||
@ -88,9 +88,13 @@ class RouteUserExtension:
|
||||
groups_with_role = [x["name"] for x in groups if x["name"] in roles.keys()]
|
||||
|
||||
user_groups_with_role = [
|
||||
x["name"] for x in Keycloak.admin.get_user_groups(user.keycloak_id) if x["name"] in roles.keys()
|
||||
x["name"]
|
||||
for x in Keycloak.admin.get_user_groups(user.keycloak_id)
|
||||
if x["name"] in roles.keys()
|
||||
]
|
||||
user_roles = set(x.name for x in await user.roles if x.name in groups_with_role)
|
||||
user_roles = set(
|
||||
x.name for x in await user.roles if x.name in groups_with_role
|
||||
)
|
||||
missing_groups = set(user_groups_with_role) - set(user_roles)
|
||||
missing_roles = set(user_roles) - set(user_groups_with_role)
|
||||
|
||||
@ -105,10 +109,12 @@ class RouteUserExtension:
|
||||
if len(missing_roles) > 0:
|
||||
await roleUserDao.delete_many(
|
||||
[
|
||||
await roleUserDao.get_single_by([
|
||||
await roleUserDao.get_single_by(
|
||||
[
|
||||
{RoleUser.role_id: roles[role].id},
|
||||
{RoleUser.user_id: user.id},
|
||||
])
|
||||
]
|
||||
)
|
||||
for role in missing_roles
|
||||
]
|
||||
)
|
||||
|
@ -69,8 +69,10 @@ class QueryABC(ObjectType):
|
||||
):
|
||||
if len(permissions) > 0:
|
||||
user = await Route.get_authenticated_user_or_api_key_or_default()
|
||||
perms = await user.permissions
|
||||
has_perms = [await user.has_permission(x) for x in permissions]
|
||||
if user is not None and all(
|
||||
[await user.has_permission(x) for x in permissions]
|
||||
has_perms
|
||||
):
|
||||
return
|
||||
|
||||
|
@ -9,6 +9,7 @@ type Group implements DbModel {
|
||||
name: String
|
||||
|
||||
shortUrls: [ShortUrl]
|
||||
roles: [Role]
|
||||
|
||||
deleted: Boolean
|
||||
editor: User
|
||||
@ -45,9 +46,11 @@ type GroupMutation {
|
||||
|
||||
input GroupCreateInput {
|
||||
name: String!
|
||||
roles: [ID]
|
||||
}
|
||||
|
||||
input GroupUpdateInput {
|
||||
id: ID!
|
||||
name: String
|
||||
roles: [ID]
|
||||
}
|
@ -7,7 +7,12 @@ class GroupCreateInput(InputABC):
|
||||
InputABC.__init__(self, src)
|
||||
|
||||
self._name = self.option("name", str, required=True)
|
||||
self._roles = self.option("roles", list[int])
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def roles(self) -> list[int]:
|
||||
return self._roles
|
||||
|
@ -8,6 +8,7 @@ class GroupUpdateInput(InputABC):
|
||||
|
||||
self._id = self.option("id", int, required=True)
|
||||
self._name = self.option("name", str)
|
||||
self._roles = self.option("roles", list[int])
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
@ -16,3 +17,7 @@ class GroupUpdateInput(InputABC):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def roles(self) -> list[int]:
|
||||
return self._roles
|
||||
|
@ -33,7 +33,6 @@ class Mutation(MutationABC):
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
self.add_mutation_type(
|
||||
"domain",
|
||||
"Domain",
|
||||
|
@ -1,9 +1,13 @@
|
||||
from typing import Optional
|
||||
|
||||
from api_graphql.abc.mutation_abc import MutationABC
|
||||
from api_graphql.input.group_create_input import GroupCreateInput
|
||||
from api_graphql.input.group_update_input import GroupUpdateInput
|
||||
from core.logger import APILogger
|
||||
from data.schemas.public.group import Group
|
||||
from data.schemas.public.group_dao import groupDao
|
||||
from data.schemas.public.group_role_assignment import GroupRoleAssignment
|
||||
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
||||
from service.permission.permissions_enum import Permissions
|
||||
|
||||
logger = APILogger(__name__)
|
||||
@ -37,25 +41,61 @@ class GroupMutation(MutationABC):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def resolve_create(obj: GroupCreateInput, *_):
|
||||
async def _handle_group_role_assignments(gid: int, roles: Optional[list[int]]):
|
||||
if roles is None:
|
||||
return
|
||||
|
||||
existing_roles = await groupDao.get_roles(gid)
|
||||
existing_role_ids = {role.id for role in existing_roles}
|
||||
|
||||
new_role_ids = set(roles)
|
||||
|
||||
roles_to_add = new_role_ids - existing_role_ids
|
||||
roles_to_remove = existing_role_ids - new_role_ids
|
||||
|
||||
if roles_to_add:
|
||||
group_role_assignments = [
|
||||
GroupRoleAssignment(0, gid, role_id) for role_id in roles_to_add
|
||||
]
|
||||
await groupRoleAssignmentDao.create_many(group_role_assignments)
|
||||
|
||||
if roles_to_remove:
|
||||
assignments_to_remove = await groupRoleAssignmentDao.find_by(
|
||||
[
|
||||
{GroupRoleAssignment.group_id: gid},
|
||||
{GroupRoleAssignment.role_id: {"in": roles_to_remove}},
|
||||
]
|
||||
)
|
||||
await groupRoleAssignmentDao.delete_many(assignments_to_remove)
|
||||
|
||||
@classmethod
|
||||
async def resolve_create(cls, obj: GroupCreateInput, *_):
|
||||
logger.debug(f"create group: {obj.__dict__}")
|
||||
|
||||
group = Group(
|
||||
0,
|
||||
obj.name,
|
||||
)
|
||||
nid = await groupDao.create(group)
|
||||
return await groupDao.get_by_id(nid)
|
||||
gid = await groupDao.create(group)
|
||||
|
||||
@staticmethod
|
||||
async def resolve_update(obj: GroupUpdateInput, *_):
|
||||
await cls._handle_group_role_assignments(gid, obj.roles)
|
||||
|
||||
return await groupDao.get_by_id(gid)
|
||||
|
||||
@classmethod
|
||||
async def resolve_update(cls, obj: GroupUpdateInput, *_):
|
||||
logger.debug(f"update group: {input}")
|
||||
|
||||
if await groupDao.find_by_id(obj.id) is None:
|
||||
raise ValueError(f"Group with id {obj.id} not found")
|
||||
|
||||
if obj.name is not None:
|
||||
group = await groupDao.get_by_id(obj.id)
|
||||
group.name = obj.name
|
||||
await groupDao.update(group)
|
||||
|
||||
await cls._handle_group_role_assignments(obj.id, obj.roles)
|
||||
|
||||
return await groupDao.get_by_id(obj.id)
|
||||
|
||||
@staticmethod
|
||||
|
@ -1,7 +1,11 @@
|
||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||
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.short_url import ShortUrl
|
||||
from data.schemas.public.short_url_dao import shortUrlDao
|
||||
from service.permission.permissions_enum import Permissions
|
||||
|
||||
|
||||
class GroupQuery(DbModelQueryABC):
|
||||
@ -9,8 +13,19 @@ class GroupQuery(DbModelQueryABC):
|
||||
DbModelQueryABC.__init__(self, "Group")
|
||||
|
||||
self.set_field("name", lambda x, *_: x.name)
|
||||
self.set_field("shortUrls", self._get_urls)
|
||||
self.field(
|
||||
ResolverFieldBuilder("shortUrls")
|
||||
.with_resolver(self._get_urls)
|
||||
.with_require_any([
|
||||
Permissions.groups,
|
||||
], [group_by_assignment_resolver])
|
||||
)
|
||||
self.set_field("roles", self._get_roles)
|
||||
|
||||
@staticmethod
|
||||
async def _get_urls(group: Group, *_):
|
||||
return await shortUrlDao.find_by({ShortUrl.group_id: group.id})
|
||||
|
||||
@staticmethod
|
||||
async def _get_roles(group: Group, *_):
|
||||
return await groupDao.get_roles(group.id)
|
||||
|
@ -11,6 +11,7 @@ from api_graphql.filter.permission_filter import PermissionFilter
|
||||
from api_graphql.filter.role_filter import RoleFilter
|
||||
from api_graphql.filter.short_url_filter import ShortUrlFilter
|
||||
from api_graphql.filter.user_filter import UserFilter
|
||||
from api_graphql.require_any_resolvers import group_by_assignment_resolver
|
||||
from data.schemas.administration.api_key import ApiKey
|
||||
from data.schemas.administration.api_key_dao import apiKeyDao
|
||||
from data.schemas.administration.user import User
|
||||
@ -51,7 +52,15 @@ class Query(QueryABC):
|
||||
.with_dao(roleDao)
|
||||
.with_filter(RoleFilter)
|
||||
.with_sort(Sort[Role])
|
||||
.with_require_any_permission([Permissions.roles])
|
||||
.with_require_any_permission(
|
||||
[
|
||||
Permissions.roles,
|
||||
Permissions.users_create,
|
||||
Permissions.users_update,
|
||||
Permissions.groups_create,
|
||||
Permissions.groups_update,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
self.field(
|
||||
@ -83,7 +92,6 @@ class Query(QueryABC):
|
||||
.with_require_any_permission([Permissions.users_create])
|
||||
)
|
||||
|
||||
|
||||
self.field(
|
||||
DaoFieldBuilder("domains")
|
||||
.with_dao(domainDao)
|
||||
@ -102,21 +110,21 @@ class Query(QueryABC):
|
||||
.with_dao(groupDao)
|
||||
.with_filter(GroupFilter)
|
||||
.with_sort(Sort[Group])
|
||||
.with_require_any_permission(
|
||||
.with_require_any(
|
||||
[
|
||||
Permissions.groups,
|
||||
Permissions.short_urls_create,
|
||||
Permissions.short_urls_update,
|
||||
]
|
||||
],
|
||||
[group_by_assignment_resolver]
|
||||
)
|
||||
)
|
||||
# partially public to load redirect if not resolved/redirected by api
|
||||
self.field(
|
||||
DaoFieldBuilder("shortUrls")
|
||||
.with_dao(shortUrlDao)
|
||||
.with_filter(ShortUrlFilter)
|
||||
.with_sort(Sort[ShortUrl])
|
||||
.with_require_any_permission([Permissions.short_urls])
|
||||
.with_require_any([Permissions.short_urls], [group_by_assignment_resolver])
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
22
api/src/api_graphql/require_any_resolvers.py
Normal file
22
api/src/api_graphql/require_any_resolvers.py
Normal file
@ -0,0 +1,22 @@
|
||||
from api_graphql.service.collection_result import CollectionResult
|
||||
from api_graphql.service.query_context import QueryContext
|
||||
from data.schemas.public.group_dao import groupDao
|
||||
from service.permission.permissions_enum import Permissions
|
||||
|
||||
|
||||
async def group_by_assignment_resolver(ctx: QueryContext) -> bool:
|
||||
if not isinstance(ctx.data, CollectionResult):
|
||||
return False
|
||||
|
||||
if ctx.has_permission(Permissions.short_urls_by_assignment):
|
||||
groups = [await x.group for x in ctx.data.nodes]
|
||||
role_ids = {x.id for x in await ctx.user.roles}
|
||||
filtered_groups = [
|
||||
g.id for g in groups if
|
||||
g is not None and (roles := await groupDao.get_roles(g.id)) and all(r.id in role_ids for r in roles)
|
||||
]
|
||||
|
||||
ctx.data.nodes = [node for node in ctx.data.nodes if (await node.group) is not None and (await node.group).id in filtered_groups]
|
||||
return True
|
||||
|
||||
return True
|
@ -14,7 +14,7 @@ class QueryContext:
|
||||
self,
|
||||
data: Any,
|
||||
user: Optional[User],
|
||||
user_permissions: Optional[list[Permission]],
|
||||
user_permissions: Optional[list[Permissions]],
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
@ -23,7 +23,7 @@ class QueryContext:
|
||||
self._user = user
|
||||
if user_permissions is None:
|
||||
user_permissions = []
|
||||
self._user_permissions: list[str] = [x.name for x in user_permissions]
|
||||
self._user_permissions: list[str] = [x.value for x in user_permissions]
|
||||
|
||||
self._resolve_info = None
|
||||
for arg in args:
|
||||
|
@ -1,11 +1,15 @@
|
||||
from collections.abc import Awaitable
|
||||
from typing import Callable, Union, Optional
|
||||
from typing import Callable, Union, Optional, Coroutine, Any
|
||||
|
||||
from api_graphql.service.query_context import QueryContext
|
||||
from service.permission.permissions_enum import Permissions
|
||||
|
||||
TRequireAnyPermissions = Optional[list[Permissions]]
|
||||
TRequireAnyResolvers = list[
|
||||
Union[Callable[[QueryContext], bool], Awaitable[[QueryContext], bool]]
|
||||
Union[
|
||||
Callable[[QueryContext], bool],
|
||||
Awaitable[[QueryContext], bool],
|
||||
Callable[[QueryContext], Coroutine[Any, Any, bool]]
|
||||
]
|
||||
]
|
||||
TRequireAny = tuple[TRequireAnyPermissions, TRequireAnyResolvers]
|
||||
|
@ -370,6 +370,9 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
if isinstance(value, NoneType):
|
||||
return "NULL"
|
||||
|
||||
if value is None:
|
||||
return "NULL"
|
||||
|
||||
if isinstance(value, Enum):
|
||||
return str(value.value)
|
||||
|
||||
@ -411,13 +414,13 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
) -> str:
|
||||
query = f"SELECT * FROM {self._table_name}"
|
||||
|
||||
if filters and len(filters) > 0:
|
||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
||||
query += f" WHERE {self._build_conditions(filters)}"
|
||||
if sorts and len(sorts) > 0:
|
||||
if sorts is not None and (not isinstance(sorts, list) or len(sorts) > 0):
|
||||
query += f" ORDER BY {self._build_order_by(sorts)}"
|
||||
if take:
|
||||
if take is not None:
|
||||
query += f" LIMIT {take}"
|
||||
if skip:
|
||||
if skip is not None:
|
||||
query += f" OFFSET {skip}"
|
||||
return query
|
||||
|
||||
@ -452,14 +455,21 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
)
|
||||
else:
|
||||
sub_conditions.append(
|
||||
f"{db_name} = {self._get_value_sql(value)}"
|
||||
self._get_value_validation_sql(db_name, value)
|
||||
)
|
||||
conditions.append(f"({' OR '.join(sub_conditions)})")
|
||||
else:
|
||||
conditions.append(f"{db_name} = {self._get_value_sql(values)}")
|
||||
conditions.append(self._get_value_validation_sql(db_name, values))
|
||||
|
||||
return " AND ".join(conditions)
|
||||
|
||||
def _get_value_validation_sql(self, field: str, value: Any):
|
||||
value = self._get_value_sql(value)
|
||||
|
||||
if value == "NULL":
|
||||
return f"{field} IS NULL"
|
||||
return f"{field} = {value}"
|
||||
|
||||
def _build_condition(self, db_name: str, operator: str, value: Any) -> str:
|
||||
"""
|
||||
Build individual SQL condition based on the operator
|
||||
|
@ -24,14 +24,21 @@ def get_value(
|
||||
|
||||
if key in source:
|
||||
value = source[key]
|
||||
if isinstance(value, cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__):
|
||||
if isinstance(
|
||||
value,
|
||||
cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__,
|
||||
):
|
||||
return value
|
||||
|
||||
try:
|
||||
if cast_type == bool:
|
||||
return value.lower() in ["true", "1"]
|
||||
|
||||
if (cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__) == list:
|
||||
if (
|
||||
cast_type
|
||||
if not hasattr(cast_type, "__origin__")
|
||||
else cast_type.__origin__
|
||||
) == list:
|
||||
subtype = (
|
||||
cast_type.__args__[0] if hasattr(cast_type, "__args__") else None
|
||||
)
|
||||
|
@ -42,17 +42,9 @@ class User(DbModelABC):
|
||||
|
||||
@async_property
|
||||
async def permissions(self):
|
||||
from data.schemas.permission.role_user_dao import roleUserDao
|
||||
from data.schemas.permission.role_permission_dao import rolePermissionDao
|
||||
from data.schemas.permission.permission_dao import permissionDao
|
||||
from data.schemas.administration.user_dao import userDao
|
||||
|
||||
x = [
|
||||
rp.permission_id
|
||||
for x in await roleUserDao.get_by_user_id(self.id)
|
||||
for rp in await rolePermissionDao.get_by_role_id(x.role_id)
|
||||
]
|
||||
|
||||
return await permissionDao.get_by({"id": {"in": x}})
|
||||
return await userDao.get_permissions(self.id)
|
||||
|
||||
async def has_permission(self, permission: Permissions) -> bool:
|
||||
from data.schemas.administration.user_dao import userDao
|
||||
|
@ -33,13 +33,30 @@ class UserDao(DbModelDaoABC[User]):
|
||||
SELECT COUNT(*)
|
||||
FROM permission.role_users ru
|
||||
JOIN permission.role_permissions rp ON ru.roleId = rp.roleId
|
||||
WHERE ru.userId = {user_id} AND rp.permissionId = {p.id};
|
||||
WHERE ru.userId = {user_id}
|
||||
AND rp.permissionId = {p.id}
|
||||
AND ru.deleted = FALSE
|
||||
AND rp.deleted = FALSE;
|
||||
"""
|
||||
)
|
||||
if result is None or len(result) == 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
return result[0]["count"] > 0
|
||||
|
||||
async def get_permissions(self, user_id: int) -> list[Permissions]:
|
||||
result = await self._db.select_map(
|
||||
f"""
|
||||
SELECT p.*
|
||||
FROM permission.permissions p
|
||||
JOIN permission.role_permissions rp ON p.id = rp.permissionId
|
||||
JOIN permission.role_users ru ON rp.roleId = ru.roleId
|
||||
WHERE ru.userId = {user_id}
|
||||
AND rp.deleted = FALSE
|
||||
AND ru.deleted = FALSE;
|
||||
"""
|
||||
)
|
||||
return [Permissions(p["name"]) for p in result]
|
||||
|
||||
|
||||
userDao = UserDao()
|
||||
|
@ -17,5 +17,19 @@ class GroupDao(DbModelDaoABC[Group]):
|
||||
)
|
||||
return self.to_object(result[0])
|
||||
|
||||
async def get_roles(self, group_id: int):
|
||||
result = await self._db.select_map(
|
||||
f"""
|
||||
SELECT r.*
|
||||
FROM permission.roles r
|
||||
JOIN public.group_role_assignments gra ON r.id = gra.roleId
|
||||
WHERE gra.groupId = {group_id}
|
||||
AND gra.deleted = FALSE
|
||||
"""
|
||||
)
|
||||
from data.schemas.permission.role_dao import roleDao
|
||||
|
||||
return [roleDao.to_object(x) for x in result]
|
||||
|
||||
|
||||
groupDao = GroupDao()
|
||||
|
43
api/src/data/schemas/public/group_role_assignment.py
Normal file
43
api/src/data/schemas/public/group_role_assignment.py
Normal file
@ -0,0 +1,43 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from async_property import async_property
|
||||
|
||||
from core.database.abc.db_model_abc import DbModelABC
|
||||
from core.typing import SerialId
|
||||
|
||||
|
||||
class GroupRoleAssignment(DbModelABC):
|
||||
def __init__(
|
||||
self,
|
||||
id: SerialId,
|
||||
group_id: SerialId,
|
||||
role_id: SerialId,
|
||||
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)
|
||||
self._group_id = group_id
|
||||
self._role_id = role_id
|
||||
|
||||
@property
|
||||
def group_id(self) -> SerialId:
|
||||
return self._group_id
|
||||
|
||||
@async_property
|
||||
async def group(self):
|
||||
from data.schemas.public.group_dao import groupDao
|
||||
|
||||
return await groupDao.get_by_id(self._group_id)
|
||||
|
||||
@property
|
||||
def role_id(self) -> SerialId:
|
||||
return self._role_id
|
||||
|
||||
@async_property
|
||||
async def role(self):
|
||||
from data.schemas.permission.role_dao import roleDao
|
||||
|
||||
return await roleDao.get_by_id(self._role_id)
|
37
api/src/data/schemas/public/group_role_assignment_dao.py
Normal file
37
api/src/data/schemas/public/group_role_assignment_dao.py
Normal file
@ -0,0 +1,37 @@
|
||||
from core.logger import DBLogger
|
||||
from data.schemas.public.group_role_assignment import GroupRoleAssignment
|
||||
|
||||
logger = DBLogger(__name__)
|
||||
|
||||
from core.database.abc.db_model_dao_abc import DbModelDaoABC
|
||||
|
||||
|
||||
class GroupRoleAssignmentDao(DbModelDaoABC[GroupRoleAssignment]):
|
||||
def __init__(self):
|
||||
DbModelDaoABC.__init__(
|
||||
self, __name__, GroupRoleAssignment, "public.group_role_assignments"
|
||||
)
|
||||
|
||||
self.attribute(GroupRoleAssignment.group_id, int)
|
||||
self.attribute(GroupRoleAssignment.role_id, int)
|
||||
|
||||
async def get_by_group_id(
|
||||
self, gid: int, with_deleted=False
|
||||
) -> list[GroupRoleAssignment]:
|
||||
f = [{GroupRoleAssignment.group_id: gid}]
|
||||
if not with_deleted:
|
||||
f.append({GroupRoleAssignment.deleted: False})
|
||||
|
||||
return await self.find_by(f)
|
||||
|
||||
async def get_by_role_id(
|
||||
self, rid: int, with_deleted=False
|
||||
) -> list[GroupRoleAssignment]:
|
||||
f = [{GroupRoleAssignment.role_id: rid}]
|
||||
if not with_deleted:
|
||||
f.append({GroupRoleAssignment.deleted: False})
|
||||
|
||||
return await self.find_by(f)
|
||||
|
||||
|
||||
groupRoleAssignmentDao = GroupRoleAssignmentDao()
|
@ -0,0 +1,27 @@
|
||||
CREATE
|
||||
SCHEMA IF NOT EXISTS public;
|
||||
|
||||
-- groups
|
||||
CREATE TABLE IF NOT EXISTS public.group_role_assignments
|
||||
(
|
||||
Id SERIAL PRIMARY KEY,
|
||||
GroupId INT NOT NULL REFERENCES public.groups (Id),
|
||||
RoleId INT NOT NULL REFERENCES permission.roles (Id),
|
||||
-- for history
|
||||
Deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
EditorId INT NULL REFERENCES administration.users (Id),
|
||||
CreatedUtc timestamptz NOT NULL DEFAULT NOW(),
|
||||
UpdatedUtc timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.group_role_assignments_history
|
||||
(
|
||||
LIKE public.group_role_assignments
|
||||
);
|
||||
|
||||
CREATE TRIGGER group_role_assignment_history_trigger
|
||||
BEFORE INSERT OR UPDATE OR DELETE
|
||||
ON public.group_role_assignments
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.history_trigger_function();
|
||||
|
@ -25,7 +25,8 @@ class PermissionSeeder(DataSeederABC):
|
||||
possible_permissions = [permission.value for permission in Permissions]
|
||||
|
||||
if len(permissions) == len(possible_permissions):
|
||||
logger.info("Permissions already completed")
|
||||
logger.info("Permissions already existing")
|
||||
await self._update_missing_descriptions()
|
||||
return
|
||||
|
||||
logger.warning("Permissions incomplete")
|
||||
@ -41,6 +42,7 @@ class PermissionSeeder(DataSeederABC):
|
||||
if permission not in permission_names
|
||||
]
|
||||
)
|
||||
await self._update_missing_descriptions()
|
||||
|
||||
await self._add_missing_to_role()
|
||||
await self._add_missing_to_api_key()
|
||||
@ -78,3 +80,28 @@ class PermissionSeeder(DataSeederABC):
|
||||
if permission.id not in [x.permission_id for x in admin_permissions]
|
||||
]
|
||||
await apiKeyPermissionDao.create_many(to_assign)
|
||||
|
||||
@staticmethod
|
||||
async def _update_missing_descriptions():
|
||||
permissions = {
|
||||
permission.name: permission
|
||||
for permission in await permissionDao.find_by(
|
||||
[{Permission.description: None}]
|
||||
)
|
||||
}
|
||||
to_update = []
|
||||
|
||||
if len(permissions) == 0:
|
||||
return
|
||||
|
||||
for key in PERMISSION_DESCRIPTIONS:
|
||||
if key.value not in permissions:
|
||||
continue
|
||||
|
||||
permissions[key.value].description = PERMISSION_DESCRIPTIONS[key]
|
||||
to_update.append(permissions[key.value])
|
||||
|
||||
if len(to_update) == 0:
|
||||
return
|
||||
|
||||
await permissionDao.update_many(to_update)
|
||||
|
@ -24,10 +24,12 @@ class Redirector(Flask):
|
||||
|
||||
app = Redirector(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("404.html"), 404
|
||||
|
||||
|
||||
@app.route("/<path:path>")
|
||||
async def _handle_request(path: str):
|
||||
short_url = await _find_short_url_by_url(path)
|
||||
@ -36,14 +38,19 @@ async def _handle_request(path: str):
|
||||
|
||||
domains = Environment.get("DOMAINS", list[str], [])
|
||||
domain = await short_url.domain
|
||||
logger.debug(f"Domain: {domain.name if domain is not None else None}, request.host: {request.host}")
|
||||
logger.debug(
|
||||
f"Domain: {domain.name if domain is not None else None}, request.host: {request.host}"
|
||||
)
|
||||
|
||||
host = request.host
|
||||
if ":" in host:
|
||||
host = host.split(":")[0]
|
||||
|
||||
domain_strict_mode = Environment.get("DOMAIN_STRICT_MODE", bool, False)
|
||||
if domain is not None and (domain.name not in domains or (domain_strict_mode and not host.endswith(domain.name))):
|
||||
if domain is not None and (
|
||||
domain.name not in domains
|
||||
or (domain_strict_mode and not host.endswith(domain.name))
|
||||
):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
user_agent = request.headers.get("User-Agent", "").lower()
|
||||
@ -71,6 +78,7 @@ async def _handle_short_url(path: str, short_url: ShortUrl):
|
||||
|
||||
return _do_redirect(short_url.target_url)
|
||||
|
||||
|
||||
async def _track_visit(short_url: ShortUrl):
|
||||
try:
|
||||
await shortUrlVisitDao.create(
|
||||
@ -79,6 +87,7 @@ async def _track_visit(short_url: ShortUrl):
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update short url {short_url.short_url} with error", e)
|
||||
|
||||
|
||||
async def _find_short_url_by_url(url: str) -> ShortUrl:
|
||||
return await shortUrlDao.find_single_by({ShortUrl.short_url: url})
|
||||
|
||||
|
@ -45,6 +45,7 @@ class Permissions(Enum):
|
||||
|
||||
# short_urls
|
||||
short_urls = "short_urls"
|
||||
short_urls_by_assignment = "short_urls.by_assignment"
|
||||
short_urls_create = "short_urls.create"
|
||||
short_urls_update = "short_urls.update"
|
||||
short_urls_delete = "short_urls.delete"
|
||||
@ -52,4 +53,6 @@ class Permissions(Enum):
|
||||
|
||||
PERMISSION_DESCRIPTIONS = {
|
||||
Permissions.users_update: "Edit users, including changing their roles",
|
||||
Permissions.short_urls: "See all URLs",
|
||||
Permissions.short_urls_by_assignment: "See all short urls assigned to a group by role",
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
1.2.0
|
||||
1.2.1
|
@ -29,6 +29,7 @@ export enum PermissionsEnum {
|
||||
groupsDelete = 'groups.delete',
|
||||
|
||||
shortUrls = 'short_urls',
|
||||
shortUrlsByAssignment = 'short_urls.by_assignment',
|
||||
shortUrlsCreate = 'short_urls.create',
|
||||
shortUrlsUpdate = 'short_urls.update',
|
||||
shortUrlsDelete = 'short_urls.delete',
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { DbModel } from 'src/app/model/entities/db-model';
|
||||
import { Role } from 'src/app/model/entities/role';
|
||||
|
||||
export interface Group extends DbModel {
|
||||
name: string;
|
||||
roles: Role[];
|
||||
}
|
||||
|
||||
export interface GroupCreateInput {
|
||||
name: string;
|
||||
roles: Role[];
|
||||
}
|
||||
|
||||
export interface GroupUpdateInput {
|
||||
id: number;
|
||||
name: string;
|
||||
roles: Role[];
|
||||
}
|
||||
|
@ -31,7 +31,12 @@ const routes: Routes = [
|
||||
m => m.ShortUrlsModule
|
||||
),
|
||||
canActivate: [PermissionGuard],
|
||||
data: { permissions: [PermissionsEnum.shortUrls] },
|
||||
data: {
|
||||
permissions: [
|
||||
PermissionsEnum.shortUrls,
|
||||
PermissionsEnum.shortUrlsByAssignment,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'administration',
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||
<div class="flex flex-col gap-5">
|
||||
<div *ngFor="let group of Object.keys(permissionGroups)">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex justify-between">
|
||||
<label class="flex-1" for="roles.permission_groups.{{ group }}">
|
||||
<h3>
|
||||
@ -52,14 +52,20 @@
|
||||
|
||||
<div
|
||||
*ngFor="let permission of permissionGroups[group]"
|
||||
class="flex items-center justify-between w-full">
|
||||
class="flex flex-col">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<label class="flex-1" for="{{ permission.name }}">
|
||||
{{ 'permissions.' + permission.name | translate }}
|
||||
</label>
|
||||
<p-inputSwitch [formControlName]="permission.name">
|
||||
>
|
||||
<p-inputSwitch class="flex items-center justify-center" [formControlName]="permission.name">
|
||||
</p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="permission.description">
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ permission.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,21 +1,22 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
Permission,
|
||||
Role,
|
||||
RoleCreateInput,
|
||||
RoleUpdateInput,
|
||||
} from "src/app/model/entities/role";
|
||||
import { InputSwitchChangeEvent } from "primeng/inputswitch";
|
||||
import { ToastService } from "src/app/service/toast.service";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { FormPageBase } from "src/app/core/base/form-page-base";
|
||||
import { RolesDataService } from "src/app/modules/admin/administration/roles/roles.data.service";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
} from 'src/app/model/entities/role';
|
||||
import { InputSwitchChangeEvent } from 'primeng/inputswitch';
|
||||
import { ToastService } from 'src/app/service/toast.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { FormPageBase } from 'src/app/core/base/form-page-base';
|
||||
import { RolesDataService } from 'src/app/modules/admin/administration/roles/roles.data.service';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: "app-role-form-page",
|
||||
templateUrl: "./role-form-page.component.html",
|
||||
styleUrl: "./role-form-page.component.scss",
|
||||
selector: 'app-role-form-page',
|
||||
templateUrl: './role-form-page.component.html',
|
||||
styleUrl: './role-form-page.component.scss',
|
||||
})
|
||||
export class RoleFormPageComponent extends FormPageBase<
|
||||
Role,
|
||||
@ -26,7 +27,10 @@ export class RoleFormPageComponent extends FormPageBase<
|
||||
permissionGroups: { [key: string]: Permission[] } = {};
|
||||
allPermissions: Permission[] = [];
|
||||
|
||||
constructor(private toast: ToastService) {
|
||||
constructor(
|
||||
private toast: ToastService,
|
||||
private translate: TranslateService
|
||||
) {
|
||||
super();
|
||||
this.initializePermissions().then(() => {
|
||||
if (!this.nodeId) {
|
||||
@ -36,7 +40,7 @@ export class RoleFormPageComponent extends FormPageBase<
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataService.loadById(this.nodeId).subscribe((role) => {
|
||||
this.dataService.loadById(this.nodeId).subscribe(role => {
|
||||
this.node = role;
|
||||
this.setForm(this.node);
|
||||
});
|
||||
@ -45,12 +49,17 @@ export class RoleFormPageComponent extends FormPageBase<
|
||||
|
||||
async initializePermissions() {
|
||||
const permissions = await firstValueFrom(
|
||||
this.dataService.getAllPermissions(),
|
||||
this.dataService.getAllPermissions()
|
||||
);
|
||||
this.allPermissions = permissions;
|
||||
this.allPermissions = permissions.map(x => {
|
||||
const key = `permission_descriptions.${x.name}`;
|
||||
const description = this.translate.instant(key);
|
||||
x.description = description === key ? undefined : description;
|
||||
return x;
|
||||
});
|
||||
this.permissionGroups = permissions.reduce(
|
||||
(acc, p) => {
|
||||
const group = p.name.includes(".") ? p.name.split(".")[0] : p.name;
|
||||
const group = p.name.includes('.') ? p.name.split('.')[0] : p.name;
|
||||
|
||||
if (!acc[group]) {
|
||||
acc[group] = [];
|
||||
@ -58,10 +67,10 @@ export class RoleFormPageComponent extends FormPageBase<
|
||||
acc[group].push(p);
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: Permission[] },
|
||||
{} as { [key: string]: Permission[] }
|
||||
);
|
||||
|
||||
permissions.forEach((p) => {
|
||||
permissions.forEach(p => {
|
||||
this.form.addControl(p.name, new FormControl<boolean>(false));
|
||||
});
|
||||
}
|
||||
@ -76,48 +85,48 @@ export class RoleFormPageComponent extends FormPageBase<
|
||||
name: new FormControl<string | undefined>(undefined, Validators.required),
|
||||
description: new FormControl<string | undefined>(undefined),
|
||||
});
|
||||
this.form.controls["id"].disable();
|
||||
this.form.controls['id'].disable();
|
||||
}
|
||||
|
||||
setForm(node?: Role) {
|
||||
this.form.controls["id"].setValue(node?.id);
|
||||
this.form.controls["name"].setValue(node?.name);
|
||||
this.form.controls["description"].setValue(node?.description);
|
||||
this.form.controls['id'].setValue(node?.id);
|
||||
this.form.controls['name'].setValue(node?.name);
|
||||
this.form.controls['description'].setValue(node?.description);
|
||||
|
||||
if (!node) return;
|
||||
|
||||
const permissions = node.permissions ?? [];
|
||||
|
||||
permissions.forEach((p) => {
|
||||
permissions.forEach(p => {
|
||||
this.form.controls[p.name].setValue(true);
|
||||
});
|
||||
}
|
||||
|
||||
getCreateInput(): RoleCreateInput {
|
||||
return {
|
||||
name: this.form.controls["name"].pristine
|
||||
name: this.form.controls['name'].pristine
|
||||
? undefined
|
||||
: (this.form.controls["name"].value ?? undefined),
|
||||
description: this.form.controls["description"].pristine
|
||||
: (this.form.controls['name'].value ?? undefined),
|
||||
description: this.form.controls['description'].pristine
|
||||
? undefined
|
||||
: (this.form.controls["description"].value ?? undefined),
|
||||
: (this.form.controls['description'].value ?? undefined),
|
||||
permissions: this.allPermissions.filter(
|
||||
(p) => this.form.controls[p.name].value,
|
||||
p => this.form.controls[p.name].value
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
getUpdateInput(): RoleUpdateInput {
|
||||
return {
|
||||
id: this.form.controls["id"].value,
|
||||
name: this.form.controls["name"].pristine
|
||||
id: this.form.controls['id'].value,
|
||||
name: this.form.controls['name'].pristine
|
||||
? undefined
|
||||
: this.form.controls["name"].value,
|
||||
description: this.form.controls["description"].pristine
|
||||
: this.form.controls['name'].value,
|
||||
description: this.form.controls['description'].pristine
|
||||
? undefined
|
||||
: this.form.controls["description"].value,
|
||||
: this.form.controls['description'].value,
|
||||
permissions: this.allPermissions.filter(
|
||||
(p) => this.form.controls[p.name].value,
|
||||
p => this.form.controls[p.name].value
|
||||
),
|
||||
};
|
||||
}
|
||||
@ -125,7 +134,7 @@ export class RoleFormPageComponent extends FormPageBase<
|
||||
create(role: RoleCreateInput): void {
|
||||
this.dataService.create(role).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.toast.success('action.created');
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
@ -133,20 +142,20 @@ export class RoleFormPageComponent extends FormPageBase<
|
||||
update(role: RoleUpdateInput): void {
|
||||
this.dataService.update(role).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.toast.success('action.created');
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
toggleGroup(event: InputSwitchChangeEvent, group: string) {
|
||||
this.permissionGroups[group].forEach((p) => {
|
||||
this.permissionGroups[group].forEach(p => {
|
||||
this.form.controls[p.name].setValue(event.checked);
|
||||
});
|
||||
}
|
||||
|
||||
isGroupChecked(group: string) {
|
||||
return this.permissionGroups[group].every(
|
||||
(p) => this.form.controls[p.name].value,
|
||||
p => this.form.controls[p.name].value
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,25 @@
|
||||
import { Injectable, Provider } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { Injectable, Provider } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
Create,
|
||||
Delete,
|
||||
PageDataService,
|
||||
Restore,
|
||||
Update,
|
||||
} from "src/app/core/base/page.data.service";
|
||||
} from 'src/app/core/base/page.data.service';
|
||||
import {
|
||||
Permission,
|
||||
Role,
|
||||
RoleCreateInput,
|
||||
RoleUpdateInput,
|
||||
} from "src/app/model/entities/role";
|
||||
import { Filter } from "src/app/model/graphql/filter/filter.model";
|
||||
import { Sort } from "src/app/model/graphql/filter/sort.model";
|
||||
import { Apollo, gql } from "apollo-angular";
|
||||
import { QueryResult } from "src/app/model/entities/query-result";
|
||||
import { DB_MODEL_FRAGMENT } from "src/app/model/graphql/db-model.query";
|
||||
import { catchError, map } from "rxjs/operators";
|
||||
import { SpinnerService } from "src/app/service/spinner.service";
|
||||
} from 'src/app/model/entities/role';
|
||||
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
||||
import { Sort } from 'src/app/model/graphql/filter/sort.model';
|
||||
import { Apollo, gql } from 'apollo-angular';
|
||||
import { QueryResult } from 'src/app/model/entities/query-result';
|
||||
import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
||||
|
||||
@Injectable()
|
||||
export class RolesDataService
|
||||
@ -32,7 +32,7 @@ export class RolesDataService
|
||||
{
|
||||
constructor(
|
||||
private spinner: SpinnerService,
|
||||
private apollo: Apollo,
|
||||
private apollo: Apollo
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@ -41,7 +41,7 @@ export class RolesDataService
|
||||
filter?: Filter[] | undefined,
|
||||
sort?: Sort[] | undefined,
|
||||
skip?: number | undefined,
|
||||
take?: number | undefined,
|
||||
take?: number | undefined
|
||||
): Observable<QueryResult<Role>> {
|
||||
return this.apollo
|
||||
.query<{ roles: QueryResult<Role> }>({
|
||||
@ -75,12 +75,12 @@ export class RolesDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.roles));
|
||||
.pipe(map(result => result.data.roles));
|
||||
}
|
||||
|
||||
loadById(id: number): Observable<Role> {
|
||||
@ -112,12 +112,12 @@ export class RolesDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.roles.nodes[0]));
|
||||
.pipe(map(result => result.data.roles.nodes[0]));
|
||||
}
|
||||
|
||||
create(object: RoleCreateInput): Observable<Role | undefined> {
|
||||
@ -145,17 +145,17 @@ export class RolesDataService
|
||||
input: {
|
||||
name: object.name,
|
||||
description: object.description,
|
||||
permissions: object.permissions?.map((x) => x.id),
|
||||
permissions: object.permissions?.map(x => x.id),
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.role.create));
|
||||
.pipe(map(result => result.data?.role.create));
|
||||
}
|
||||
|
||||
update(object: RoleUpdateInput): Observable<Role | undefined> {
|
||||
@ -184,17 +184,17 @@ export class RolesDataService
|
||||
id: object.id,
|
||||
name: object.name,
|
||||
description: object.description,
|
||||
permissions: object.permissions?.map((x) => x.id),
|
||||
permissions: object.permissions?.map(x => x.id),
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.role.update));
|
||||
.pipe(map(result => result.data?.role.update));
|
||||
}
|
||||
|
||||
delete(object: Role): Observable<boolean> {
|
||||
@ -212,12 +212,12 @@ export class RolesDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.role.delete ?? false));
|
||||
.pipe(map(result => result.data?.role.delete ?? false));
|
||||
}
|
||||
|
||||
restore(object: Role): Observable<boolean> {
|
||||
@ -235,12 +235,12 @@ export class RolesDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.role.restore ?? false));
|
||||
.pipe(map(result => result.data?.role.restore ?? false));
|
||||
}
|
||||
|
||||
getAllPermissions(): Observable<Permission[]> {
|
||||
@ -252,18 +252,19 @@ export class RolesDataService
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.permissions.nodes));
|
||||
.pipe(map(result => result.data.permissions.nodes));
|
||||
}
|
||||
|
||||
static provide(): Provider[] {
|
||||
|
@ -1,20 +1,21 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
import { ToastService } from "src/app/service/toast.service";
|
||||
import { FormPageBase } from "src/app/core/base/form-page-base";
|
||||
import { Component } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { ToastService } from 'src/app/service/toast.service';
|
||||
import { FormPageBase } from 'src/app/core/base/form-page-base';
|
||||
import {
|
||||
NotExistingUser,
|
||||
User,
|
||||
UserCreateInput,
|
||||
UserUpdateInput,
|
||||
} from "src/app/model/auth/user";
|
||||
import { Role } from "src/app/model/entities/role";
|
||||
import { UsersDataService } from "src/app/modules/admin/administration/users/users.data.service";
|
||||
} from 'src/app/model/auth/user';
|
||||
import { Role } from 'src/app/model/entities/role';
|
||||
import { UsersDataService } from 'src/app/modules/admin/administration/users/users.data.service';
|
||||
import { CommonDataService } from 'src/app/modules/shared/service/common-data.service';
|
||||
|
||||
@Component({
|
||||
selector: "app-user-form-page",
|
||||
templateUrl: "./user-form-page.component.html",
|
||||
styleUrl: "./user-form-page.component.scss",
|
||||
selector: 'app-user-form-page',
|
||||
templateUrl: './user-form-page.component.html',
|
||||
styleUrl: './user-form-page.component.scss',
|
||||
})
|
||||
export class UserFormPageComponent extends FormPageBase<
|
||||
User,
|
||||
@ -25,14 +26,17 @@ export class UserFormPageComponent extends FormPageBase<
|
||||
notExistingUsers: NotExistingUser[] = [];
|
||||
roles: Role[] = [];
|
||||
|
||||
constructor(private toast: ToastService) {
|
||||
constructor(
|
||||
private toast: ToastService,
|
||||
private cds: CommonDataService
|
||||
) {
|
||||
super();
|
||||
this.dataService.getAllRoles().subscribe((roles) => {
|
||||
this.cds.getAllRoles().subscribe(roles => {
|
||||
this.roles = roles;
|
||||
});
|
||||
|
||||
if (!this.nodeId) {
|
||||
this.dataService.getNotExistingUsersFromKeycloak().subscribe((users) => {
|
||||
this.dataService.getNotExistingUsersFromKeycloak().subscribe(users => {
|
||||
this.notExistingUsers = users;
|
||||
this.node = this.new();
|
||||
this.setForm(this.node);
|
||||
@ -41,7 +45,7 @@ export class UserFormPageComponent extends FormPageBase<
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataService.loadById(this.nodeId).subscribe((user) => {
|
||||
this.dataService.loadById(this.nodeId).subscribe(user => {
|
||||
this.node = user;
|
||||
this.setForm(this.node);
|
||||
});
|
||||
@ -59,47 +63,47 @@ export class UserFormPageComponent extends FormPageBase<
|
||||
email: new FormControl<string | undefined>(undefined),
|
||||
roles: new FormControl<Role[]>([]),
|
||||
});
|
||||
this.form.controls["id"].disable();
|
||||
this.form.controls["username"].disable();
|
||||
this.form.controls["email"].disable();
|
||||
this.form.controls['id'].disable();
|
||||
this.form.controls['username'].disable();
|
||||
this.form.controls['email'].disable();
|
||||
}
|
||||
|
||||
setForm(node?: User) {
|
||||
this.form.controls["id"].setValue(node?.id);
|
||||
this.form.controls["username"].setValue(node?.username);
|
||||
this.form.controls["email"].setValue(node?.email);
|
||||
this.form.controls["roles"].setValue(node?.roles ?? []);
|
||||
this.form.controls['id'].setValue(node?.id);
|
||||
this.form.controls['username'].setValue(node?.username);
|
||||
this.form.controls['email'].setValue(node?.email);
|
||||
this.form.controls['roles'].setValue(node?.roles ?? []);
|
||||
|
||||
if (this.notExistingUsers.length > 0) {
|
||||
this.form.controls["id"].enable();
|
||||
this.form.controls["keycloakId"].reset(undefined, { required: true });
|
||||
this.form.controls['id'].enable();
|
||||
this.form.controls['keycloakId'].reset(undefined, { required: true });
|
||||
}
|
||||
}
|
||||
|
||||
getCreateInput(): UserCreateInput {
|
||||
return {
|
||||
keycloakId: this.form.controls["keycloakId"].pristine
|
||||
keycloakId: this.form.controls['keycloakId'].pristine
|
||||
? undefined
|
||||
: this.form.controls["keycloakId"].value,
|
||||
roles: this.form.controls["roles"].pristine
|
||||
: this.form.controls['keycloakId'].value,
|
||||
roles: this.form.controls['roles'].pristine
|
||||
? undefined
|
||||
: this.form.controls["roles"].value,
|
||||
: this.form.controls['roles'].value,
|
||||
};
|
||||
}
|
||||
|
||||
getUpdateInput(): UserUpdateInput {
|
||||
return {
|
||||
id: this.form.controls["id"].value,
|
||||
roles: this.form.controls["roles"].pristine
|
||||
id: this.form.controls['id'].value,
|
||||
roles: this.form.controls['roles'].pristine
|
||||
? undefined
|
||||
: this.form.controls["roles"].value,
|
||||
: this.form.controls['roles'].value,
|
||||
};
|
||||
}
|
||||
|
||||
create(user: UserCreateInput): void {
|
||||
this.dataService.create(user).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.toast.success('action.created');
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
@ -107,7 +111,7 @@ export class UserFormPageComponent extends FormPageBase<
|
||||
update(user: UserUpdateInput): void {
|
||||
this.dataService.update(user).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.toast.success('action.created');
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
import { Injectable, Provider } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { Injectable, Provider } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
Create,
|
||||
Delete,
|
||||
PageDataService,
|
||||
Restore,
|
||||
Update,
|
||||
} from "src/app/core/base/page.data.service";
|
||||
import { Filter } from "src/app/model/graphql/filter/filter.model";
|
||||
import { Sort } from "src/app/model/graphql/filter/sort.model";
|
||||
import { Apollo, gql } from "apollo-angular";
|
||||
import { QueryResult } from "src/app/model/entities/query-result";
|
||||
import { DB_MODEL_FRAGMENT } from "src/app/model/graphql/db-model.query";
|
||||
import { catchError, map } from "rxjs/operators";
|
||||
import { SpinnerService } from "src/app/service/spinner.service";
|
||||
} from 'src/app/core/base/page.data.service';
|
||||
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
||||
import { Sort } from 'src/app/model/graphql/filter/sort.model';
|
||||
import { Apollo, gql } from 'apollo-angular';
|
||||
import { QueryResult } from 'src/app/model/entities/query-result';
|
||||
import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
||||
import {
|
||||
NotExistingUser,
|
||||
User,
|
||||
UserCreateInput,
|
||||
UserUpdateInput,
|
||||
} from "src/app/model/auth/user";
|
||||
import { Role } from "src/app/model/entities/role";
|
||||
} from 'src/app/model/auth/user';
|
||||
import { Role } from 'src/app/model/entities/role';
|
||||
|
||||
@Injectable()
|
||||
export class UsersDataService
|
||||
@ -33,7 +33,7 @@ export class UsersDataService
|
||||
{
|
||||
constructor(
|
||||
private spinner: SpinnerService,
|
||||
private apollo: Apollo,
|
||||
private apollo: Apollo
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@ -42,7 +42,7 @@ export class UsersDataService
|
||||
filter?: Filter[] | undefined,
|
||||
sort?: Sort[] | undefined,
|
||||
skip?: number | undefined,
|
||||
take?: number | undefined,
|
||||
take?: number | undefined
|
||||
): Observable<QueryResult<User>> {
|
||||
return this.apollo
|
||||
.query<{ users: QueryResult<User> }>({
|
||||
@ -77,12 +77,12 @@ export class UsersDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.users));
|
||||
.pipe(map(result => result.data.users));
|
||||
}
|
||||
|
||||
loadById(id: number): Observable<User> {
|
||||
@ -115,12 +115,12 @@ export class UsersDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.users.nodes[0]));
|
||||
.pipe(map(result => result.data.users.nodes[0]));
|
||||
}
|
||||
|
||||
create(object: UserCreateInput): Observable<User | undefined> {
|
||||
@ -145,17 +145,17 @@ export class UsersDataService
|
||||
variables: {
|
||||
input: {
|
||||
keycloakId: object.keycloakId,
|
||||
roles: object.roles?.map((x) => x.id),
|
||||
roles: object.roles?.map(x => x.id),
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.user.create));
|
||||
.pipe(map(result => result.data?.user.create));
|
||||
}
|
||||
|
||||
update(object: UserUpdateInput): Observable<User | undefined> {
|
||||
@ -180,17 +180,17 @@ export class UsersDataService
|
||||
variables: {
|
||||
input: {
|
||||
id: object.id,
|
||||
roles: object.roles?.map((x) => x.id),
|
||||
roles: object.roles?.map(x => x.id),
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.user.update));
|
||||
.pipe(map(result => result.data?.user.update));
|
||||
}
|
||||
|
||||
delete(object: User): Observable<boolean> {
|
||||
@ -208,12 +208,12 @@ export class UsersDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.user.delete ?? false));
|
||||
.pipe(map(result => result.data?.user.delete ?? false));
|
||||
}
|
||||
|
||||
restore(object: User): Observable<boolean> {
|
||||
@ -231,35 +231,12 @@ export class UsersDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data?.user.restore ?? false));
|
||||
}
|
||||
|
||||
getAllRoles(): Observable<Role[]> {
|
||||
return this.apollo
|
||||
.query<{ roles: QueryResult<Role> }>({
|
||||
query: gql`
|
||||
query getRoles {
|
||||
roles {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data.roles.nodes));
|
||||
.pipe(map(result => result.data?.user.restore ?? false));
|
||||
}
|
||||
|
||||
getNotExistingUsersFromKeycloak(): Observable<NotExistingUser[]> {
|
||||
@ -277,12 +254,12 @@ export class UsersDataService
|
||||
`,
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.notExistingUsersFromKeycloak.nodes));
|
||||
.pipe(map(result => result.data.notExistingUsersFromKeycloak.nodes));
|
||||
}
|
||||
|
||||
static provide(): Provider[] {
|
||||
|
@ -3,9 +3,9 @@ import { PageBase } from 'src/app/core/base/page-base';
|
||||
import { ToastService } from 'src/app/service/toast.service';
|
||||
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
|
||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||
import { Group } from 'src/app/model/entities/group';
|
||||
import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service';
|
||||
import { DomainsColumns } from 'src/app/modules/admin/domains/domains.columns';
|
||||
import { Domain } from 'src/app/model/entities/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'app-domains',
|
||||
@ -13,7 +13,7 @@ import { DomainsColumns } from 'src/app/modules/admin/domains/domains.columns';
|
||||
styleUrl: './domains.page.scss',
|
||||
})
|
||||
export class DomainsPage extends PageBase<
|
||||
Group,
|
||||
Domain,
|
||||
DomainsDataService,
|
||||
DomainsColumns
|
||||
> {
|
||||
@ -40,33 +40,33 @@ export class DomainsPage extends PageBase<
|
||||
});
|
||||
}
|
||||
|
||||
delete(group: Group): void {
|
||||
delete(domain: Domain): void {
|
||||
this.confirmation.confirmDialog({
|
||||
header: 'dialog.delete.header',
|
||||
message: 'dialog.delete.message',
|
||||
accept: () => {
|
||||
this.loading = true;
|
||||
this.dataService.delete(group).subscribe(() => {
|
||||
this.dataService.delete(domain).subscribe(() => {
|
||||
this.toast.success('action.deleted');
|
||||
this.load();
|
||||
});
|
||||
},
|
||||
messageParams: { entity: group.name },
|
||||
messageParams: { entity: domain.name },
|
||||
});
|
||||
}
|
||||
|
||||
restore(group: Group): void {
|
||||
restore(domain: Domain): void {
|
||||
this.confirmation.confirmDialog({
|
||||
header: 'dialog.restore.header',
|
||||
message: 'dialog.restore.message',
|
||||
accept: () => {
|
||||
this.loading = true;
|
||||
this.dataService.restore(group).subscribe(() => {
|
||||
this.dataService.restore(domain).subscribe(() => {
|
||||
this.toast.success('action.restored');
|
||||
this.load();
|
||||
});
|
||||
},
|
||||
messageParams: { entity: group.name },
|
||||
messageParams: { entity: domain.name },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -27,5 +27,13 @@
|
||||
type="text"
|
||||
formControlName="name"/>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<p-multiSelect
|
||||
[options]="roles"
|
||||
formControlName="roles"
|
||||
optionLabel="name"
|
||||
placeholder="{{ 'user.assign_roles' | translate }}"
|
||||
display="chip"
|
||||
[showClear]="true" />
|
||||
</ng-template>
|
||||
</app-form-page>
|
||||
|
@ -1,18 +1,20 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { ToastService } from "src/app/service/toast.service";
|
||||
import { FormPageBase } from "src/app/core/base/form-page-base";
|
||||
import { Component } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { ToastService } from 'src/app/service/toast.service';
|
||||
import { FormPageBase } from 'src/app/core/base/form-page-base';
|
||||
import {
|
||||
Group,
|
||||
GroupCreateInput,
|
||||
GroupUpdateInput,
|
||||
} from "src/app/model/entities/group";
|
||||
import { GroupsDataService } from "src/app/modules/admin/groups/groups.data.service";
|
||||
} from 'src/app/model/entities/group';
|
||||
import { GroupsDataService } from 'src/app/modules/admin/groups/groups.data.service';
|
||||
import { Role } from 'src/app/model/entities/role';
|
||||
import { CommonDataService } from 'src/app/modules/shared/service/common-data.service';
|
||||
|
||||
@Component({
|
||||
selector: "app-group-form-page",
|
||||
templateUrl: "./group-form-page.component.html",
|
||||
styleUrl: "./group-form-page.component.scss",
|
||||
selector: 'app-group-form-page',
|
||||
templateUrl: './group-form-page.component.html',
|
||||
styleUrl: './group-form-page.component.scss',
|
||||
})
|
||||
export class GroupFormPageComponent extends FormPageBase<
|
||||
Group,
|
||||
@ -20,8 +22,17 @@ export class GroupFormPageComponent extends FormPageBase<
|
||||
GroupUpdateInput,
|
||||
GroupsDataService
|
||||
> {
|
||||
constructor(private toast: ToastService) {
|
||||
roles: Role[] = [];
|
||||
|
||||
constructor(
|
||||
private toast: ToastService,
|
||||
private cds: CommonDataService
|
||||
) {
|
||||
super();
|
||||
this.cds.getAllRoles().subscribe(roles => {
|
||||
this.roles = roles;
|
||||
});
|
||||
|
||||
if (!this.nodeId) {
|
||||
this.node = this.new();
|
||||
this.setForm(this.node);
|
||||
@ -31,7 +42,7 @@ export class GroupFormPageComponent extends FormPageBase<
|
||||
|
||||
this.dataService
|
||||
.load([{ id: { equal: this.nodeId } }])
|
||||
.subscribe((apiKey) => {
|
||||
.subscribe(apiKey => {
|
||||
this.node = apiKey.nodes[0];
|
||||
this.setForm(this.node);
|
||||
});
|
||||
@ -45,40 +56,48 @@ export class GroupFormPageComponent extends FormPageBase<
|
||||
this.form = new FormGroup({
|
||||
id: new FormControl<number | undefined>(undefined),
|
||||
name: new FormControl<string | undefined>(undefined, Validators.required),
|
||||
roles: new FormControl<Role[]>([]),
|
||||
});
|
||||
this.form.controls["id"].disable();
|
||||
this.form.controls['id'].disable();
|
||||
}
|
||||
|
||||
setForm(node?: Group) {
|
||||
this.form.controls["id"].setValue(node?.id);
|
||||
this.form.controls["name"].setValue(node?.name);
|
||||
this.form.controls['id'].setValue(node?.id);
|
||||
this.form.controls['name'].setValue(node?.name);
|
||||
this.form.controls['roles'].setValue(node?.roles ?? []);
|
||||
}
|
||||
|
||||
getCreateInput(): GroupCreateInput {
|
||||
return {
|
||||
name: this.form.controls["name"].pristine
|
||||
name: this.form.controls['name'].pristine
|
||||
? undefined
|
||||
: (this.form.controls["name"].value ?? undefined),
|
||||
: (this.form.controls['name'].value ?? undefined),
|
||||
roles: this.form.controls['roles'].pristine
|
||||
? undefined
|
||||
: this.form.controls['roles'].value,
|
||||
};
|
||||
}
|
||||
|
||||
getUpdateInput(): GroupUpdateInput {
|
||||
if (!this.node?.id) {
|
||||
throw new Error("Node id is missing");
|
||||
throw new Error('Node id is missing');
|
||||
}
|
||||
|
||||
return {
|
||||
id: this.form.controls["id"].value,
|
||||
name: this.form.controls["name"].pristine
|
||||
id: this.form.controls['id'].value,
|
||||
name: this.form.controls['name'].pristine
|
||||
? undefined
|
||||
: (this.form.controls["name"].value ?? undefined),
|
||||
: (this.form.controls['name'].value ?? undefined),
|
||||
roles: this.form.controls['roles'].pristine
|
||||
? undefined
|
||||
: this.form.controls['roles'].value,
|
||||
};
|
||||
}
|
||||
|
||||
create(apiKey: GroupCreateInput): void {
|
||||
this.dataService.create(apiKey).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.toast.success('action.created');
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
@ -86,7 +105,7 @@ export class GroupFormPageComponent extends FormPageBase<
|
||||
update(apiKey: GroupUpdateInput): void {
|
||||
this.dataService.update(apiKey).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.toast.success('action.created');
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
import { Injectable, Provider } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { Injectable, Provider } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
Create,
|
||||
Delete,
|
||||
PageDataService,
|
||||
Restore,
|
||||
Update,
|
||||
} from "src/app/core/base/page.data.service";
|
||||
import { Filter } from "src/app/model/graphql/filter/filter.model";
|
||||
import { Sort } from "src/app/model/graphql/filter/sort.model";
|
||||
import { Apollo, gql } from "apollo-angular";
|
||||
import { QueryResult } from "src/app/model/entities/query-result";
|
||||
import { DB_MODEL_FRAGMENT } from "src/app/model/graphql/db-model.query";
|
||||
import { catchError, map } from "rxjs/operators";
|
||||
import { SpinnerService } from "src/app/service/spinner.service";
|
||||
} from 'src/app/core/base/page.data.service';
|
||||
import { Filter } from 'src/app/model/graphql/filter/filter.model';
|
||||
import { Sort } from 'src/app/model/graphql/filter/sort.model';
|
||||
import { Apollo, gql } from 'apollo-angular';
|
||||
import { QueryResult } from 'src/app/model/entities/query-result';
|
||||
import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
||||
import {
|
||||
Group,
|
||||
GroupCreateInput,
|
||||
GroupUpdateInput,
|
||||
} from "src/app/model/entities/group";
|
||||
} from 'src/app/model/entities/group';
|
||||
|
||||
@Injectable()
|
||||
export class GroupsDataService
|
||||
@ -31,7 +31,7 @@ export class GroupsDataService
|
||||
{
|
||||
constructor(
|
||||
private spinner: SpinnerService,
|
||||
private apollo: Apollo,
|
||||
private apollo: Apollo
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@ -40,7 +40,7 @@ export class GroupsDataService
|
||||
filter?: Filter[] | undefined,
|
||||
sort?: Sort[] | undefined,
|
||||
skip?: number | undefined,
|
||||
take?: number | undefined,
|
||||
take?: number | undefined
|
||||
): Observable<QueryResult<Group>> {
|
||||
return this.apollo
|
||||
.query<{ groups: QueryResult<Group> }>({
|
||||
@ -57,6 +57,10 @@ export class GroupsDataService
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
roles {
|
||||
id
|
||||
name
|
||||
}
|
||||
|
||||
...DB_MODEL
|
||||
}
|
||||
@ -73,12 +77,12 @@ export class GroupsDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.groups));
|
||||
.pipe(map(result => result.data.groups));
|
||||
}
|
||||
|
||||
loadById(id: number): Observable<Group> {
|
||||
@ -89,6 +93,10 @@ export class GroupsDataService
|
||||
group(filter: { id: { equal: $id } }) {
|
||||
id
|
||||
name
|
||||
roles {
|
||||
id
|
||||
name
|
||||
}
|
||||
|
||||
...DB_MODEL
|
||||
}
|
||||
@ -101,12 +109,12 @@ export class GroupsDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data.groups.nodes[0]));
|
||||
.pipe(map(result => result.data.groups.nodes[0]));
|
||||
}
|
||||
|
||||
create(object: GroupCreateInput): Observable<Group | undefined> {
|
||||
@ -129,16 +137,17 @@ export class GroupsDataService
|
||||
variables: {
|
||||
input: {
|
||||
name: object.name,
|
||||
roles: object.roles?.map(x => x.id),
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.group.create));
|
||||
.pipe(map(result => result.data?.group.create));
|
||||
}
|
||||
|
||||
update(object: GroupUpdateInput): Observable<Group | undefined> {
|
||||
@ -162,16 +171,17 @@ export class GroupsDataService
|
||||
input: {
|
||||
id: object.id,
|
||||
name: object.name,
|
||||
roles: object.roles?.map(x => x.id),
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.group.update));
|
||||
.pipe(map(result => result.data?.group.update));
|
||||
}
|
||||
|
||||
delete(object: Group): Observable<boolean> {
|
||||
@ -189,12 +199,12 @@ export class GroupsDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.group.delete ?? false));
|
||||
.pipe(map(result => result.data?.group.delete ?? false));
|
||||
}
|
||||
|
||||
restore(object: Group): Observable<boolean> {
|
||||
@ -212,12 +222,12 @@ export class GroupsDataService
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.pipe(map((result) => result.data?.group.restore ?? false));
|
||||
.pipe(map(result => result.data?.group.restore ?? false));
|
||||
}
|
||||
|
||||
static provide(): Provider[] {
|
||||
|
@ -109,7 +109,7 @@
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
<div class="bg2 rounded-xl p-3">
|
||||
<div class="bg2 rounded-xl p-3" *ngIf="shortUrlsWithoutGroup.length > 0">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<ng-template *ngFor="let url of shortUrlsWithoutGroup" [ngTemplateOutlet]="shortUrl"
|
||||
[ngTemplateOutletContext]="{ $implicit: url }"></ng-template>
|
||||
|
40
web/src/app/modules/shared/service/common-data.service.ts
Normal file
40
web/src/app/modules/shared/service/common-data.service.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Role } from 'src/app/model/entities/role';
|
||||
import { QueryResult } from 'src/app/model/entities/query-result';
|
||||
import { Apollo, gql } from 'apollo-angular';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { SpinnerService } from 'src/app/service/spinner.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CommonDataService {
|
||||
constructor(
|
||||
private spinner: SpinnerService,
|
||||
private apollo: Apollo
|
||||
) {}
|
||||
|
||||
getAllRoles(): Observable<Role[]> {
|
||||
return this.apollo
|
||||
.query<{ roles: QueryResult<Role> }>({
|
||||
query: gql`
|
||||
query getRoles {
|
||||
roles {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.pipe(
|
||||
catchError(err => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
})
|
||||
)
|
||||
.pipe(map(result => result.data.roles.nodes));
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { User } from "src/app/model/auth/user";
|
||||
import { BehaviorSubject, firstValueFrom, Observable } from "rxjs";
|
||||
import { Apollo, gql } from "apollo-angular";
|
||||
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum";
|
||||
import { KeycloakService } from "keycloak-angular";
|
||||
import { map } from "rxjs/operators";
|
||||
import { Logger } from "src/app/service/logger.service";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { User } from 'src/app/model/auth/user';
|
||||
import { BehaviorSubject, concatWith, firstValueFrom, Observable } from 'rxjs';
|
||||
import { Apollo, gql } from 'apollo-angular';
|
||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Logger } from 'src/app/service/logger.service';
|
||||
|
||||
const log = new Logger("AuthService");
|
||||
const log = new Logger('AuthService');
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
protected anyPermissionForAdminPage = [
|
||||
@ -23,7 +23,7 @@ export class AuthService {
|
||||
|
||||
constructor(
|
||||
private apollo: Apollo,
|
||||
private keycloakService: KeycloakService,
|
||||
private keycloakService: KeycloakService
|
||||
) {}
|
||||
|
||||
private requestUser() {
|
||||
@ -60,11 +60,11 @@ export class AuthService {
|
||||
permission,
|
||||
},
|
||||
})
|
||||
.pipe(map((result) => result.data.userHasPermission));
|
||||
.pipe(map(result => result.data.userHasPermission));
|
||||
}
|
||||
|
||||
private userHasAnyPermission(
|
||||
permissions: PermissionsEnum[],
|
||||
permissions: PermissionsEnum[]
|
||||
): Observable<boolean> {
|
||||
return this.apollo
|
||||
.query<{ userHasAnyPermission: boolean }>({
|
||||
@ -77,7 +77,7 @@ export class AuthService {
|
||||
permissions,
|
||||
},
|
||||
})
|
||||
.pipe(map((result) => result.data.userHasAnyPermission));
|
||||
.pipe(map(result => result.data.userHasAnyPermission));
|
||||
}
|
||||
|
||||
loadUser() {
|
||||
@ -88,8 +88,8 @@ export class AuthService {
|
||||
return;
|
||||
}
|
||||
|
||||
this.requestUser().subscribe((result) => {
|
||||
log.info("User loaded");
|
||||
this.requestUser().subscribe(result => {
|
||||
log.info('User loaded');
|
||||
this.user$.next(result.data.user);
|
||||
});
|
||||
}
|
||||
@ -111,18 +111,15 @@ export class AuthService {
|
||||
if (!this.user$.value) return false;
|
||||
|
||||
const userPermissions = this.user$.value.roles
|
||||
.map((role) => (role.permissions ?? []).map((p) => p.name))
|
||||
.map(role => (role.permissions ?? []).map(p => p.name))
|
||||
.flat();
|
||||
return permissions.every((permission) =>
|
||||
userPermissions.includes(permission),
|
||||
);
|
||||
return permissions.some(permission => userPermissions.includes(permission));
|
||||
}
|
||||
|
||||
async hasAnyPermissionLazy(permissions: PermissionsEnum[]): Promise<boolean> {
|
||||
if (this.user$.value && this.user$.value.roles) {
|
||||
return this.hasAnyPermission(permissions);
|
||||
}
|
||||
|
||||
return await firstValueFrom(this.userHasAnyPermission(permissions));
|
||||
}
|
||||
|
||||
@ -130,7 +127,7 @@ export class AuthService {
|
||||
if (!this.user$.value) return false;
|
||||
|
||||
const permissions = this.user$.value.roles
|
||||
.map((role) => (role.permissions ?? []).map((p) => p.name))
|
||||
.map(role => (role.permissions ?? []).map(p => p.name))
|
||||
.flat();
|
||||
return permissions.includes(permission);
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Logger } from "src/app/service/logger.service";
|
||||
import { ErrorHandler, Injectable } from "@angular/core";
|
||||
import { ToastService } from "src/app/service/toast.service";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { ApolloError } from "@apollo/client/errors";
|
||||
import { MissingPermissionException } from "src/app/model/utils/error";
|
||||
import { GraphQLError } from "graphql/error/GraphQLError";
|
||||
import { ToastOptions } from "src/app/model/utils/toast-options";
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Logger } from 'src/app/service/logger.service';
|
||||
import { ErrorHandler, Injectable } from '@angular/core';
|
||||
import { ToastService } from 'src/app/service/toast.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ApolloError } from '@apollo/client/errors';
|
||||
import { MissingPermissionException } from 'src/app/model/utils/error';
|
||||
import { GraphQLError } from 'graphql/error/GraphQLError';
|
||||
import { ToastOptions } from 'src/app/model/utils/toast-options';
|
||||
|
||||
const logger = new Logger("ErrorHandler");
|
||||
const logger = new Logger('ErrorHandler');
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ErrorHandlingService implements ErrorHandler {
|
||||
constructor(
|
||||
private t: TranslateService,
|
||||
private toast: ToastService,
|
||||
private toast: ToastService
|
||||
) {}
|
||||
|
||||
handleError(error: HttpErrorResponse | ApolloError) {
|
||||
@ -30,7 +30,7 @@ export class ErrorHandlingService implements ErrorHandler {
|
||||
return;
|
||||
}
|
||||
console.error(error);
|
||||
// this.handleHttpError(error);
|
||||
this.handleHttpError(error);
|
||||
}
|
||||
|
||||
private handleHttpError(e: HttpErrorResponse) {
|
||||
@ -40,14 +40,14 @@ export class ErrorHandlingService implements ErrorHandler {
|
||||
error?: string;
|
||||
options?: ToastOptions;
|
||||
} = {
|
||||
summary: this.t.instant("common.error"),
|
||||
summary: this.t.instant('common.error'),
|
||||
detail: e.message,
|
||||
};
|
||||
|
||||
if (e.status === 401) {
|
||||
toast = {
|
||||
summary: this.t.instant("common.error"),
|
||||
detail: this.t.instant("error.unauthorized"),
|
||||
summary: this.t.instant('common.error'),
|
||||
detail: this.t.instant('error.unauthorized'),
|
||||
};
|
||||
}
|
||||
|
||||
@ -57,15 +57,15 @@ export class ErrorHandlingService implements ErrorHandler {
|
||||
.permissions;
|
||||
|
||||
toast = {
|
||||
summary: this.t.instant("common.error"),
|
||||
detail: this.t.instant("error.missing_permissions", {
|
||||
permissions: missingPermissions.join(", "),
|
||||
summary: this.t.instant('common.error'),
|
||||
detail: this.t.instant('error.missing_permissions', {
|
||||
permissions: missingPermissions.join(', '),
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
toast = {
|
||||
summary: this.t.instant("common.error"),
|
||||
detail: this.t.instant("error.permission_denied"),
|
||||
summary: this.t.instant('common.error'),
|
||||
detail: this.t.instant('error.permission_denied'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -73,7 +73,7 @@ export class ErrorHandlingService implements ErrorHandler {
|
||||
if (e.status === 500 && e.error && e.error.errors) {
|
||||
if (e.error.errors.length > 0) {
|
||||
toast = {
|
||||
summary: this.t.instant("common.api_error"),
|
||||
summary: this.t.instant('common.api_error'),
|
||||
detail: e.error.errors[0].message,
|
||||
};
|
||||
}
|
||||
@ -86,8 +86,8 @@ export class ErrorHandlingService implements ErrorHandler {
|
||||
private handleGraphQlErrors(errors: GraphQLError[]) {
|
||||
errors.forEach((e: GraphQLError) => {
|
||||
this.toast.error(
|
||||
this.t.instant("common.api_error"),
|
||||
`${e.message}${e.path ? " " + e.path.join(".") : ""}`,
|
||||
this.t.instant('common.api_error'),
|
||||
`${e.message}${e.path ? ' ' + e.path.join('.') : ''}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ export class SidebarService {
|
||||
routerLink: ['/admin/urls'],
|
||||
visible: await this.auth.hasAnyPermissionLazy([
|
||||
PermissionsEnum.shortUrls,
|
||||
PermissionsEnum.shortUrlsByAssignment,
|
||||
]),
|
||||
},
|
||||
await this.groupAdministration(),
|
||||
|
@ -116,6 +116,7 @@
|
||||
"roles.delete": "Löschen",
|
||||
"roles.update": "Bearbeiten",
|
||||
"short_urls": "Kurz-URLs",
|
||||
"short_urls.by_assignment": "Zuweisung",
|
||||
"short_urls.create": "Erstellen",
|
||||
"short_urls.delete": "Löschen",
|
||||
"short_urls.update": "Bearbeiten",
|
||||
@ -127,6 +128,11 @@
|
||||
"qr": {
|
||||
"width": "Breite"
|
||||
},
|
||||
"permission_descriptions": {
|
||||
"users.update": "Benutzer inkl. der Rollen ändern",
|
||||
"short_urls": "Alle URLs sehen",
|
||||
"short_urls.by_assignment": "Alle Kurz-URLs anzeigen, die einer Gruppe nach Rolle zugewiesen sind"
|
||||
},
|
||||
"role": {
|
||||
"count_header": "Rolle(n)"
|
||||
},
|
||||
|
@ -116,6 +116,7 @@
|
||||
"roles.delete": "Delete",
|
||||
"roles.update": "Update",
|
||||
"short_urls": "Short URLs",
|
||||
"short_urls.by_assignment": "By assignment",
|
||||
"short_urls.create": "Create",
|
||||
"short_urls.delete": "Delete",
|
||||
"short_urls.update": "Update",
|
||||
@ -124,6 +125,11 @@
|
||||
"users.delete": "Delete",
|
||||
"users.update": "Update"
|
||||
},
|
||||
"permission_descriptions": {
|
||||
"users.update": "Change users including their roles",
|
||||
"short_urls": "See all URLs",
|
||||
"short_urls.by_assignment": "See all short urls assigned to a group by role"
|
||||
},
|
||||
"qr": {
|
||||
"width": "Width"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user