Manage user space users #15
Some checks failed
Test API before pr merge / test-lint (pull_request) Failing after 10s
Test before pr merge / test-translation-lint (pull_request) Successful in 36s
Test before pr merge / test-lint (pull_request) Failing after 39s
Test before pr merge / test-before-merge (pull_request) Failing after 1m28s
Some checks failed
Test API before pr merge / test-lint (pull_request) Failing after 10s
Test before pr merge / test-translation-lint (pull_request) Successful in 36s
Test before pr merge / test-lint (pull_request) Failing after 39s
Test before pr merge / test-before-merge (pull_request) Failing after 1m28s
This commit is contained in:
parent
bcd79b18ab
commit
b815710dd6
@ -1,5 +1,5 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Type, Union
|
from typing import Type, Union, Any
|
||||||
|
|
||||||
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
|
||||||
|
@ -240,6 +240,18 @@ class QueryABC(ObjectType):
|
|||||||
):
|
):
|
||||||
await self._require_any_permission(field.require_any_permission)
|
await self._require_any_permission(field.require_any_permission)
|
||||||
|
|
||||||
|
if isinstance(field, MutationField):
|
||||||
|
if field.require_any is not None:
|
||||||
|
await self._require_any(
|
||||||
|
None,
|
||||||
|
*field.require_any,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await resolver(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
|
||||||
result = await resolver(*args, **kwargs)
|
result = await resolver(*args, **kwargs)
|
||||||
|
|
||||||
if field.require_any is not None:
|
if field.require_any is not None:
|
||||||
|
@ -60,7 +60,7 @@ class MutationFieldBuilder(FieldBuilderABC):
|
|||||||
|
|
||||||
def with_input(
|
def with_input(
|
||||||
self,
|
self,
|
||||||
input_type: Type[Union[InputABC, str, int, bool]],
|
input_type: Type[Union[InputABC, str, int, bool, list]],
|
||||||
input_key: str = "input",
|
input_key: str = "input",
|
||||||
) -> Self:
|
) -> Self:
|
||||||
self._input_type = input_type
|
self._input_type = input_type
|
||||||
|
@ -74,6 +74,8 @@ type UserSpaceMutation {
|
|||||||
update(input: UserSpaceUpdateInput!): UserSpace
|
update(input: UserSpaceUpdateInput!): UserSpace
|
||||||
delete(id: Int!): Boolean
|
delete(id: Int!): Boolean
|
||||||
restore(id: Int!): Boolean
|
restore(id: Int!): Boolean
|
||||||
|
|
||||||
|
inviteUsers(emails: [String!]!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input UserSpaceCreateInput {
|
input UserSpaceCreateInput {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from api_graphql.abc.mutation_abc import MutationABC
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
|
from api_graphql.require_any_resolvers import has_assigned_user_spaces
|
||||||
|
from api_graphql.service.query_context import QueryContext
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
|
||||||
@ -51,26 +53,32 @@ class Mutation(MutationABC):
|
|||||||
Permissions.user_spaces_update,
|
Permissions.user_spaces_update,
|
||||||
Permissions.user_spaces_delete,
|
Permissions.user_spaces_delete,
|
||||||
],
|
],
|
||||||
[self._test],
|
[lambda ctx: True],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.add_mutation_type(
|
self.add_mutation_type(
|
||||||
"group",
|
"group",
|
||||||
"Group",
|
"Group",
|
||||||
require_any_permission=[
|
require_any=(
|
||||||
Permissions.groups_create,
|
[
|
||||||
Permissions.groups_update,
|
Permissions.groups_create,
|
||||||
Permissions.groups_delete,
|
Permissions.groups_update,
|
||||||
],
|
Permissions.groups_delete,
|
||||||
|
],
|
||||||
|
[has_assigned_user_spaces],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.add_mutation_type(
|
self.add_mutation_type(
|
||||||
"shortUrl",
|
"shortUrl",
|
||||||
"ShortUrl",
|
"ShortUrl",
|
||||||
require_any_permission=[
|
require_any=(
|
||||||
Permissions.short_urls_create,
|
[
|
||||||
Permissions.short_urls_update,
|
Permissions.short_urls_create,
|
||||||
Permissions.short_urls_delete,
|
Permissions.short_urls_update,
|
||||||
],
|
Permissions.short_urls_delete,
|
||||||
|
],
|
||||||
|
[has_assigned_user_spaces],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add_mutation_type(
|
self.add_mutation_type(
|
||||||
@ -95,7 +103,3 @@ class Mutation(MutationABC):
|
|||||||
"privacy",
|
"privacy",
|
||||||
"Privacy",
|
"Privacy",
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _test(*args, **kwargs):
|
|
||||||
return True
|
|
||||||
|
@ -4,6 +4,7 @@ from api_graphql.abc.mutation_abc import MutationABC
|
|||||||
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
||||||
from api_graphql.input.group_create_input import GroupCreateInput
|
from api_graphql.input.group_create_input import GroupCreateInput
|
||||||
from api_graphql.input.group_update_input import GroupUpdateInput
|
from api_graphql.input.group_update_input import GroupUpdateInput
|
||||||
|
from api_graphql.require_any_resolvers import has_assigned_user_spaces
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
from core.string import first_to_lower
|
from core.string import first_to_lower
|
||||||
from data.schemas.public.group import Group
|
from data.schemas.public.group import Group
|
||||||
@ -26,7 +27,7 @@ class GroupMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.groups_create])
|
.with_require_any([Permissions.groups_create], [has_assigned_user_spaces])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -36,7 +37,7 @@ class GroupMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.groups_update])
|
.with_require_any([Permissions.groups_update], [has_assigned_user_spaces])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -45,7 +46,7 @@ class GroupMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.groups_delete])
|
.with_require_any([Permissions.groups_delete], [has_assigned_user_spaces])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -54,7 +55,7 @@ class GroupMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.groups_delete])
|
.with_require_any([Permissions.groups_delete], [has_assigned_user_spaces])
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -113,9 +114,7 @@ class GroupMutation(MutationABC):
|
|||||||
raise ValueError(f"Group with id {obj.id} not found")
|
raise ValueError(f"Group with id {obj.id} not found")
|
||||||
|
|
||||||
if obj.name is not None:
|
if obj.name is not None:
|
||||||
already_exists = await groupDao.find_by(
|
already_exists = await groupDao.find_by({Group.name: obj.name})
|
||||||
{Group.name: obj.name, Group.id: {"ne": obj.id}}
|
|
||||||
)
|
|
||||||
if len(already_exists) > 0:
|
if len(already_exists) > 0:
|
||||||
raise ValueError(f"Group {obj.name} already exists")
|
raise ValueError(f"Group {obj.name} already exists")
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ from api_graphql.abc.mutation_abc import MutationABC
|
|||||||
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
||||||
from api_graphql.input.short_url_create_input import ShortUrlCreateInput
|
from api_graphql.input.short_url_create_input import ShortUrlCreateInput
|
||||||
from api_graphql.input.short_url_update_input import ShortUrlUpdateInput
|
from api_graphql.input.short_url_update_input import ShortUrlUpdateInput
|
||||||
|
from api_graphql.require_any_resolvers import has_assigned_user_spaces
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
from core.string import first_to_lower
|
from core.string import first_to_lower
|
||||||
from data.schemas.public.domain_dao import domainDao
|
from data.schemas.public.domain_dao import domainDao
|
||||||
@ -27,7 +28,9 @@ class ShortUrlMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.short_urls_create])
|
.with_require_any(
|
||||||
|
[Permissions.short_urls_create], [has_assigned_user_spaces]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -37,7 +40,9 @@ class ShortUrlMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.short_urls_update])
|
.with_require_any(
|
||||||
|
[Permissions.short_urls_update], [has_assigned_user_spaces]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -46,7 +51,9 @@ class ShortUrlMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.short_urls_delete])
|
.with_require_any(
|
||||||
|
[Permissions.short_urls_delete], [has_assigned_user_spaces]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -55,7 +62,9 @@ class ShortUrlMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.short_urls_delete])
|
.with_require_any(
|
||||||
|
[Permissions.short_urls_delete], [has_assigned_user_spaces]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
|
@ -3,6 +3,7 @@ from api_graphql.abc.mutation_abc import MutationABC
|
|||||||
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
|
||||||
from api_graphql.input.user_space_create_input import UserSpaceCreateInput
|
from api_graphql.input.user_space_create_input import UserSpaceCreateInput
|
||||||
from api_graphql.input.user_space_update_input import UserSpaceUpdateInput
|
from api_graphql.input.user_space_update_input import UserSpaceUpdateInput
|
||||||
|
from api_graphql.service.query_context import QueryContext
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
from core.string import first_to_lower
|
from core.string import first_to_lower
|
||||||
from data.schemas.administration.user_dao import userDao
|
from data.schemas.administration.user_dao import userDao
|
||||||
@ -26,7 +27,6 @@ class UserSpaceMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.user_spaces_create])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -36,7 +36,10 @@ class UserSpaceMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.user_spaces_update])
|
.with_require_any(
|
||||||
|
[Permissions.user_spaces_update],
|
||||||
|
[self._resolve_input_user_space_assigned],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -45,7 +48,10 @@ class UserSpaceMutation(MutationABC):
|
|||||||
.with_change_broadcast(
|
.with_change_broadcast(
|
||||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
)
|
)
|
||||||
.with_require_any_permission([Permissions.user_spaces_delete])
|
.with_require_any(
|
||||||
|
[Permissions.user_spaces_delete],
|
||||||
|
[self._resolve_input_user_space_assigned],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
@ -57,6 +63,19 @@ class UserSpaceMutation(MutationABC):
|
|||||||
.with_require_any_permission([Permissions.user_spaces_delete])
|
.with_require_any_permission([Permissions.user_spaces_delete])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.field(
|
||||||
|
MutationFieldBuilder("inviteUsers")
|
||||||
|
.with_resolver(self.resolve_invite_users)
|
||||||
|
.with_input(list, "emails")
|
||||||
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any(
|
||||||
|
[Permissions.user_spaces_create, Permissions.user_spaces_update],
|
||||||
|
[self._resolve_input_user_space_assigned],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def resolve_create(self, obj: UserSpaceCreateInput, *_):
|
async def resolve_create(self, obj: UserSpaceCreateInput, *_):
|
||||||
logger.debug(f"create user_space: {obj.__dict__}")
|
logger.debug(f"create user_space: {obj.__dict__}")
|
||||||
|
|
||||||
@ -88,9 +107,7 @@ class UserSpaceMutation(MutationABC):
|
|||||||
|
|
||||||
user_space = await userSpaceDao.get_by_id(obj.id)
|
user_space = await userSpaceDao.get_by_id(obj.id)
|
||||||
if obj.name is not None:
|
if obj.name is not None:
|
||||||
already_exists = await userSpaceDao.find_by(
|
already_exists = await userSpaceDao.find_by({UserSpace.name: obj.name})
|
||||||
{UserSpace.name: obj.name, UserSpace.id: {"ne": obj.id}}
|
|
||||||
)
|
|
||||||
if len(already_exists) > 0:
|
if len(already_exists) > 0:
|
||||||
raise ValueError(f"UserSpace {obj.name} already exists")
|
raise ValueError(f"UserSpace {obj.name} already exists")
|
||||||
|
|
||||||
@ -123,3 +140,16 @@ class UserSpaceMutation(MutationABC):
|
|||||||
user_space = await userSpaceDao.get_by_id(id)
|
user_space = await userSpaceDao.get_by_id(id)
|
||||||
await userSpaceDao.restore(user_space)
|
await userSpaceDao.restore(user_space)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def resolve_invite_users(self, emails: list[str], *_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _resolve_input_user_space_assigned(ctx: QueryContext):
|
||||||
|
check_dict = ctx.kwargs
|
||||||
|
if "input" in ctx.kwargs:
|
||||||
|
check_dict = ctx.kwargs["input"]
|
||||||
|
|
||||||
|
return "id" in check_dict and check_dict["id"] in [
|
||||||
|
x.id for x in await userSpaceDao.get_assigned_by_user_id(ctx.user.id)
|
||||||
|
]
|
||||||
|
@ -17,5 +17,5 @@ class UserSpaceQuery(DbModelQueryABC):
|
|||||||
self.set_history_reference_dao(shortUrlDao, "userspaceid")
|
self.set_history_reference_dao(shortUrlDao, "userspaceid")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _get_users(group: UserSpace, *_):
|
async def _get_users(space: UserSpace, *_):
|
||||||
return await userSpaceDao.get(group.id)
|
return await userSpaceDao.get_users(space.id)
|
||||||
|
@ -15,6 +15,8 @@ from api_graphql.filter.user_filter import UserFilter
|
|||||||
from api_graphql.filter.user_space_filter import UserSpaceFilter
|
from api_graphql.filter.user_space_filter import UserSpaceFilter
|
||||||
from api_graphql.require_any_resolvers import (
|
from api_graphql.require_any_resolvers import (
|
||||||
by_group_assignment_resolver,
|
by_group_assignment_resolver,
|
||||||
|
by_user_space_assignment_resolver,
|
||||||
|
has_assigned_user_spaces,
|
||||||
)
|
)
|
||||||
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.api_key_dao import apiKeyDao
|
||||||
@ -57,19 +59,21 @@ class Query(QueryABC):
|
|||||||
.with_filter(PermissionFilter)
|
.with_filter(PermissionFilter)
|
||||||
.with_sort(Sort[Permission])
|
.with_sort(Sort[Permission])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
DaoFieldBuilder("roles")
|
DaoFieldBuilder("roles")
|
||||||
.with_dao(roleDao)
|
.with_dao(roleDao)
|
||||||
.with_filter(RoleFilter)
|
.with_filter(RoleFilter)
|
||||||
.with_sort(Sort[Role])
|
.with_sort(Sort[Role])
|
||||||
.with_require_any_permission(
|
.with_require_any(
|
||||||
[
|
[
|
||||||
Permissions.roles,
|
Permissions.roles,
|
||||||
Permissions.users_create,
|
Permissions.users_create,
|
||||||
Permissions.users_update,
|
Permissions.users_update,
|
||||||
Permissions.groups_create,
|
Permissions.groups_create,
|
||||||
Permissions.groups_update,
|
Permissions.groups_update,
|
||||||
]
|
],
|
||||||
|
[has_assigned_user_spaces],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -107,12 +111,13 @@ class Query(QueryABC):
|
|||||||
.with_dao(domainDao)
|
.with_dao(domainDao)
|
||||||
.with_filter(DomainFilter)
|
.with_filter(DomainFilter)
|
||||||
.with_sort(Sort[Domain])
|
.with_sort(Sort[Domain])
|
||||||
.with_require_any_permission(
|
.with_require_any(
|
||||||
[
|
[
|
||||||
Permissions.domains,
|
Permissions.domains,
|
||||||
Permissions.domains_create,
|
Permissions.domains_create,
|
||||||
Permissions.domains_update,
|
Permissions.domains_update,
|
||||||
]
|
],
|
||||||
|
[has_assigned_user_spaces]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,10 +132,11 @@ class Query(QueryABC):
|
|||||||
.with_dao(userSpaceDao)
|
.with_dao(userSpaceDao)
|
||||||
.with_filter(UserSpaceFilter)
|
.with_filter(UserSpaceFilter)
|
||||||
.with_sort(Sort[UserSpace])
|
.with_sort(Sort[UserSpace])
|
||||||
.with_require_any_permission(
|
.with_require_any(
|
||||||
[
|
[
|
||||||
Permissions.user_spaces,
|
Permissions.user_spaces,
|
||||||
]
|
],
|
||||||
|
[lambda ctx: all(x.owner_id == ctx.user.id for x in ctx.data.nodes)]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -145,7 +151,7 @@ class Query(QueryABC):
|
|||||||
Permissions.short_urls_create,
|
Permissions.short_urls_create,
|
||||||
Permissions.short_urls_update,
|
Permissions.short_urls_update,
|
||||||
],
|
],
|
||||||
[by_group_assignment_resolver],
|
[by_user_space_assignment_resolver, by_group_assignment_resolver],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -156,7 +162,7 @@ class Query(QueryABC):
|
|||||||
.with_sort(Sort[ShortUrl])
|
.with_sort(Sort[ShortUrl])
|
||||||
.with_require_any(
|
.with_require_any(
|
||||||
[Permissions.short_urls],
|
[Permissions.short_urls],
|
||||||
[by_group_assignment_resolver],
|
[by_user_space_assignment_resolver, by_group_assignment_resolver],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from api_graphql.service.collection_result import CollectionResult
|
from api_graphql.service.collection_result import CollectionResult
|
||||||
from api_graphql.service.query_context import QueryContext
|
from api_graphql.service.query_context import QueryContext
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
|
from data.schemas.public.user_space_dao import userSpaceDao
|
||||||
from data.schemas.public.user_space_user_dao import userSpaceUserDao
|
from data.schemas.public.user_space_user_dao import userSpaceUserDao
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
@ -9,10 +10,13 @@ async def by_user_space_assignment_resolver(ctx: QueryContext) -> bool:
|
|||||||
if not isinstance(ctx.data, CollectionResult):
|
if not isinstance(ctx.data, CollectionResult):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if len(ctx.data.nodes) == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
user = ctx.user
|
user = ctx.user
|
||||||
|
|
||||||
assigned_user_space_ids = {
|
assigned_user_space_ids = {
|
||||||
us.user_space_id for us in await userSpaceUserDao.find_by_user_id(user.id)
|
us.user_space_id for us in await userSpaceUserDao.get_by_user_id(user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
for node in ctx.data.nodes:
|
for node in ctx.data.nodes:
|
||||||
@ -23,7 +27,7 @@ async def by_user_space_assignment_resolver(ctx: QueryContext) -> bool:
|
|||||||
if user_space.owner_id == user.id or user_space.id in assigned_user_space_ids:
|
if user_space.owner_id == user.id or user_space.id in assigned_user_space_ids:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return len(ctx.data.nodes) == 0
|
||||||
|
|
||||||
|
|
||||||
async def by_group_assignment_resolver(ctx: QueryContext) -> bool:
|
async def by_group_assignment_resolver(ctx: QueryContext) -> bool:
|
||||||
@ -47,3 +51,8 @@ async def by_group_assignment_resolver(ctx: QueryContext) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def has_assigned_user_spaces(ctx: QueryContext):
|
||||||
|
user_spaces = await userSpaceDao.get_assigned_by_user_id(ctx.user.id)
|
||||||
|
return len(user_spaces) > 0
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from api_graphql.abc.subscription_abc import SubscriptionABC
|
from api_graphql.abc.subscription_abc import SubscriptionABC
|
||||||
from api_graphql.field.subscription_field_builder import SubscriptionFieldBuilder
|
from api_graphql.field.subscription_field_builder import SubscriptionFieldBuilder
|
||||||
|
from api_graphql.require_any_resolvers import has_assigned_user_spaces
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
|
||||||
@ -68,10 +69,10 @@ class Subscription(SubscriptionABC):
|
|||||||
self.subscribe(
|
self.subscribe(
|
||||||
SubscriptionFieldBuilder("groupChange")
|
SubscriptionFieldBuilder("groupChange")
|
||||||
.with_resolver(lambda message, *_: message.message)
|
.with_resolver(lambda message, *_: message.message)
|
||||||
.with_require_any_permission([Permissions.groups])
|
.with_require_any([Permissions.groups], [has_assigned_user_spaces])
|
||||||
)
|
)
|
||||||
self.subscribe(
|
self.subscribe(
|
||||||
SubscriptionFieldBuilder("shortUrlChange")
|
SubscriptionFieldBuilder("shortUrlChange")
|
||||||
.with_resolver(lambda message, *_: message.message)
|
.with_resolver(lambda message, *_: message.message)
|
||||||
.with_require_any_permission([Permissions.short_urls])
|
.with_require_any([Permissions.short_urls], [has_assigned_user_spaces])
|
||||||
)
|
)
|
||||||
|
@ -10,8 +10,8 @@ class UserSpaceUserDao(DbModelDaoABC[UserSpaceUser]):
|
|||||||
DbModelDaoABC.__init__(
|
DbModelDaoABC.__init__(
|
||||||
self, __name__, UserSpaceUser, "public.user_spaces_users"
|
self, __name__, UserSpaceUser, "public.user_spaces_users"
|
||||||
)
|
)
|
||||||
self.attribute(UserSpaceUser.user_space_id, str)
|
self.attribute(UserSpaceUser.user_space_id, int)
|
||||||
self.attribute(UserSpaceUser.user_id, str)
|
self.attribute(UserSpaceUser.user_id, int)
|
||||||
|
|
||||||
async def get_by_user_space_id(
|
async def get_by_user_space_id(
|
||||||
self, user_space_id: str, with_deleted=False
|
self, user_space_id: str, with_deleted=False
|
||||||
|
@ -78,6 +78,7 @@ class DataPrivacyService:
|
|||||||
|
|
||||||
# Anonymize internal data
|
# Anonymize internal data
|
||||||
await user.anonymize()
|
await user.anonymize()
|
||||||
|
await userDao.delete(user)
|
||||||
|
|
||||||
# Anonymize external data
|
# Anonymize external data
|
||||||
try:
|
try:
|
||||||
|
@ -4,6 +4,7 @@ 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';
|
||||||
|
import { SidebarService } from 'src/app/service/sidebar.service';
|
||||||
|
|
||||||
const log = new Logger('PermissionGuard');
|
const log = new Logger('PermissionGuard');
|
||||||
|
|
||||||
@ -14,11 +15,20 @@ export class PermissionGuard {
|
|||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private auth: AuthService
|
private auth: AuthService,
|
||||||
|
private sidebar: SidebarService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
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[];
|
||||||
|
const isInUserSpace = route.data['isInUserSpace'] as boolean;
|
||||||
|
|
||||||
|
if (isInUserSpace) {
|
||||||
|
return (
|
||||||
|
this.sidebar.selectedUserSpace$.value !== undefined &&
|
||||||
|
this.sidebar.selectedUserSpace$.value !== null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!permissions || permissions.length === 0) {
|
if (!permissions || permissions.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -13,6 +13,7 @@ export function initializeKeycloak(
|
|||||||
},
|
},
|
||||||
initOptions: {
|
initOptions: {
|
||||||
onLoad: 'check-sso',
|
onLoad: 'check-sso',
|
||||||
|
checkLoginIframe: false,
|
||||||
},
|
},
|
||||||
enableBearerInterceptor: false,
|
enableBearerInterceptor: false,
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,7 @@ export const tokenInterceptor: HttpInterceptorFn = (req, next) => {
|
|||||||
return next(req);
|
return next(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auth = inject(AuthService);
|
||||||
return from(keycloak.getToken()).pipe(
|
return from(keycloak.getToken()).pipe(
|
||||||
switchMap(token => {
|
switchMap(token => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@ -46,7 +47,6 @@ export const tokenInterceptor: HttpInterceptorFn = (req, next) => {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
const auth = inject(AuthService);
|
|
||||||
auth.logout().then();
|
auth.logout().then();
|
||||||
return next(req);
|
return next(req);
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,7 @@ import { DbModelWithHistory } from 'src/app/model/entities/db-model';
|
|||||||
export interface UserSpace extends DbModelWithHistory {
|
export interface UserSpace extends DbModelWithHistory {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
owner?: User;
|
||||||
users?: User[];
|
users?: User[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ const routes: Routes = [
|
|||||||
m => m.GroupsModule
|
m => m.GroupsModule
|
||||||
),
|
),
|
||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: { permissions: [PermissionsEnum.groups] },
|
data: { permissions: [PermissionsEnum.groups], isInUserSpace: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'urls',
|
path: 'urls',
|
||||||
@ -32,6 +32,7 @@ const routes: Routes = [
|
|||||||
PermissionsEnum.shortUrls,
|
PermissionsEnum.shortUrls,
|
||||||
PermissionsEnum.shortUrlsByAssignment,
|
PermissionsEnum.shortUrlsByAssignment,
|
||||||
],
|
],
|
||||||
|
isInUserSpace: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.groupsCreate],
|
permissions: [PermissionsEnum.groupsCreate],
|
||||||
|
isInUserSpace: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -30,6 +31,7 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.groupsUpdate],
|
permissions: [PermissionsEnum.groupsUpdate],
|
||||||
|
isInUserSpace: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -38,6 +40,7 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.groups],
|
permissions: [PermissionsEnum.groups],
|
||||||
|
isInUserSpace: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -22,6 +22,7 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.shortUrlsCreate],
|
permissions: [PermissionsEnum.shortUrlsCreate],
|
||||||
|
isInUserSpace: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -30,6 +31,7 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.shortUrlsUpdate],
|
permissions: [PermissionsEnum.shortUrlsUpdate],
|
||||||
|
isInUserSpace: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -38,6 +40,7 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.shortUrls],
|
permissions: [PermissionsEnum.shortUrls],
|
||||||
|
isInUserSpace: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -13,6 +13,7 @@ import { ConfigService } from 'src/app/service/config.service';
|
|||||||
import { ResolvedTableColumn } from 'src/app/modules/shared/components/table/table.model';
|
import { ResolvedTableColumn } from 'src/app/modules/shared/components/table/table.model';
|
||||||
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';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-short-urls',
|
selector: 'app-short-urls',
|
||||||
@ -61,7 +62,8 @@ export class ShortUrlsPage extends PageBase<
|
|||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private confirmation: ConfirmationDialogService,
|
private confirmation: ConfirmationDialogService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private sidebar: SidebarService
|
private sidebar: SidebarService,
|
||||||
|
private router: Router
|
||||||
) {
|
) {
|
||||||
super(true, {
|
super(true, {
|
||||||
read: [],
|
read: [],
|
||||||
|
@ -27,5 +27,23 @@
|
|||||||
type="text"
|
type="text"
|
||||||
formControlName="name"/>
|
formControlName="name"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="font-bold label">{{ 'user_space.assign_users' | translate }}</p>
|
||||||
|
<div *ngIf="node.users && node.users.length > 0" class="flex flex-wrap gap-1">
|
||||||
|
<div *ngFor="let user of node.users">
|
||||||
|
<p-chip [label]="user.username" [removable]="true" (onRemove)="node.users.splice(node.users.indexOf(user), 1)"></p-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="node.users?.length === 0">
|
||||||
|
<span>{{ 'table.no_entries_found' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="font-bold label">{{ 'user_space.invite_users' | translate }}</p>
|
||||||
|
<div>
|
||||||
|
<app-chip-input formControlName="emailsToInvite" type="email" placeholder="{{'common.enter_emails' | translate}}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-form-page>
|
</app-form-page>
|
||||||
|
@ -8,6 +8,8 @@ import {
|
|||||||
UserSpaceUpdateInput,
|
UserSpaceUpdateInput,
|
||||||
} from 'src/app/model/entities/user-space';
|
} from 'src/app/model/entities/user-space';
|
||||||
import { UserSpacesDataService } from 'src/app/modules/admin/user-spaces/user-spaces.data.service';
|
import { UserSpacesDataService } from 'src/app/modules/admin/user-spaces/user-spaces.data.service';
|
||||||
|
import { User } from 'src/app/model/auth/user';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-space-form-page',
|
selector: 'app-user-space-form-page',
|
||||||
@ -45,6 +47,7 @@ export class UserSpaceFormPageComponent extends FormPageBase<
|
|||||||
name: new FormControl<string | undefined>(undefined, [
|
name: new FormControl<string | undefined>(undefined, [
|
||||||
Validators.required,
|
Validators.required,
|
||||||
]),
|
]),
|
||||||
|
emailsToInvite: new FormControl<string[]>([]),
|
||||||
});
|
});
|
||||||
this.form.controls['id'].disable();
|
this.form.controls['id'].disable();
|
||||||
}
|
}
|
||||||
@ -59,6 +62,7 @@ export class UserSpaceFormPageComponent extends FormPageBase<
|
|||||||
getCreateInput(): UserSpaceCreateInput {
|
getCreateInput(): UserSpaceCreateInput {
|
||||||
return {
|
return {
|
||||||
name: this.form.controls['name'].value ?? undefined,
|
name: this.form.controls['name'].value ?? undefined,
|
||||||
|
users: this.node.users?.map(x => x.id) ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,11 +73,36 @@ export class UserSpaceFormPageComponent extends FormPageBase<
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: this.form.controls['id'].value,
|
id: this.form.controls['id'].value,
|
||||||
name: this.form.controls['name'].value ?? undefined,
|
name: this.form.controls['name'].pristine
|
||||||
|
? undefined
|
||||||
|
: this.form.controls['name'].value,
|
||||||
|
users: this.node.users?.map(x => x.id) ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected handleEMailInvitation() {
|
||||||
|
const emailsToInvite = this.form.controls['emailsToInvite'].value;
|
||||||
|
if (!(emailsToInvite && emailsToInvite.length > 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.inviteUsers(emailsToInvite)
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.error('action.failed', 'user_space.invite_users');
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.success('user_space.invited_users');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
create(object: UserSpaceCreateInput): void {
|
create(object: UserSpaceCreateInput): void {
|
||||||
|
this.handleEMailInvitation();
|
||||||
this.dataService.create(object).subscribe(() => {
|
this.dataService.create(object).subscribe(() => {
|
||||||
this.spinner.hide();
|
this.spinner.hide();
|
||||||
this.toast.success('action.created');
|
this.toast.success('action.created');
|
||||||
@ -82,6 +111,7 @@ export class UserSpaceFormPageComponent extends FormPageBase<
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(object: UserSpaceUpdateInput): void {
|
update(object: UserSpaceUpdateInput): void {
|
||||||
|
this.handleEMailInvitation();
|
||||||
this.dataService.update(object).subscribe(() => {
|
this.dataService.update(object).subscribe(() => {
|
||||||
this.spinner.hide();
|
this.spinner.hide();
|
||||||
this.toast.success('action.created');
|
this.toast.success('action.created');
|
||||||
|
@ -57,6 +57,10 @@ export class UserSpacesDataService
|
|||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
owner {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
|
||||||
...DB_MODEL
|
...DB_MODEL
|
||||||
}
|
}
|
||||||
@ -85,6 +89,16 @@ export class UserSpacesDataService
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
|
||||||
|
owner {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
|
||||||
|
users {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
|
||||||
...DB_MODEL
|
...DB_MODEL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,6 +290,29 @@ export class UserSpacesDataService
|
|||||||
.pipe(map(result => result.data?.userSpace.restore ?? false));
|
.pipe(map(result => result.data?.userSpace.restore ?? false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inviteUsers(emails: string[]): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ userSpace: { inviteUsers: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation inviteUsers($emails: [String!]!) {
|
||||||
|
userSpace {
|
||||||
|
inviteUsers(emails: $emails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
emails,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.userSpace.inviteUsers ?? false));
|
||||||
|
}
|
||||||
|
|
||||||
static provide(): Provider[] {
|
static provide(): Provider[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
<div class="flex flex-wrap items-center input rounded-md p-2.5">
|
||||||
|
<div class="flex flex-wrap items-center flex-grow gap-1">
|
||||||
|
<p-chip *ngFor="let chip of chips; let i = index" [label]="chip" [removable]="true" (onRemove)="onChange(chips)"/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
pInputText
|
||||||
|
[type]="type"
|
||||||
|
[(ngModel)]="inputValue"
|
||||||
|
(keydown.enter)="addChip()"
|
||||||
|
(blur)="onTouched()"
|
||||||
|
class="no-border flex-grow border-0 p-0"
|
||||||
|
[placeholder]="placeholder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ChipInputComponent } from './chip-input.component';
|
||||||
|
|
||||||
|
describe('ChipInputComponent', () => {
|
||||||
|
let component: ChipInputComponent;
|
||||||
|
let fixture: ComponentFixture<ChipInputComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ChipInputComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ChipInputComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,55 @@
|
|||||||
|
import { Component, Input, forwardRef } from '@angular/core';
|
||||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chip-input',
|
||||||
|
templateUrl: './chip-input.component.html',
|
||||||
|
styleUrls: ['./chip-input.component.scss'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => ChipInputComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ChipInputComponent implements ControlValueAccessor {
|
||||||
|
@Input() type: string = 'text';
|
||||||
|
@Input() placeholder?: string;
|
||||||
|
chips: string[] = [];
|
||||||
|
inputValue: string = '';
|
||||||
|
|
||||||
|
protected onChange: (value: string[]) => void = () => {};
|
||||||
|
protected onTouched: () => void = () => {};
|
||||||
|
|
||||||
|
writeValue(value: string[]): void {
|
||||||
|
this.chips = value || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: (value: string[]) => void): void {
|
||||||
|
this.onChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: () => void): void {
|
||||||
|
this.onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
addChip(): void {
|
||||||
|
if (!this.inputValue.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.type === 'number' && isNaN(Number(this.inputValue.trim()))) {
|
||||||
|
return; // Invalid number, do not add
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.type === 'email' &&
|
||||||
|
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.inputValue.trim())
|
||||||
|
) {
|
||||||
|
return; // Invalid email, do not add
|
||||||
|
}
|
||||||
|
this.chips.push(this.inputValue.trim());
|
||||||
|
this.inputValue = '';
|
||||||
|
this.onChange(this.chips);
|
||||||
|
}
|
||||||
|
}
|
@ -72,6 +72,7 @@ import { getMainDefinition } from '@apollo/client/utilities';
|
|||||||
import { Kind, OperationTypeNode } from 'graphql/index';
|
import { Kind, OperationTypeNode } from 'graphql/index';
|
||||||
import { HistorySidebarComponent } from 'src/app/modules/shared/components/history/history-sidebar.component';
|
import { HistorySidebarComponent } from 'src/app/modules/shared/components/history/history-sidebar.component';
|
||||||
import { SliderModule } from 'primeng/slider';
|
import { SliderModule } from 'primeng/slider';
|
||||||
|
import { ChipInputComponent } from './components/chip-input/chip-input.component';
|
||||||
|
|
||||||
const sharedModules = [
|
const sharedModules = [
|
||||||
StepsModule,
|
StepsModule,
|
||||||
@ -146,9 +147,9 @@ function debounce(func: (...args: unknown[]) => void, wait: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [...sharedComponents],
|
declarations: [...sharedComponents, ChipInputComponent],
|
||||||
imports: [CommonModule, ...sharedModules],
|
imports: [CommonModule, ...sharedModules],
|
||||||
exports: [...sharedModules, ...sharedComponents],
|
exports: [...sharedModules, ...sharedComponents, ChipInputComponent],
|
||||||
providers: [
|
providers: [
|
||||||
provideHttpClient(withInterceptors([tokenInterceptor])),
|
provideHttpClient(withInterceptors([tokenInterceptor])),
|
||||||
provideApollo(() => {
|
provideApollo(() => {
|
||||||
|
@ -24,6 +24,11 @@ export class SidebarDataService {
|
|||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
|
||||||
|
owner {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,9 @@ export class SidebarService {
|
|||||||
|
|
||||||
// trust me, you'll need this async
|
// trust me, you'll need this async
|
||||||
async setElements() {
|
async setElements() {
|
||||||
|
const isSelectedUserSpaceOwner =
|
||||||
|
this.selectedUserSpace$.value?.owner?.id === this.auth.user$.value?.id;
|
||||||
|
|
||||||
const elements: MenuElement[] = [
|
const elements: MenuElement[] = [
|
||||||
{
|
{
|
||||||
label: 'sidebar.user_spaces',
|
label: 'sidebar.user_spaces',
|
||||||
@ -108,6 +111,7 @@ export class SidebarService {
|
|||||||
{
|
{
|
||||||
label: 'sidebar.user_space_edit',
|
label: 'sidebar.user_space_edit',
|
||||||
icon: 'pi pi-pencil',
|
icon: 'pi pi-pencil',
|
||||||
|
visible: isSelectedUserSpaceOwner,
|
||||||
routerLink: [
|
routerLink: [
|
||||||
`/admin/rooms/edit/${this.selectedUserSpace$.value?.id}`,
|
`/admin/rooms/edit/${this.selectedUserSpace$.value?.id}`,
|
||||||
],
|
],
|
||||||
@ -117,7 +121,7 @@ export class SidebarService {
|
|||||||
icon: 'pi pi-tags',
|
icon: 'pi pi-tags',
|
||||||
routerLink: ['/admin/groups'],
|
routerLink: ['/admin/groups'],
|
||||||
visible:
|
visible:
|
||||||
this.selectedUserSpace$.value !== null &&
|
!!this.selectedUserSpace$.value ||
|
||||||
(await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups])),
|
(await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups])),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -125,7 +129,7 @@ export class SidebarService {
|
|||||||
icon: 'pi pi-tag',
|
icon: 'pi pi-tag',
|
||||||
routerLink: ['/admin/urls'],
|
routerLink: ['/admin/urls'],
|
||||||
visible:
|
visible:
|
||||||
this.selectedUserSpace$.value !== null &&
|
!!this.selectedUserSpace$.value ||
|
||||||
(await this.auth.hasAnyPermissionLazy([
|
(await this.auth.hasAnyPermissionLazy([
|
||||||
PermissionsEnum.shortUrls,
|
PermissionsEnum.shortUrls,
|
||||||
PermissionsEnum.shortUrlsByAssignment,
|
PermissionsEnum.shortUrlsByAssignment,
|
||||||
@ -134,6 +138,7 @@ export class SidebarService {
|
|||||||
{
|
{
|
||||||
label: 'sidebar.user_space_delete',
|
label: 'sidebar.user_space_delete',
|
||||||
icon: 'pi pi-trash',
|
icon: 'pi pi-trash',
|
||||||
|
visible: isSelectedUserSpaceOwner,
|
||||||
command: () => {
|
command: () => {
|
||||||
this.confirmation.confirmDialog({
|
this.confirmation.confirmDialog({
|
||||||
header: 'dialog.delete.header',
|
header: 'dialog.delete.header',
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"created": "Erstellt",
|
"created": "Erstellt",
|
||||||
"deleted": "Gelöscht",
|
"deleted": "Gelöscht",
|
||||||
|
"failed": "Fehlgeschlagen",
|
||||||
"restored": "Wiederhergestellt",
|
"restored": "Wiederhergestellt",
|
||||||
"updated": "Geändert"
|
"updated": "Geändert"
|
||||||
},
|
},
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"download": "Herunterladen",
|
"download": "Herunterladen",
|
||||||
"editor": "Bearbeiter",
|
"editor": "Bearbeiter",
|
||||||
|
"enter_emails": "E-Mails eintragen",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"group": "Gruppe",
|
"group": "Gruppe",
|
||||||
"groups": "Gruppen",
|
"groups": "Gruppen",
|
||||||
@ -270,5 +272,10 @@
|
|||||||
"keycloak_id": "Keycloak Id",
|
"keycloak_id": "Keycloak Id",
|
||||||
"user": "Benutzer",
|
"user": "Benutzer",
|
||||||
"username": "Benutzername"
|
"username": "Benutzername"
|
||||||
|
},
|
||||||
|
"user_space": {
|
||||||
|
"assign_users": "Benutzer",
|
||||||
|
"invite_users": "Benutzer einladen",
|
||||||
|
"invited_users": "Benutzer eingeladen"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"created": "Created",
|
"created": "Created",
|
||||||
"deleted": "Deleted",
|
"deleted": "Deleted",
|
||||||
|
"failed": "Failed",
|
||||||
"restored": "Recovered",
|
"restored": "Recovered",
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
|
"enter_emails": "Enter E-Mails",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"group": "Group",
|
"group": "Group",
|
||||||
"groups": "Groups",
|
"groups": "Groups",
|
||||||
@ -270,5 +272,10 @@
|
|||||||
"keycloak_id": "Keycloak Id",
|
"keycloak_id": "Keycloak Id",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"username": "Username"
|
"username": "Username"
|
||||||
|
},
|
||||||
|
"user_space": {
|
||||||
|
"assign_users": "Users",
|
||||||
|
"invite_users": "Invite users",
|
||||||
|
"invited_users": "Invited users"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -68,6 +68,11 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-border {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
color: $headerColor;
|
color: $headerColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, .p-checkbox-box, .p-dropdown {
|
input, .input, .p-checkbox-box, .p-dropdown {
|
||||||
border: 1px solid $accentColor;
|
border: 1px solid $accentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user