Reworked user spaces #15
Some checks failed
Test API before pr merge / test-lint (pull_request) Successful in 12s
Test before pr merge / test-translation-lint (pull_request) Successful in 43s
Test before pr merge / test-lint (pull_request) Failing after 46s
Test before pr merge / test-before-merge (pull_request) Failing after 1m39s
Some checks failed
Test API before pr merge / test-lint (pull_request) Successful in 12s
Test before pr merge / test-translation-lint (pull_request) Successful in 43s
Test before pr merge / test-lint (pull_request) Failing after 46s
Test before pr merge / test-before-merge (pull_request) Failing after 1m39s
This commit is contained in:
parent
bf0f5aa54d
commit
bcd79b18ab
@ -51,6 +51,10 @@ class RouteUserExtension:
|
|||||||
if request is None:
|
if request is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
dev_user = await cls.get_dev_user()
|
||||||
|
if dev_user is not None:
|
||||||
|
return dev_user
|
||||||
|
|
||||||
user_id = cls._get_user_id_from_token(request)
|
user_id = cls._get_user_id_from_token(request)
|
||||||
if not user_id:
|
if not user_id:
|
||||||
return None
|
return None
|
||||||
@ -63,6 +67,15 @@ class RouteUserExtension:
|
|||||||
if request is None:
|
if request is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if "Authorization" not in request.headers:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(request.headers.get("Authorization").split()) < 2:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if request.headers.get("Authorization").split()[0] != "DEV-User":
|
||||||
|
return None
|
||||||
|
|
||||||
return await userDao.find_by_keycloak_id(cls.get_token(request))
|
return await userDao.find_by_keycloak_id(cls.get_token(request))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -174,9 +174,9 @@ class QueryABC(ObjectType):
|
|||||||
kwargs["sort"] = None
|
kwargs["sort"] = None
|
||||||
|
|
||||||
if iscoroutinefunction(field.resolver):
|
if iscoroutinefunction(field.resolver):
|
||||||
collection = await field.collection_resolver(*args)
|
collection = await field.resolver(*args)
|
||||||
else:
|
else:
|
||||||
collection = field.collection_resolver(*args)
|
collection = field.resolver(*args)
|
||||||
|
|
||||||
return self._resolve_collection(
|
return self._resolve_collection(
|
||||||
collection,
|
collection,
|
||||||
|
@ -15,7 +15,7 @@ class CollectionField(FieldABC):
|
|||||||
require_any_permission: list[Permissions] = None,
|
require_any_permission: list[Permissions] = None,
|
||||||
require_any: TRequireAny = None,
|
require_any: TRequireAny = None,
|
||||||
public: bool = False,
|
public: bool = False,
|
||||||
collection_resolver: Callable = None,
|
resolver: Callable = None,
|
||||||
filter_type: Type[CollectionFilterABC] = None,
|
filter_type: Type[CollectionFilterABC] = None,
|
||||||
sort_type: Type[T] = None,
|
sort_type: Type[T] = None,
|
||||||
):
|
):
|
||||||
@ -23,13 +23,13 @@ class CollectionField(FieldABC):
|
|||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
self._public = public
|
self._public = public
|
||||||
self._collection_resolver = collection_resolver
|
self._resolver = resolver
|
||||||
self._filter_type = filter_type
|
self._filter_type = filter_type
|
||||||
self._sort_type = sort_type
|
self._sort_type = sort_type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def collection_resolver(self) -> Optional[Callable]:
|
def resolver(self) -> Optional[Callable]:
|
||||||
return self._collection_resolver
|
return self._resolver
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_type(
|
def filter_type(
|
||||||
|
@ -11,13 +11,13 @@ class CollectionFieldBuilder(FieldBuilderABC):
|
|||||||
def __init__(self, name: str):
|
def __init__(self, name: str):
|
||||||
FieldBuilderABC.__init__(self, name)
|
FieldBuilderABC.__init__(self, name)
|
||||||
|
|
||||||
self._collection_resolver = None
|
self._resolver = None
|
||||||
self._filter_type = None
|
self._filter_type = None
|
||||||
self._sort_type = None
|
self._sort_type = None
|
||||||
|
|
||||||
def with_collection_resolver(self, collection_resolver: Callable) -> Self:
|
def with_resolver(self, resolver: Callable) -> Self:
|
||||||
assert collection_resolver is not None, "collection_resolver cannot be None"
|
assert resolver is not None, "resolver cannot be None"
|
||||||
self._collection_resolver = collection_resolver
|
self._resolver = resolver
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def with_filter(self, filter_type: Type[CollectionFilterABC]) -> Self:
|
def with_filter(self, filter_type: Type[CollectionFilterABC]) -> Self:
|
||||||
@ -31,16 +31,14 @@ class CollectionFieldBuilder(FieldBuilderABC):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def build(self) -> CollectionField:
|
def build(self) -> CollectionField:
|
||||||
assert (
|
assert self._resolver is not None, "resolver cannot be None"
|
||||||
self._collection_resolver is not None
|
|
||||||
), "collection_resolver cannot be None"
|
|
||||||
|
|
||||||
return CollectionField(
|
return CollectionField(
|
||||||
self._name,
|
self._name,
|
||||||
self._require_any_permission,
|
self._require_any_permission,
|
||||||
self._require_any,
|
self._require_any,
|
||||||
self._public,
|
self._public,
|
||||||
self._collection_resolver,
|
self._resolver,
|
||||||
self._filter_type,
|
self._filter_type,
|
||||||
self._sort_type,
|
self._sort_type,
|
||||||
)
|
)
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.db_model_collection_filter_abc import DbModelCollectionFilterABC
|
|
||||||
from api_graphql.abc.filter.string_filter import StringCollectionFilter
|
|
||||||
from api_graphql.abc.collection_filter_abc import FilterOperator
|
|
||||||
from core.typing import T
|
|
||||||
|
|
||||||
|
|
||||||
class ApiKeyCollectionFilter(DbModelCollectionFilterABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
obj: dict,
|
|
||||||
op: Optional[FilterOperator] = None,
|
|
||||||
source_value: Optional[T] = None,
|
|
||||||
):
|
|
||||||
DbModelCollectionFilterABC.__init__(self, obj, op, source_value)
|
|
||||||
|
|
||||||
self.add_field("identifier", StringCollectionFilter)
|
|
@ -1,21 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.db_model_collection_filter_abc import DbModelCollectionFilterABC
|
|
||||||
from api_graphql.abc.filter.string_filter import StringCollectionFilter
|
|
||||||
from api_graphql.abc.collection_filter_abc import FilterOperator
|
|
||||||
from core.typing import T
|
|
||||||
|
|
||||||
|
|
||||||
class IpListFilter(DbModelCollectionFilterABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
obj: dict,
|
|
||||||
op: Optional[FilterOperator] = None,
|
|
||||||
source_value: Optional[T] = None,
|
|
||||||
):
|
|
||||||
DbModelCollectionFilterABC.__init__(self, obj, op, source_value)
|
|
||||||
|
|
||||||
self.add_field("ip", StringCollectionFilter)
|
|
||||||
self.add_field("description", StringCollectionFilter)
|
|
||||||
self.add_field("mac", StringCollectionFilter)
|
|
||||||
self.add_field("dns", StringCollectionFilter)
|
|
@ -1,20 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.db_model_collection_filter_abc import DbModelCollectionFilterABC
|
|
||||||
from api_graphql.abc.filter.bool_filter import BoolCollectionFilter
|
|
||||||
from api_graphql.abc.filter.string_filter import StringCollectionFilter
|
|
||||||
from api_graphql.abc.collection_filter_abc import FilterOperator
|
|
||||||
from core.typing import T
|
|
||||||
|
|
||||||
|
|
||||||
class NewsFilter(DbModelCollectionFilterABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
obj: dict,
|
|
||||||
op: Optional[FilterOperator] = None,
|
|
||||||
source_value: Optional[T] = None,
|
|
||||||
):
|
|
||||||
DbModelCollectionFilterABC.__init__(self, obj, op, source_value)
|
|
||||||
|
|
||||||
self.add_field("title", StringCollectionFilter)
|
|
||||||
self.add_field("published", BoolCollectionFilter)
|
|
@ -1,19 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.db_model_collection_filter_abc import DbModelCollectionFilterABC
|
|
||||||
from api_graphql.abc.filter.string_filter import StringCollectionFilter
|
|
||||||
from api_graphql.abc.collection_filter_abc import FilterOperator
|
|
||||||
from core.typing import T
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionFilter(DbModelCollectionFilterABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
obj: dict,
|
|
||||||
op: Optional[FilterOperator] = None,
|
|
||||||
source_value: Optional[T] = None,
|
|
||||||
):
|
|
||||||
DbModelCollectionFilterABC.__init__(self, obj, op, source_value)
|
|
||||||
|
|
||||||
self.add_field("name", StringCollectionFilter)
|
|
||||||
self.add_field("description", StringCollectionFilter)
|
|
@ -1,19 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.db_model_collection_filter_abc import DbModelCollectionFilterABC
|
|
||||||
from api_graphql.abc.filter.string_filter import StringCollectionFilter
|
|
||||||
from api_graphql.abc.collection_filter_abc import FilterOperator
|
|
||||||
from core.typing import T
|
|
||||||
|
|
||||||
|
|
||||||
class RoleFilter(DbModelCollectionFilterABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
obj: dict,
|
|
||||||
op: Optional[FilterOperator] = None,
|
|
||||||
source_value: Optional[T] = None,
|
|
||||||
):
|
|
||||||
DbModelCollectionFilterABC.__init__(self, obj, op, source_value)
|
|
||||||
|
|
||||||
self.add_field("name", StringCollectionFilter)
|
|
||||||
self.add_field("description", StringCollectionFilter)
|
|
@ -1,20 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from api_graphql.abc.db_model_collection_filter_abc import DbModelCollectionFilterABC
|
|
||||||
from api_graphql.abc.filter.string_filter import StringCollectionFilter
|
|
||||||
from api_graphql.abc.collection_filter_abc import FilterOperator
|
|
||||||
from core.typing import T
|
|
||||||
|
|
||||||
|
|
||||||
class UserFilter(DbModelCollectionFilterABC):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
obj: dict,
|
|
||||||
op: Optional[FilterOperator] = None,
|
|
||||||
source_value: Optional[T] = None,
|
|
||||||
):
|
|
||||||
DbModelCollectionFilterABC.__init__(self, obj, op, source_value)
|
|
||||||
|
|
||||||
self.add_field("keycloakId", StringCollectionFilter)
|
|
||||||
self.add_field("username", StringCollectionFilter)
|
|
||||||
self.add_field("email", StringCollectionFilter)
|
|
@ -1,5 +1,6 @@
|
|||||||
from api_graphql.abc.db_model_filter_abc import DbModelFilterABC
|
from api_graphql.abc.db_model_filter_abc import DbModelFilterABC
|
||||||
from api_graphql.abc.filter.string_filter import StringFilter
|
from api_graphql.abc.filter.string_filter import StringFilter
|
||||||
|
from api_graphql.filter.user_space_filter import UserSpaceFilter
|
||||||
|
|
||||||
|
|
||||||
class GroupFilter(DbModelFilterABC):
|
class GroupFilter(DbModelFilterABC):
|
||||||
@ -11,6 +12,7 @@ class GroupFilter(DbModelFilterABC):
|
|||||||
|
|
||||||
self.add_field("name", StringFilter)
|
self.add_field("name", StringFilter)
|
||||||
self.add_field("description", StringFilter)
|
self.add_field("description", StringFilter)
|
||||||
|
self.add_field("userSpace", UserSpaceFilter, "userspace")
|
||||||
|
|
||||||
self.add_field("isNull", bool)
|
self.add_field("isNull", bool)
|
||||||
self.add_field("isNotNull", bool)
|
self.add_field("isNotNull", bool)
|
||||||
|
@ -2,6 +2,7 @@ from api_graphql.abc.db_model_filter_abc import DbModelFilterABC
|
|||||||
from api_graphql.abc.filter.string_filter import StringFilter
|
from api_graphql.abc.filter.string_filter import StringFilter
|
||||||
from api_graphql.filter.domain_filter import DomainFilter
|
from api_graphql.filter.domain_filter import DomainFilter
|
||||||
from api_graphql.filter.group_filter import GroupFilter
|
from api_graphql.filter.group_filter import GroupFilter
|
||||||
|
from api_graphql.filter.user_space_filter import UserSpaceFilter
|
||||||
|
|
||||||
|
|
||||||
class ShortUrlFilter(DbModelFilterABC):
|
class ShortUrlFilter(DbModelFilterABC):
|
||||||
@ -17,3 +18,5 @@ class ShortUrlFilter(DbModelFilterABC):
|
|||||||
|
|
||||||
self.add_field("group", GroupFilter)
|
self.add_field("group", GroupFilter)
|
||||||
self.add_field("domain", DomainFilter)
|
self.add_field("domain", DomainFilter)
|
||||||
|
|
||||||
|
self.add_field("userSpace", UserSpaceFilter, "userspace")
|
||||||
|
17
api/src/api_graphql/filter/user_space_filter.py
Normal file
17
api/src/api_graphql/filter/user_space_filter.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from api_graphql.abc.db_model_filter_abc import DbModelFilterABC
|
||||||
|
from api_graphql.abc.filter.string_filter import StringFilter
|
||||||
|
from api_graphql.filter.user_filter import UserFilter
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceFilter(DbModelFilterABC):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
obj: dict,
|
||||||
|
):
|
||||||
|
DbModelFilterABC.__init__(self, obj)
|
||||||
|
|
||||||
|
self.add_field("name", StringFilter)
|
||||||
|
self.add_field("owner", UserFilter)
|
||||||
|
|
||||||
|
self.add_field("isNull", bool)
|
||||||
|
self.add_field("isNotNull", bool)
|
@ -8,6 +8,8 @@ type GroupHistory implements DbHistoryModel {
|
|||||||
id: Int
|
id: Int
|
||||||
name: String
|
name: String
|
||||||
|
|
||||||
|
userSpace: UserSpace
|
||||||
|
|
||||||
shortUrls: [ShortUrl]
|
shortUrls: [ShortUrl]
|
||||||
roles: [Role]
|
roles: [Role]
|
||||||
|
|
||||||
@ -21,6 +23,8 @@ type Group implements DbModel {
|
|||||||
id: Int
|
id: Int
|
||||||
name: String
|
name: String
|
||||||
|
|
||||||
|
userSpace: UserSpace
|
||||||
|
|
||||||
shortUrls: [ShortUrl]
|
shortUrls: [ShortUrl]
|
||||||
roles: [Role]
|
roles: [Role]
|
||||||
|
|
||||||
@ -55,6 +59,8 @@ input GroupFilter {
|
|||||||
id: IntFilter
|
id: IntFilter
|
||||||
name: StringFilter
|
name: StringFilter
|
||||||
|
|
||||||
|
userSpace: UserSpaceFilter
|
||||||
|
|
||||||
fuzzy: GroupFuzzy
|
fuzzy: GroupFuzzy
|
||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
@ -74,6 +80,7 @@ type GroupMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input GroupCreateInput {
|
input GroupCreateInput {
|
||||||
|
userSpaceId: Int!
|
||||||
name: String!
|
name: String!
|
||||||
roles: [Int]
|
roles: [Int]
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,10 @@ type Mutation {
|
|||||||
user: UserMutation
|
user: UserMutation
|
||||||
role: RoleMutation
|
role: RoleMutation
|
||||||
|
|
||||||
group: GroupMutation
|
|
||||||
domain: DomainMutation
|
domain: DomainMutation
|
||||||
|
|
||||||
|
userSpace: UserSpaceMutation
|
||||||
|
group: GroupMutation
|
||||||
shortUrl: ShortUrlMutation
|
shortUrl: ShortUrlMutation
|
||||||
|
|
||||||
setting: SettingMutation
|
setting: SettingMutation
|
||||||
|
@ -12,6 +12,9 @@ type Query {
|
|||||||
notExistingUsersFromKeycloak: [KeycloakUser]
|
notExistingUsersFromKeycloak: [KeycloakUser]
|
||||||
|
|
||||||
domains(filter: [DomainFilter], sort: [DomainSort], skip: Int, take: Int): DomainResult
|
domains(filter: [DomainFilter], sort: [DomainSort], skip: Int, take: Int): DomainResult
|
||||||
|
|
||||||
|
assignedUserSpaces: UserSpaceResult
|
||||||
|
userSpaces(filter: [UserSpaceFilter], sort: [UserSpaceSort], skip: Int, take: Int): UserSpaceResult
|
||||||
groups(filter: [GroupFilter], sort: [GroupSort], skip: Int, take: Int): GroupResult
|
groups(filter: [GroupFilter], sort: [GroupSort], skip: Int, take: Int): GroupResult
|
||||||
shortUrls(filter: [ShortUrlFilter], sort: [ShortUrlSort], skip: Int, take: Int): ShortUrlResult
|
shortUrls(filter: [ShortUrlFilter], sort: [ShortUrlSort], skip: Int, take: Int): ShortUrlResult
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ type ShortUrlHistory implements DbHistoryModel {
|
|||||||
|
|
||||||
group: Group
|
group: Group
|
||||||
domain: Domain
|
domain: Domain
|
||||||
|
userSpace: UserSpace
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: String
|
editor: String
|
||||||
@ -27,9 +28,11 @@ type ShortUrl implements DbModel {
|
|||||||
targetUrl: String
|
targetUrl: String
|
||||||
description: String
|
description: String
|
||||||
visits: Int
|
visits: Int
|
||||||
|
loadingScreen: Boolean
|
||||||
|
|
||||||
group: Group
|
group: Group
|
||||||
domain: Domain
|
domain: Domain
|
||||||
loadingScreen: Boolean
|
userSpace: UserSpace
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
@ -68,9 +71,12 @@ input ShortUrlFilter {
|
|||||||
targetUrl: StringFilter
|
targetUrl: StringFilter
|
||||||
description: StringFilter
|
description: StringFilter
|
||||||
loadingScreen: BooleanFilter
|
loadingScreen: BooleanFilter
|
||||||
|
|
||||||
group: GroupFilter
|
group: GroupFilter
|
||||||
domain: DomainFilter
|
domain: DomainFilter
|
||||||
|
|
||||||
|
userSpace: UserSpaceFilter
|
||||||
|
|
||||||
fuzzy: ShortUrlFuzzy
|
fuzzy: ShortUrlFuzzy
|
||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
@ -88,6 +94,7 @@ type ShortUrlMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input ShortUrlCreateInput {
|
input ShortUrlCreateInput {
|
||||||
|
userSpaceId: Int!
|
||||||
shortUrl: String!
|
shortUrl: String!
|
||||||
targetUrl: String!
|
targetUrl: String!
|
||||||
description: String
|
description: String
|
||||||
|
@ -12,6 +12,7 @@ type Subscription {
|
|||||||
userLogout: SubscriptionChange
|
userLogout: SubscriptionChange
|
||||||
|
|
||||||
domainChange: SubscriptionChange
|
domainChange: SubscriptionChange
|
||||||
|
userSpaceChange: SubscriptionChange
|
||||||
groupChange: SubscriptionChange
|
groupChange: SubscriptionChange
|
||||||
shortUrlChange: SubscriptionChange
|
shortUrlChange: SubscriptionChange
|
||||||
}
|
}
|
88
api/src/api_graphql/graphql/user_space.gql
Normal file
88
api/src/api_graphql/graphql/user_space.gql
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
type UserSpaceResult {
|
||||||
|
totalCount: Int
|
||||||
|
count: Int
|
||||||
|
nodes: [UserSpace]
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSpaceHistory implements DbHistoryModel {
|
||||||
|
id: Int
|
||||||
|
name: String
|
||||||
|
owner: User
|
||||||
|
|
||||||
|
users: [User]
|
||||||
|
|
||||||
|
deleted: Boolean
|
||||||
|
editor: String
|
||||||
|
created: String
|
||||||
|
updated: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSpace implements DbModel {
|
||||||
|
id: Int
|
||||||
|
name: String
|
||||||
|
owner: User
|
||||||
|
|
||||||
|
users: [User]
|
||||||
|
|
||||||
|
deleted: Boolean
|
||||||
|
editor: User
|
||||||
|
created: String
|
||||||
|
updated: String
|
||||||
|
history: [UserSpaceHistory]
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserSpaceSort {
|
||||||
|
id: SortOrder
|
||||||
|
name: SortOrder
|
||||||
|
owner: SortOrder
|
||||||
|
|
||||||
|
deleted: SortOrder
|
||||||
|
editorId: SortOrder
|
||||||
|
created: SortOrder
|
||||||
|
updated: SortOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserSpaceFuzzyFields {
|
||||||
|
name
|
||||||
|
owner
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserSpaceFuzzy {
|
||||||
|
fields: [UserSpaceFuzzyFields]
|
||||||
|
term: String
|
||||||
|
threshold: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserSpaceFilter {
|
||||||
|
id: IntFilter
|
||||||
|
name: StringFilter
|
||||||
|
owner: UserFilter
|
||||||
|
|
||||||
|
fuzzy: UserSpaceFuzzy
|
||||||
|
|
||||||
|
deleted: BooleanFilter
|
||||||
|
editor: IntFilter
|
||||||
|
created: DateFilter
|
||||||
|
updated: DateFilter
|
||||||
|
|
||||||
|
isNull: Boolean
|
||||||
|
isNotNull: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSpaceMutation {
|
||||||
|
create(input: UserSpaceCreateInput!): UserSpace
|
||||||
|
update(input: UserSpaceUpdateInput!): UserSpace
|
||||||
|
delete(id: Int!): Boolean
|
||||||
|
restore(id: Int!): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserSpaceCreateInput {
|
||||||
|
name: String!
|
||||||
|
users: [Int]
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserSpaceUpdateInput {
|
||||||
|
id: Int!
|
||||||
|
name: String
|
||||||
|
users: [Int]
|
||||||
|
}
|
@ -6,9 +6,14 @@ class GroupCreateInput(InputABC):
|
|||||||
def __init__(self, src: dict):
|
def __init__(self, src: dict):
|
||||||
InputABC.__init__(self, src)
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._user_space_id = self.option("userSpaceId", int, required=True)
|
||||||
self._name = self.option("name", str, required=True)
|
self._name = self.option("name", str, required=True)
|
||||||
self._roles = self.option("roles", list[int])
|
self._roles = self.option("roles", list[int])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_space_id(self) -> int:
|
||||||
|
return self._user_space_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self._name
|
return self._name
|
||||||
|
@ -8,6 +8,7 @@ class ShortUrlCreateInput(InputABC):
|
|||||||
def __init__(self, src: dict):
|
def __init__(self, src: dict):
|
||||||
InputABC.__init__(self, src)
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._user_space_id = self.option("userSpaceId", int, required=True)
|
||||||
self._short_url = self.option("shortUrl", str, required=True)
|
self._short_url = self.option("shortUrl", str, required=True)
|
||||||
self._target_url = self.option("targetUrl", str, required=True)
|
self._target_url = self.option("targetUrl", str, required=True)
|
||||||
self._description = self.option("description", str)
|
self._description = self.option("description", str)
|
||||||
@ -15,6 +16,10 @@ class ShortUrlCreateInput(InputABC):
|
|||||||
self._domain_id = self.option("domainId", int)
|
self._domain_id = self.option("domainId", int)
|
||||||
self._loading_screen = self.option("loadingScreen", bool)
|
self._loading_screen = self.option("loadingScreen", bool)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_space_id(self) -> int:
|
||||||
|
return self._user_space_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def short_url(self) -> str:
|
def short_url(self) -> str:
|
||||||
return self._short_url
|
return self._short_url
|
||||||
|
18
api/src/api_graphql/input/user_space_create_input.py
Normal file
18
api/src/api_graphql/input/user_space_create_input.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceCreateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._name = self.option("name", str, required=True)
|
||||||
|
self._users = self.option("users", list[int])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def users(self) -> list[int]:
|
||||||
|
return self._users
|
23
api/src/api_graphql/input/user_space_update_input.py
Normal file
23
api/src/api_graphql/input/user_space_update_input.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceUpdateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._id = self.option("id", int, required=True)
|
||||||
|
self._name = self.option("name", str)
|
||||||
|
self._users = self.option("users", list[int])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> int:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def users(self) -> list[int]:
|
||||||
|
return self._users
|
@ -1,5 +1,4 @@
|
|||||||
from api_graphql.abc.mutation_abc import MutationABC
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
from api_graphql.require_any_resolvers import by_user_setup_mutation
|
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
|
||||||
@ -44,28 +43,34 @@ class Mutation(MutationABC):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.add_mutation_type(
|
self.add_mutation_type(
|
||||||
"group",
|
"userSpace",
|
||||||
"Group",
|
"UserSpace",
|
||||||
require_any=(
|
require_any=(
|
||||||
[
|
[
|
||||||
Permissions.groups_create,
|
Permissions.user_spaces_create,
|
||||||
Permissions.groups_update,
|
Permissions.user_spaces_update,
|
||||||
Permissions.groups_delete,
|
Permissions.user_spaces_delete,
|
||||||
],
|
],
|
||||||
[by_user_setup_mutation],
|
[self._test],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
self.add_mutation_type(
|
||||||
|
"group",
|
||||||
|
"Group",
|
||||||
|
require_any_permission=[
|
||||||
|
Permissions.groups_create,
|
||||||
|
Permissions.groups_update,
|
||||||
|
Permissions.groups_delete,
|
||||||
|
],
|
||||||
|
)
|
||||||
self.add_mutation_type(
|
self.add_mutation_type(
|
||||||
"shortUrl",
|
"shortUrl",
|
||||||
"ShortUrl",
|
"ShortUrl",
|
||||||
require_any=(
|
require_any_permission=[
|
||||||
[
|
Permissions.short_urls_create,
|
||||||
Permissions.short_urls_create,
|
Permissions.short_urls_update,
|
||||||
Permissions.short_urls_update,
|
Permissions.short_urls_delete,
|
||||||
Permissions.short_urls_delete,
|
],
|
||||||
],
|
|
||||||
[by_user_setup_mutation],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add_mutation_type(
|
self.add_mutation_type(
|
||||||
@ -90,3 +95,7 @@ class Mutation(MutationABC):
|
|||||||
"privacy",
|
"privacy",
|
||||||
"Privacy",
|
"Privacy",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _test(*args, **kwargs):
|
||||||
|
return True
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from api.route import Route
|
|
||||||
from api_graphql.abc.mutation_abc import MutationABC
|
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 by_user_setup_mutation
|
|
||||||
from core.configuration.feature_flags import FeatureFlags
|
|
||||||
from core.configuration.feature_flags_enum import FeatureFlagsEnum
|
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
|
from core.string import first_to_lower
|
||||||
from data.schemas.public.group import Group
|
from data.schemas.public.group import Group
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
from data.schemas.public.group_role_assignment import GroupRoleAssignment
|
from data.schemas.public.group_role_assignment import GroupRoleAssignment
|
||||||
@ -26,26 +23,38 @@ class GroupMutation(MutationABC):
|
|||||||
MutationFieldBuilder("create")
|
MutationFieldBuilder("create")
|
||||||
.with_resolver(self.resolve_create)
|
.with_resolver(self.resolve_create)
|
||||||
.with_input(GroupCreateInput)
|
.with_input(GroupCreateInput)
|
||||||
.with_require_any([Permissions.groups_create], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.groups_create])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
MutationFieldBuilder("update")
|
MutationFieldBuilder("update")
|
||||||
.with_resolver(self.resolve_update)
|
.with_resolver(self.resolve_update)
|
||||||
.with_input(GroupUpdateInput)
|
.with_input(GroupUpdateInput)
|
||||||
.with_require_any([Permissions.groups_update], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.groups_update])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
MutationFieldBuilder("delete")
|
MutationFieldBuilder("delete")
|
||||||
.with_resolver(self.resolve_delete)
|
.with_resolver(self.resolve_delete)
|
||||||
.with_require_any([Permissions.groups_delete], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.groups_delete])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
MutationFieldBuilder("restore")
|
MutationFieldBuilder("restore")
|
||||||
.with_resolver(self.resolve_restore)
|
.with_resolver(self.resolve_restore)
|
||||||
.with_require_any([Permissions.groups_delete], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.groups_delete])
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -87,12 +96,9 @@ class GroupMutation(MutationABC):
|
|||||||
group = Group(
|
group = Group(
|
||||||
0,
|
0,
|
||||||
obj.name,
|
obj.name,
|
||||||
(
|
obj.user_space_id,
|
||||||
(await Route.get_user()).id
|
|
||||||
if await FeatureFlags.has_feature(FeatureFlagsEnum.per_user_setup)
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
gid = await groupDao.create(group)
|
gid = await groupDao.create(group)
|
||||||
|
|
||||||
await cls._handle_group_role_assignments(gid, obj.roles)
|
await cls._handle_group_role_assignments(gid, obj.roles)
|
||||||
|
@ -3,10 +3,8 @@ 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 by_user_setup_mutation
|
|
||||||
from core.configuration.feature_flags import FeatureFlags
|
|
||||||
from core.configuration.feature_flags_enum import FeatureFlagsEnum
|
|
||||||
from core.logger import APILogger
|
from core.logger import APILogger
|
||||||
|
from core.string import first_to_lower
|
||||||
from data.schemas.public.domain_dao import domainDao
|
from data.schemas.public.domain_dao import domainDao
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
from data.schemas.public.short_url import ShortUrl
|
from data.schemas.public.short_url import ShortUrl
|
||||||
@ -26,31 +24,46 @@ class ShortUrlMutation(MutationABC):
|
|||||||
MutationFieldBuilder("create")
|
MutationFieldBuilder("create")
|
||||||
.with_resolver(self.resolve_create)
|
.with_resolver(self.resolve_create)
|
||||||
.with_input(ShortUrlCreateInput)
|
.with_input(ShortUrlCreateInput)
|
||||||
.with_require_any([Permissions.short_urls_create], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.short_urls_create])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
MutationFieldBuilder("update")
|
MutationFieldBuilder("update")
|
||||||
.with_resolver(self.resolve_update)
|
.with_resolver(self.resolve_update)
|
||||||
.with_input(ShortUrlUpdateInput)
|
.with_input(ShortUrlUpdateInput)
|
||||||
.with_require_any([Permissions.short_urls_update], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.short_urls_update])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
MutationFieldBuilder("delete")
|
MutationFieldBuilder("delete")
|
||||||
.with_resolver(self.resolve_delete)
|
.with_resolver(self.resolve_delete)
|
||||||
.with_require_any([Permissions.short_urls_delete], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.short_urls_delete])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
MutationFieldBuilder("restore")
|
MutationFieldBuilder("restore")
|
||||||
.with_resolver(self.resolve_restore)
|
.with_resolver(self.resolve_restore)
|
||||||
.with_require_any([Permissions.short_urls_delete], [by_user_setup_mutation])
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.short_urls_delete])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
MutationFieldBuilder("trackVisit")
|
MutationFieldBuilder("trackVisit")
|
||||||
.with_resolver(self.resolve_track_visit)
|
.with_resolver(self.resolve_track_visit)
|
||||||
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
.with_require_any_permission([Permissions.short_urls_update])
|
.with_require_any_permission([Permissions.short_urls_update])
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,11 +83,7 @@ class ShortUrlMutation(MutationABC):
|
|||||||
obj.group_id,
|
obj.group_id,
|
||||||
obj.domain_id,
|
obj.domain_id,
|
||||||
obj.loading_screen,
|
obj.loading_screen,
|
||||||
(
|
obj.user_space_id,
|
||||||
(await Route.get_user()).id
|
|
||||||
if await FeatureFlags.has_feature(FeatureFlagsEnum.per_user_setup)
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
nid = await shortUrlDao.create(short_url)
|
nid = await shortUrlDao.create(short_url)
|
||||||
return await shortUrlDao.get_by_id(nid)
|
return await shortUrlDao.get_by_id(nid)
|
||||||
|
125
api/src/api_graphql/mutations/user_space_mutation.py
Normal file
125
api/src/api_graphql/mutations/user_space_mutation.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
from api.route import Route
|
||||||
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
|
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_update_input import UserSpaceUpdateInput
|
||||||
|
from core.logger import APILogger
|
||||||
|
from core.string import first_to_lower
|
||||||
|
from data.schemas.administration.user_dao import userDao
|
||||||
|
from data.schemas.public.user_space import UserSpace
|
||||||
|
from data.schemas.public.user_space_dao import userSpaceDao
|
||||||
|
from data.schemas.public.user_space_user import UserSpaceUser
|
||||||
|
from data.schemas.public.user_space_user_dao import userSpaceUserDao
|
||||||
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
logger = APILogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceMutation(MutationABC):
|
||||||
|
def __init__(self):
|
||||||
|
MutationABC.__init__(self, "UserSpace")
|
||||||
|
|
||||||
|
self.field(
|
||||||
|
MutationFieldBuilder("create")
|
||||||
|
.with_resolver(self.resolve_create)
|
||||||
|
.with_input(UserSpaceCreateInput)
|
||||||
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.user_spaces_create])
|
||||||
|
)
|
||||||
|
|
||||||
|
self.field(
|
||||||
|
MutationFieldBuilder("update")
|
||||||
|
.with_resolver(self.resolve_update)
|
||||||
|
.with_input(UserSpaceUpdateInput)
|
||||||
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.user_spaces_update])
|
||||||
|
)
|
||||||
|
|
||||||
|
self.field(
|
||||||
|
MutationFieldBuilder("delete")
|
||||||
|
.with_resolver(self.resolve_delete)
|
||||||
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.user_spaces_delete])
|
||||||
|
)
|
||||||
|
|
||||||
|
self.field(
|
||||||
|
MutationFieldBuilder("restore")
|
||||||
|
.with_resolver(self.resolve_restore)
|
||||||
|
.with_change_broadcast(
|
||||||
|
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||||
|
)
|
||||||
|
.with_require_any_permission([Permissions.user_spaces_delete])
|
||||||
|
)
|
||||||
|
|
||||||
|
async def resolve_create(self, obj: UserSpaceCreateInput, *_):
|
||||||
|
logger.debug(f"create user_space: {obj.__dict__}")
|
||||||
|
|
||||||
|
already_exists = await userSpaceDao.find_by({UserSpace.name: obj.name})
|
||||||
|
if len(already_exists) > 0:
|
||||||
|
raise ValueError(f"UserSpace {obj.name} already exists")
|
||||||
|
|
||||||
|
user_space = UserSpace(0, obj.name, (await Route.get_user()).id)
|
||||||
|
gid = await userSpaceDao.create(user_space)
|
||||||
|
|
||||||
|
await self._resolve_assignments(
|
||||||
|
obj.get("users", []),
|
||||||
|
user_space,
|
||||||
|
UserSpaceUser.user_space_id,
|
||||||
|
UserSpaceUser.user_id,
|
||||||
|
userSpaceDao,
|
||||||
|
userSpaceUserDao,
|
||||||
|
UserSpaceUser,
|
||||||
|
userDao,
|
||||||
|
)
|
||||||
|
|
||||||
|
return await userSpaceDao.get_by_id(gid)
|
||||||
|
|
||||||
|
async def resolve_update(self, obj: UserSpaceUpdateInput, *_):
|
||||||
|
logger.debug(f"update user_space: {input}")
|
||||||
|
|
||||||
|
if await userSpaceDao.find_by_id(obj.id) is None:
|
||||||
|
raise ValueError(f"UserSpace with id {obj.id} not found")
|
||||||
|
|
||||||
|
user_space = await userSpaceDao.get_by_id(obj.id)
|
||||||
|
if obj.name is not None:
|
||||||
|
already_exists = await userSpaceDao.find_by(
|
||||||
|
{UserSpace.name: obj.name, UserSpace.id: {"ne": obj.id}}
|
||||||
|
)
|
||||||
|
if len(already_exists) > 0:
|
||||||
|
raise ValueError(f"UserSpace {obj.name} already exists")
|
||||||
|
|
||||||
|
user_space.name = obj.name
|
||||||
|
await userSpaceDao.update(user_space)
|
||||||
|
|
||||||
|
await self._resolve_assignments(
|
||||||
|
obj.get("users", []),
|
||||||
|
user_space,
|
||||||
|
UserSpaceUser.user_space_id,
|
||||||
|
UserSpaceUser.user_id,
|
||||||
|
userSpaceDao,
|
||||||
|
userSpaceUserDao,
|
||||||
|
UserSpaceUser,
|
||||||
|
userDao,
|
||||||
|
)
|
||||||
|
|
||||||
|
return await userSpaceDao.get_by_id(obj.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_delete(*_, id: str):
|
||||||
|
logger.debug(f"delete user_space: {id}")
|
||||||
|
user_space = await userSpaceDao.get_by_id(id)
|
||||||
|
await userSpaceDao.delete(user_space)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_restore(*_, id: str):
|
||||||
|
logger.debug(f"restore user_space: {id}")
|
||||||
|
user_space = await userSpaceDao.get_by_id(id)
|
||||||
|
await userSpaceDao.restore(user_space)
|
||||||
|
return True
|
@ -1,6 +1,6 @@
|
|||||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
||||||
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
||||||
from api_graphql.require_any_resolvers import by_assignment_resolver
|
from api_graphql.require_any_resolvers import by_group_assignment_resolver
|
||||||
from data.schemas.public.group import Group
|
from data.schemas.public.group import Group
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
||||||
@ -14,6 +14,7 @@ class GroupHistoryQuery(DbHistoryModelQueryABC):
|
|||||||
DbHistoryModelQueryABC.__init__(self, "Group")
|
DbHistoryModelQueryABC.__init__(self, "Group")
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
|
self.set_field("userSpace", lambda x, *_: x.user_space)
|
||||||
self.field(
|
self.field(
|
||||||
ResolverFieldBuilder("shortUrls")
|
ResolverFieldBuilder("shortUrls")
|
||||||
.with_resolver(self._get_urls)
|
.with_resolver(self._get_urls)
|
||||||
@ -21,7 +22,7 @@ class GroupHistoryQuery(DbHistoryModelQueryABC):
|
|||||||
[
|
[
|
||||||
Permissions.groups,
|
Permissions.groups,
|
||||||
],
|
],
|
||||||
[by_assignment_resolver],
|
[by_group_assignment_resolver],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.set_field(
|
self.set_field(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
||||||
from api_graphql.require_any_resolvers import by_assignment_resolver
|
from api_graphql.require_any_resolvers import by_group_assignment_resolver
|
||||||
|
|
||||||
from data.schemas.public.group import Group
|
from data.schemas.public.group import Group
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
||||||
@ -14,6 +15,7 @@ class GroupQuery(DbModelQueryABC):
|
|||||||
DbModelQueryABC.__init__(self, "Group", groupDao, with_history=True)
|
DbModelQueryABC.__init__(self, "Group", groupDao, with_history=True)
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
|
self.set_field("userSpace", lambda x, *_: x.user_space)
|
||||||
self.field(
|
self.field(
|
||||||
ResolverFieldBuilder("shortUrls")
|
ResolverFieldBuilder("shortUrls")
|
||||||
.with_resolver(self._get_urls)
|
.with_resolver(self._get_urls)
|
||||||
@ -21,7 +23,7 @@ class GroupQuery(DbModelQueryABC):
|
|||||||
[
|
[
|
||||||
Permissions.groups,
|
Permissions.groups,
|
||||||
],
|
],
|
||||||
[by_assignment_resolver],
|
[by_group_assignment_resolver],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.set_field("roles", self._get_roles)
|
self.set_field("roles", self._get_roles)
|
||||||
|
@ -8,7 +8,10 @@ class ShortUrlQuery(DbHistoryModelQueryABC):
|
|||||||
self.set_field("shortUrl", lambda x, *_: x.short_url)
|
self.set_field("shortUrl", lambda x, *_: x.short_url)
|
||||||
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
||||||
self.set_field("description", lambda x, *_: x.description)
|
self.set_field("description", lambda x, *_: x.description)
|
||||||
self.set_field("group", lambda x, *_: x.group)
|
|
||||||
self.set_field("domain", lambda x, *_: x.domain)
|
|
||||||
self.set_field("visits", lambda x, *_: x.visit_count)
|
self.set_field("visits", lambda x, *_: x.visit_count)
|
||||||
self.set_field("loadingScreen", lambda x, *_: x.loading_screen)
|
self.set_field("loadingScreen", lambda x, *_: x.loading_screen)
|
||||||
|
|
||||||
|
self.set_field("group", lambda x, *_: x.group)
|
||||||
|
self.set_field("domain", lambda x, *_: x.domain)
|
||||||
|
|
||||||
|
self.set_field("userSpace", lambda x, *_: x.user_space)
|
||||||
|
@ -9,7 +9,10 @@ class ShortUrlQuery(DbModelQueryABC):
|
|||||||
self.set_field("shortUrl", lambda x, *_: x.short_url)
|
self.set_field("shortUrl", lambda x, *_: x.short_url)
|
||||||
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
self.set_field("targetUrl", lambda x, *_: x.target_url)
|
||||||
self.set_field("description", lambda x, *_: x.description)
|
self.set_field("description", lambda x, *_: x.description)
|
||||||
self.set_field("group", lambda x, *_: x.group)
|
|
||||||
self.set_field("domain", lambda x, *_: x.domain)
|
|
||||||
self.set_field("visits", lambda x, *_: x.visit_count)
|
self.set_field("visits", lambda x, *_: x.visit_count)
|
||||||
self.set_field("loadingScreen", lambda x, *_: x.loading_screen)
|
self.set_field("loadingScreen", lambda x, *_: x.loading_screen)
|
||||||
|
|
||||||
|
self.set_field("group", lambda x, *_: x.group)
|
||||||
|
self.set_field("domain", lambda x, *_: x.domain)
|
||||||
|
|
||||||
|
self.set_field("userSpace", lambda x, *_: x.user_space)
|
||||||
|
22
api/src/api_graphql/queries/user_space_history_query.py
Normal file
22
api/src/api_graphql/queries/user_space_history_query.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
||||||
|
from data.schemas.administration.user_dao import userDao
|
||||||
|
from data.schemas.public.user_space_user_dao import userSpaceUserDao
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceHistoryQuery(DbHistoryModelQueryABC):
|
||||||
|
def __init__(self):
|
||||||
|
DbHistoryModelQueryABC.__init__(self, "UserSpace")
|
||||||
|
|
||||||
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
|
self.set_field("owner", lambda x, *_: x.owner)
|
||||||
|
self.set_field(
|
||||||
|
"users",
|
||||||
|
lambda x, *_: self._resolve_foreign_history(
|
||||||
|
x.updated,
|
||||||
|
x.id,
|
||||||
|
userSpaceUserDao,
|
||||||
|
userDao,
|
||||||
|
lambda y: y.user_id,
|
||||||
|
obj_key="userspaceid",
|
||||||
|
),
|
||||||
|
)
|
21
api/src/api_graphql/queries/user_space_query.py
Normal file
21
api/src/api_graphql/queries/user_space_query.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||||
|
from data.schemas.public.group_dao import groupDao
|
||||||
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
|
from data.schemas.public.user_space import UserSpace
|
||||||
|
from data.schemas.public.user_space_dao import userSpaceDao
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceQuery(DbModelQueryABC):
|
||||||
|
def __init__(self):
|
||||||
|
DbModelQueryABC.__init__(self, "UserSpace", userSpaceDao, with_history=True)
|
||||||
|
|
||||||
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
|
self.set_field("owner", lambda x, *_: x.owner)
|
||||||
|
self.set_field("users", self._get_users)
|
||||||
|
|
||||||
|
self.set_history_reference_dao(groupDao, "userspaceid")
|
||||||
|
self.set_history_reference_dao(shortUrlDao, "userspaceid")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _get_users(group: UserSpace, *_):
|
||||||
|
return await userSpaceDao.get(group.id)
|
@ -2,6 +2,7 @@ from api.auth.keycloak_client import Keycloak
|
|||||||
from api.route import Route
|
from api.route import Route
|
||||||
from api_graphql.abc.query_abc import QueryABC
|
from api_graphql.abc.query_abc import QueryABC
|
||||||
from api_graphql.abc.sort_abc import Sort
|
from api_graphql.abc.sort_abc import Sort
|
||||||
|
from api_graphql.field.collection_field_builder import CollectionFieldBuilder
|
||||||
from api_graphql.field.dao_field_builder import DaoFieldBuilder
|
from api_graphql.field.dao_field_builder import DaoFieldBuilder
|
||||||
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
||||||
from api_graphql.filter.api_key_filter import ApiKeyFilter
|
from api_graphql.filter.api_key_filter import ApiKeyFilter
|
||||||
@ -11,12 +12,10 @@ from api_graphql.filter.permission_filter import PermissionFilter
|
|||||||
from api_graphql.filter.role_filter import RoleFilter
|
from api_graphql.filter.role_filter import RoleFilter
|
||||||
from api_graphql.filter.short_url_filter import ShortUrlFilter
|
from api_graphql.filter.short_url_filter import ShortUrlFilter
|
||||||
from api_graphql.filter.user_filter import UserFilter
|
from api_graphql.filter.user_filter import UserFilter
|
||||||
|
from api_graphql.filter.user_space_filter import UserSpaceFilter
|
||||||
from api_graphql.require_any_resolvers import (
|
from api_graphql.require_any_resolvers import (
|
||||||
by_assignment_resolver,
|
by_group_assignment_resolver,
|
||||||
by_user_setup_resolver,
|
|
||||||
)
|
)
|
||||||
from core.configuration.feature_flags import FeatureFlags
|
|
||||||
from core.configuration.feature_flags_enum import FeatureFlagsEnum
|
|
||||||
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
|
||||||
from data.schemas.administration.user import User
|
from data.schemas.administration.user import User
|
||||||
@ -33,6 +32,8 @@ from data.schemas.public.short_url import ShortUrl
|
|||||||
from data.schemas.public.short_url_dao import shortUrlDao
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
from data.schemas.public.user_setting import UserSetting
|
from data.schemas.public.user_setting import UserSetting
|
||||||
from data.schemas.public.user_setting_dao import userSettingDao
|
from data.schemas.public.user_setting_dao import userSettingDao
|
||||||
|
from data.schemas.public.user_space import UserSpace
|
||||||
|
from data.schemas.public.user_space_dao import userSpaceDao
|
||||||
from data.schemas.system.feature_flag_dao import featureFlagDao
|
from data.schemas.system.feature_flag_dao import featureFlagDao
|
||||||
from data.schemas.system.setting_dao import settingDao
|
from data.schemas.system.setting_dao import settingDao
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
@ -109,13 +110,31 @@ class Query(QueryABC):
|
|||||||
.with_require_any_permission(
|
.with_require_any_permission(
|
||||||
[
|
[
|
||||||
Permissions.domains,
|
Permissions.domains,
|
||||||
Permissions.short_urls_create,
|
Permissions.domains_create,
|
||||||
Permissions.short_urls_update,
|
Permissions.domains_update,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
group_field = (
|
self.field(
|
||||||
|
CollectionFieldBuilder("assignedUserSpaces").with_resolver(
|
||||||
|
self._resolve_assigned_user_spaces
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.field(
|
||||||
|
DaoFieldBuilder("userSpaces")
|
||||||
|
.with_dao(userSpaceDao)
|
||||||
|
.with_filter(UserSpaceFilter)
|
||||||
|
.with_sort(Sort[UserSpace])
|
||||||
|
.with_require_any_permission(
|
||||||
|
[
|
||||||
|
Permissions.user_spaces,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.field(
|
||||||
DaoFieldBuilder("groups")
|
DaoFieldBuilder("groups")
|
||||||
.with_dao(groupDao)
|
.with_dao(groupDao)
|
||||||
.with_filter(GroupFilter)
|
.with_filter(GroupFilter)
|
||||||
@ -126,35 +145,21 @@ class Query(QueryABC):
|
|||||||
Permissions.short_urls_create,
|
Permissions.short_urls_create,
|
||||||
Permissions.short_urls_update,
|
Permissions.short_urls_update,
|
||||||
],
|
],
|
||||||
[by_assignment_resolver, by_user_setup_resolver],
|
[by_group_assignment_resolver],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if FeatureFlags.get_default(FeatureFlagsEnum.per_user_setup):
|
self.field(
|
||||||
group_field = group_field.with_default_filter(
|
|
||||||
self._resolve_default_user_filter
|
|
||||||
)
|
|
||||||
|
|
||||||
self.field(group_field)
|
|
||||||
|
|
||||||
short_url_field = (
|
|
||||||
DaoFieldBuilder("shortUrls")
|
DaoFieldBuilder("shortUrls")
|
||||||
.with_dao(shortUrlDao)
|
.with_dao(shortUrlDao)
|
||||||
.with_filter(ShortUrlFilter)
|
.with_filter(ShortUrlFilter)
|
||||||
.with_sort(Sort[ShortUrl])
|
.with_sort(Sort[ShortUrl])
|
||||||
.with_require_any(
|
.with_require_any(
|
||||||
[Permissions.short_urls],
|
[Permissions.short_urls],
|
||||||
[by_assignment_resolver, by_user_setup_resolver],
|
[by_group_assignment_resolver],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if FeatureFlags.get_default(FeatureFlagsEnum.per_user_setup):
|
|
||||||
short_url_field = short_url_field.with_default_filter(
|
|
||||||
self._resolve_default_user_filter
|
|
||||||
)
|
|
||||||
|
|
||||||
self.field(short_url_field)
|
|
||||||
|
|
||||||
self.field(
|
self.field(
|
||||||
ResolverFieldBuilder("settings")
|
ResolverFieldBuilder("settings")
|
||||||
.with_resolver(self._resolve_settings)
|
.with_resolver(self._resolve_settings)
|
||||||
@ -230,3 +235,11 @@ class Query(QueryABC):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def _resolve_default_user_filter(*args, **kwargs) -> dict:
|
async def _resolve_default_user_filter(*args, **kwargs) -> dict:
|
||||||
return {"user": {"id": {"equal": (await Route.get_user()).id}}}
|
return {"user": {"id": {"equal": (await Route.get_user()).id}}}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _resolve_assigned_user_spaces(*args, **kwargs):
|
||||||
|
user = await Route.get_user()
|
||||||
|
if user is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return await userSpaceDao.get_assigned_by_user_id(user.id)
|
||||||
|
@ -1,12 +1,32 @@
|
|||||||
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 core.configuration.feature_flags import FeatureFlags
|
|
||||||
from core.configuration.feature_flags_enum import FeatureFlagsEnum
|
|
||||||
from data.schemas.public.group_dao import groupDao
|
from data.schemas.public.group_dao import groupDao
|
||||||
|
from data.schemas.public.user_space_user_dao import userSpaceUserDao
|
||||||
from service.permission.permissions_enum import Permissions
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
|
||||||
async def by_assignment_resolver(ctx: QueryContext) -> bool:
|
async def by_user_space_assignment_resolver(ctx: QueryContext) -> bool:
|
||||||
|
if not isinstance(ctx.data, CollectionResult):
|
||||||
|
return False
|
||||||
|
|
||||||
|
user = ctx.user
|
||||||
|
|
||||||
|
assigned_user_space_ids = {
|
||||||
|
us.user_space_id for us in await userSpaceUserDao.find_by_user_id(user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
for node in ctx.data.nodes:
|
||||||
|
user_space = await node.user_space
|
||||||
|
if user_space is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user_space.owner_id == user.id or user_space.id in assigned_user_space_ids:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def by_group_assignment_resolver(ctx: QueryContext) -> bool:
|
||||||
if not isinstance(ctx.data, CollectionResult):
|
if not isinstance(ctx.data, CollectionResult):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -27,17 +47,3 @@ async def by_assignment_resolver(ctx: QueryContext) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def by_user_setup_resolver(ctx: QueryContext) -> bool:
|
|
||||||
if not isinstance(ctx.data, CollectionResult):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not FeatureFlags.has_feature(FeatureFlagsEnum.per_user_setup):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return all(x.user_id == ctx.user.id for x in ctx.data.nodes)
|
|
||||||
|
|
||||||
|
|
||||||
async def by_user_setup_mutation(ctx: QueryContext) -> bool:
|
|
||||||
return await FeatureFlags.has_feature(FeatureFlagsEnum.per_user_setup)
|
|
||||||
|
@ -60,6 +60,11 @@ class Subscription(SubscriptionABC):
|
|||||||
.with_resolver(lambda message, *_: message.message)
|
.with_resolver(lambda message, *_: message.message)
|
||||||
.with_require_any_permission([Permissions.domains])
|
.with_require_any_permission([Permissions.domains])
|
||||||
)
|
)
|
||||||
|
self.subscribe(
|
||||||
|
SubscriptionFieldBuilder("userSpaceChange").with_resolver(
|
||||||
|
lambda message, *_: message.message
|
||||||
|
)
|
||||||
|
)
|
||||||
self.subscribe(
|
self.subscribe(
|
||||||
SubscriptionFieldBuilder("groupChange")
|
SubscriptionFieldBuilder("groupChange")
|
||||||
.with_resolver(lambda message, *_: message.message)
|
.with_resolver(lambda message, *_: message.message)
|
||||||
|
@ -9,14 +9,9 @@ class FeatureFlags:
|
|||||||
_flags = {
|
_flags = {
|
||||||
FeatureFlagsEnum.version_endpoint.value: True, # 15.01.2025
|
FeatureFlagsEnum.version_endpoint.value: True, # 15.01.2025
|
||||||
FeatureFlagsEnum.technical_demo_banner.value: False, # 18.04.2025
|
FeatureFlagsEnum.technical_demo_banner.value: False, # 18.04.2025
|
||||||
FeatureFlagsEnum.per_user_setup.value: Environment.get(
|
|
||||||
"PER_USER_SETUP", bool, False
|
|
||||||
), # 18.04.2025
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_overwrite_flags = [
|
_overwrite_flags = []
|
||||||
FeatureFlagsEnum.per_user_setup.value,
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def overwrite_flag(key: str):
|
def overwrite_flag(key: str):
|
||||||
|
@ -4,4 +4,3 @@ from enum import Enum
|
|||||||
class FeatureFlagsEnum(Enum):
|
class FeatureFlagsEnum(Enum):
|
||||||
version_endpoint = "VersionEndpoint"
|
version_endpoint = "VersionEndpoint"
|
||||||
technical_demo_banner = "TechnicalDemoBanner"
|
technical_demo_banner = "TechnicalDemoBanner"
|
||||||
per_user_setup = "PerUserSetup"
|
|
||||||
|
@ -37,6 +37,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
self.__db_names: dict[str, str] = {}
|
self.__db_names: dict[str, str] = {}
|
||||||
self.__foreign_tables: dict[str, tuple[str, str]] = {}
|
self.__foreign_tables: dict[str, tuple[str, str]] = {}
|
||||||
self.__foreign_table_keys: dict[str, str] = {}
|
self.__foreign_table_keys: dict[str, str] = {}
|
||||||
|
self.__foreign_dao: dict[str, "DataAccessObjectABC"] = {}
|
||||||
|
|
||||||
self.__date_attributes: set[str] = set()
|
self.__date_attributes: set[str] = set()
|
||||||
self.__ignored_attributes: set[str] = set()
|
self.__ignored_attributes: set[str] = set()
|
||||||
@ -108,6 +109,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
primary_attr: Attribute,
|
primary_attr: Attribute,
|
||||||
foreign_attr: Attribute,
|
foreign_attr: Attribute,
|
||||||
table_name: str,
|
table_name: str,
|
||||||
|
reference_dao: "DataAccessObjectABC" = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Add a reference to another table for the given attribute
|
Add a reference to another table for the given attribute
|
||||||
@ -115,6 +117,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
:param str primary_attr: Name of the primary key in the foreign object
|
:param str primary_attr: Name of the primary key in the foreign object
|
||||||
:param str foreign_attr: Name of the foreign key in the object
|
:param str foreign_attr: Name of the foreign key in the object
|
||||||
:param str table_name: Name of the table to reference
|
:param str table_name: Name of the table to reference
|
||||||
|
:param DataAccessObjectABC reference_dao: The data access object for the referenced table
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if isinstance(attr, property):
|
if isinstance(attr, property):
|
||||||
@ -131,6 +134,9 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
foreign_attr = foreign_attr.lower().replace("_", "")
|
foreign_attr = foreign_attr.lower().replace("_", "")
|
||||||
|
|
||||||
self.__foreign_table_keys[attr] = foreign_attr
|
self.__foreign_table_keys[attr] = foreign_attr
|
||||||
|
if reference_dao is not None:
|
||||||
|
self.__foreign_dao[attr] = reference_dao
|
||||||
|
|
||||||
if table_name == self._table_name:
|
if table_name == self._table_name:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -622,7 +628,27 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
external_table_deps.append(external_field)
|
external_table_deps.append(external_field)
|
||||||
parse_node(value, f"{external_field}.{key}")
|
parse_node(value, f"{external_field}.{key}")
|
||||||
elif parent_key in self.__foreign_table_keys:
|
elif parent_key in self.__foreign_table_keys:
|
||||||
parse_node({key: value}, self.__foreign_table_keys[parent_key])
|
if key in operators:
|
||||||
|
parse_node({key: value}, self.__foreign_table_keys[parent_key])
|
||||||
|
continue
|
||||||
|
|
||||||
|
if parent_key in self.__foreign_dao:
|
||||||
|
foreign_dao = self.__foreign_dao[parent_key]
|
||||||
|
if key in foreign_dao.__foreign_tables:
|
||||||
|
x = f"{self.__foreign_tables[parent_key][0]}.{foreign_dao.__foreign_table_keys[key]}"
|
||||||
|
parse_node(
|
||||||
|
value,
|
||||||
|
f"{self.__foreign_tables[parent_key][0]}.{foreign_dao.__foreign_table_keys[key]}",
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if parent_key in self.__foreign_tables:
|
||||||
|
parse_node(
|
||||||
|
value, f"{self.__foreign_tables[parent_key][0]}.{key}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
parse_node({parent_key: value})
|
||||||
elif key in operators:
|
elif key in operators:
|
||||||
operator = operators[key]
|
operator = operators[key]
|
||||||
if key == "contains" or key == "notContains":
|
if key == "contains" or key == "notContains":
|
||||||
@ -633,6 +659,23 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
value = f"{value}%"
|
value = f"{value}%"
|
||||||
elif key == "endsWith":
|
elif key == "endsWith":
|
||||||
value = f"%{value}"
|
value = f"%{value}"
|
||||||
|
elif key == "isNull" or key == "isNotNull":
|
||||||
|
is_null_value = (
|
||||||
|
value.get("equal", None)
|
||||||
|
if isinstance(value, dict)
|
||||||
|
else value
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_null_value is None:
|
||||||
|
operator = operators[key]
|
||||||
|
elif (key == "isNull" and is_null_value) or (
|
||||||
|
key == "isNotNull" and not is_null_value
|
||||||
|
):
|
||||||
|
operator = "IS NULL"
|
||||||
|
else:
|
||||||
|
operator = "IS NOT NULL"
|
||||||
|
|
||||||
|
conditions.append((parent_key, operator, None))
|
||||||
elif (key == "equal" or key == "notEqual") and value is None:
|
elif (key == "equal" or key == "notEqual") and value is None:
|
||||||
operator = operators["isNull"]
|
operator = operators["isNull"]
|
||||||
|
|
||||||
@ -641,6 +684,8 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
|||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
if key in self.__foreign_table_keys:
|
if key in self.__foreign_table_keys:
|
||||||
parse_node(value, key)
|
parse_node(value, key)
|
||||||
|
elif key in self.__db_names and parent_key is not None:
|
||||||
|
parse_node({f"{parent_key}": value})
|
||||||
elif key in self.__db_names:
|
elif key in self.__db_names:
|
||||||
parse_node(value, self.__db_names[key])
|
parse_node(value, self.__db_names[key])
|
||||||
else:
|
else:
|
||||||
|
@ -3,11 +3,12 @@ from typing import Optional
|
|||||||
|
|
||||||
from async_property import async_property
|
from async_property import async_property
|
||||||
|
|
||||||
|
from core.database.abc.db_join_model_abc import DbJoinModelABC
|
||||||
from core.typing import SerialId
|
from core.typing import SerialId
|
||||||
from core.database.abc.db_model_abc import DbModelABC
|
from core.database.abc.db_model_abc import DbModelABC
|
||||||
|
|
||||||
|
|
||||||
class RoleUser(DbModelABC):
|
class RoleUser(DbJoinModelABC):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
id: SerialId,
|
id: SerialId,
|
||||||
@ -18,7 +19,9 @@ class RoleUser(DbModelABC):
|
|||||||
created: Optional[datetime] = None,
|
created: Optional[datetime] = None,
|
||||||
updated: Optional[datetime] = None,
|
updated: Optional[datetime] = None,
|
||||||
):
|
):
|
||||||
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
DbJoinModelABC.__init__(
|
||||||
|
self, id, role_id, user_id, deleted, editor_id, created, updated
|
||||||
|
)
|
||||||
self._role_id = role_id
|
self._role_id = role_id
|
||||||
self._user_id = user_id
|
self._user_id = user_id
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class Group(DbModelABC):
|
|||||||
self,
|
self,
|
||||||
id: SerialId,
|
id: SerialId,
|
||||||
name: str,
|
name: str,
|
||||||
user_id: Optional[SerialId] = None,
|
user_space_id: Optional[SerialId] = None,
|
||||||
deleted: bool = False,
|
deleted: bool = False,
|
||||||
editor_id: Optional[SerialId] = None,
|
editor_id: Optional[SerialId] = None,
|
||||||
created: Optional[datetime] = None,
|
created: Optional[datetime] = None,
|
||||||
@ -20,7 +20,7 @@ class Group(DbModelABC):
|
|||||||
):
|
):
|
||||||
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
||||||
self._name = name
|
self._name = name
|
||||||
self._user_id = user_id
|
self._user_space_id = user_space_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -31,15 +31,14 @@ class Group(DbModelABC):
|
|||||||
self._name = value
|
self._name = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_id(self) -> Optional[SerialId]:
|
def user_space_id(self) -> Optional[SerialId]:
|
||||||
return self._user_id
|
return self._user_space_id
|
||||||
|
|
||||||
@async_property
|
@async_property
|
||||||
async def user(self):
|
async def user_space(self):
|
||||||
if self._user_id is None:
|
if self._user_space_id is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
from data.schemas.administration.user_dao import userDao
|
from data.schemas.public.user_space_dao import userSpaceDao
|
||||||
|
|
||||||
user = await userDao.get_by_id(self.user_id)
|
return await userSpaceDao.get_by_id(self._user_space_id)
|
||||||
return user
|
|
||||||
|
@ -11,8 +11,8 @@ class GroupDao(DbModelDaoABC[Group]):
|
|||||||
DbModelDaoABC.__init__(self, __name__, Group, "public.groups")
|
DbModelDaoABC.__init__(self, __name__, Group, "public.groups")
|
||||||
self.attribute(Group.name, str)
|
self.attribute(Group.name, str)
|
||||||
|
|
||||||
self.attribute(Group.user_id, int)
|
self.attribute(Group.user_space_id, int)
|
||||||
self.reference("user", "id", Group.user_id, "administration.users")
|
self.reference("userspace", "id", Group.user_space_id, "public.user_spaces")
|
||||||
|
|
||||||
async def get_by_name(self, name: str) -> Group:
|
async def get_by_name(self, name: str) -> Group:
|
||||||
result = await self._db.select_map(
|
result = await self._db.select_map(
|
||||||
|
@ -18,7 +18,7 @@ class ShortUrl(DbModelABC):
|
|||||||
group_id: Optional[SerialId],
|
group_id: Optional[SerialId],
|
||||||
domain_id: Optional[SerialId],
|
domain_id: Optional[SerialId],
|
||||||
loading_screen: Optional[str] = None,
|
loading_screen: Optional[str] = None,
|
||||||
user_id: Optional[SerialId] = None,
|
user_space_id: Optional[SerialId] = None,
|
||||||
deleted: bool = False,
|
deleted: bool = False,
|
||||||
editor_id: Optional[SerialId] = None,
|
editor_id: Optional[SerialId] = None,
|
||||||
created: Optional[datetime] = None,
|
created: Optional[datetime] = None,
|
||||||
@ -35,7 +35,7 @@ class ShortUrl(DbModelABC):
|
|||||||
loading_screen = False
|
loading_screen = False
|
||||||
self._loading_screen = loading_screen
|
self._loading_screen = loading_screen
|
||||||
|
|
||||||
self._user_id = user_id
|
self._user_space_id = user_space_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def short_url(self) -> str:
|
def short_url(self) -> str:
|
||||||
@ -110,18 +110,17 @@ class ShortUrl(DbModelABC):
|
|||||||
self._loading_screen = value
|
self._loading_screen = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_id(self) -> Optional[SerialId]:
|
def user_space_id(self) -> Optional[SerialId]:
|
||||||
return self._user_id
|
return self._user_space_id
|
||||||
|
|
||||||
@async_property
|
@async_property
|
||||||
async def user(self):
|
async def user_space(self):
|
||||||
if self._user_id is None:
|
if self._user_space_id is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
from data.schemas.administration.user_dao import userDao
|
from data.schemas.public.user_space_dao import userSpaceDao
|
||||||
|
|
||||||
user = await userDao.get_by_id(self.user_id)
|
return await userSpaceDao.get_by_id(self._user_space_id)
|
||||||
return user
|
|
||||||
|
|
||||||
def to_dto(self) -> dict:
|
def to_dto(self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
@ -13,13 +13,17 @@ class ShortUrlDao(DbModelDaoABC[ShortUrl]):
|
|||||||
self.attribute(ShortUrl.target_url, str)
|
self.attribute(ShortUrl.target_url, str)
|
||||||
self.attribute(ShortUrl.description, str)
|
self.attribute(ShortUrl.description, str)
|
||||||
self.attribute(ShortUrl.group_id, int)
|
self.attribute(ShortUrl.group_id, int)
|
||||||
self.reference("group", "id", ShortUrl.group_id, "public.groups")
|
from data.schemas.public.group_dao import groupDao
|
||||||
|
|
||||||
|
self.reference("group", "id", ShortUrl.group_id, "public.groups", groupDao)
|
||||||
self.attribute(ShortUrl.domain_id, int)
|
self.attribute(ShortUrl.domain_id, int)
|
||||||
self.reference("domain", "id", ShortUrl.domain_id, "public.domains")
|
from data.schemas.public.domain_dao import domainDao
|
||||||
|
|
||||||
|
self.reference("domain", "id", ShortUrl.domain_id, "public.domains", domainDao)
|
||||||
self.attribute(ShortUrl.loading_screen, bool)
|
self.attribute(ShortUrl.loading_screen, bool)
|
||||||
|
|
||||||
self.attribute(ShortUrl.user_id, int)
|
self.attribute(ShortUrl.user_space_id, int)
|
||||||
self.reference("user", "id", ShortUrl.user_id, "administration.users")
|
self.reference("userspace", "id", ShortUrl.user_space_id, "public.user_spaces")
|
||||||
|
|
||||||
|
|
||||||
shortUrlDao = ShortUrlDao()
|
shortUrlDao = ShortUrlDao()
|
||||||
|
45
api/src/data/schemas/public/user_space.py
Normal file
45
api/src/data/schemas/public/user_space.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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 UserSpace(DbModelABC):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: SerialId,
|
||||||
|
name: str,
|
||||||
|
owner_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._name = name
|
||||||
|
self._owner_id = owner_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@name.setter
|
||||||
|
def name(self, value: str):
|
||||||
|
self._name = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def owner_id(self) -> SerialId:
|
||||||
|
return self._owner_id
|
||||||
|
|
||||||
|
@owner_id.setter
|
||||||
|
def owner_id(self, value: SerialId):
|
||||||
|
self._owner_id = value
|
||||||
|
|
||||||
|
@async_property
|
||||||
|
async def owner(self):
|
||||||
|
from data.schemas.administration.user_dao import userDao
|
||||||
|
|
||||||
|
return await userDao.get_by_id(self._owner_id)
|
49
api/src/data/schemas/public/user_space_dao.py
Normal file
49
api/src/data/schemas/public/user_space_dao.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from core.database.abc.db_model_dao_abc import DbModelDaoABC
|
||||||
|
from core.logger import DBLogger
|
||||||
|
from data.schemas.public.user_space import UserSpace
|
||||||
|
|
||||||
|
logger = DBLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceDao(DbModelDaoABC[UserSpace]):
|
||||||
|
def __init__(self):
|
||||||
|
DbModelDaoABC.__init__(self, __name__, UserSpace, "public.user_spaces")
|
||||||
|
self.attribute(UserSpace.name, str)
|
||||||
|
self.attribute(UserSpace.owner_id, int)
|
||||||
|
|
||||||
|
async def get_by_name(self, name: str) -> UserSpace:
|
||||||
|
result = await self._db.select_map(
|
||||||
|
f"SELECT * FROM {self._table_name} WHERE Name = '{name}'"
|
||||||
|
)
|
||||||
|
return self.to_object(result[0])
|
||||||
|
|
||||||
|
async def get_users(self, user_space_id: int) -> list:
|
||||||
|
result = await self._db.select_map(
|
||||||
|
f"""
|
||||||
|
SELECT u.*
|
||||||
|
FROM administration.users u
|
||||||
|
JOIN public.user_spaces_users usu ON u.id = usu.userId
|
||||||
|
WHERE usu.userSpaceId = {user_space_id}
|
||||||
|
AND usu.deleted = FALSE
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
from data.schemas.administration.user_dao import userDao
|
||||||
|
|
||||||
|
return [userDao.to_object(x) for x in result]
|
||||||
|
|
||||||
|
async def get_assigned_by_user_id(self, user_id: int):
|
||||||
|
result = await self._db.select_map(
|
||||||
|
f"""
|
||||||
|
SELECT DISTINCT us.*
|
||||||
|
FROM public.user_spaces us
|
||||||
|
LEFT JOIN public.user_spaces_users usu ON us.id = usu.userSpaceId
|
||||||
|
WHERE (usu.userId = {user_id} OR us.ownerId = {user_id})
|
||||||
|
AND us.deleted = FALSE;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
return [self.to_object(x) for x in result]
|
||||||
|
|
||||||
|
|
||||||
|
userSpaceDao = UserSpaceDao()
|
46
api/src/data/schemas/public/user_space_user.py
Normal file
46
api/src/data/schemas/public/user_space_user.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from async_property import async_property
|
||||||
|
|
||||||
|
from core.database.abc.db_join_model_abc import DbJoinModelABC
|
||||||
|
from core.database.abc.db_model_abc import DbModelABC
|
||||||
|
from core.typing import SerialId
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceUser(DbJoinModelABC):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: SerialId,
|
||||||
|
user_space_id: SerialId,
|
||||||
|
user_id: SerialId,
|
||||||
|
deleted: bool = False,
|
||||||
|
editor_id: Optional[SerialId] = None,
|
||||||
|
created: Optional[datetime] = None,
|
||||||
|
updated: Optional[datetime] = None,
|
||||||
|
):
|
||||||
|
DbJoinModelABC.__init__(
|
||||||
|
self, id, user_space_id, user_id, deleted, editor_id, created, updated
|
||||||
|
)
|
||||||
|
self._user_space_id = user_space_id
|
||||||
|
self._user_id = user_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_space_id(self) -> SerialId:
|
||||||
|
return self._user_space_id
|
||||||
|
|
||||||
|
@async_property
|
||||||
|
async def user_space(self):
|
||||||
|
from data.schemas.public.user_space_dao import userSpaceDao
|
||||||
|
|
||||||
|
return await userSpaceDao.get_by_id(self._user_space_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_id(self) -> SerialId:
|
||||||
|
return self._user_id
|
||||||
|
|
||||||
|
@async_property
|
||||||
|
async def user(self):
|
||||||
|
from data.schemas.administration.user_dao import userDao
|
||||||
|
|
||||||
|
return await userDao.get_by_id(self._user_id)
|
35
api/src/data/schemas/public/user_space_user_dao.py
Normal file
35
api/src/data/schemas/public/user_space_user_dao.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from core.database.abc.db_model_dao_abc import DbModelDaoABC
|
||||||
|
from core.logger import DBLogger
|
||||||
|
from data.schemas.public.user_space_user import UserSpaceUser
|
||||||
|
|
||||||
|
logger = DBLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSpaceUserDao(DbModelDaoABC[UserSpaceUser]):
|
||||||
|
def __init__(self):
|
||||||
|
DbModelDaoABC.__init__(
|
||||||
|
self, __name__, UserSpaceUser, "public.user_spaces_users"
|
||||||
|
)
|
||||||
|
self.attribute(UserSpaceUser.user_space_id, str)
|
||||||
|
self.attribute(UserSpaceUser.user_id, str)
|
||||||
|
|
||||||
|
async def get_by_user_space_id(
|
||||||
|
self, user_space_id: str, with_deleted=False
|
||||||
|
) -> list[UserSpaceUser]:
|
||||||
|
f = [{UserSpaceUser.user_space_id: user_space_id}]
|
||||||
|
if not with_deleted:
|
||||||
|
f.append({UserSpaceUser.deleted: False})
|
||||||
|
|
||||||
|
return await self.find_by(f)
|
||||||
|
|
||||||
|
async def get_by_user_id(
|
||||||
|
self, user_id: str, with_deleted=False
|
||||||
|
) -> list[UserSpaceUser]:
|
||||||
|
f = [{UserSpaceUser.user_id: user_id}]
|
||||||
|
if not with_deleted:
|
||||||
|
f.append({UserSpaceUser.deleted: False})
|
||||||
|
|
||||||
|
return await self.find_by(f)
|
||||||
|
|
||||||
|
|
||||||
|
userSpaceUserDao = UserSpaceUserDao()
|
79
api/src/data/scripts/2025-04-30-21-50-user-spaces2.sql
Normal file
79
api/src/data/scripts/2025-04-30-21-50-user-spaces2.sql
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS public.user_spaces
|
||||||
|
(
|
||||||
|
Id SERIAL PRIMARY KEY,
|
||||||
|
Name VARCHAR(255) NOT NULL,
|
||||||
|
OwnerId INT NOT NULL REFERENCES administration.users (Id),
|
||||||
|
-- for history
|
||||||
|
Deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
EditorId INT NULL REFERENCES administration.users (Id),
|
||||||
|
Created timestamptz NOT NULL DEFAULT NOW(),
|
||||||
|
Updated timestamptz NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.user_spaces_history
|
||||||
|
(
|
||||||
|
LIKE public.user_spaces
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER user_spaces_history_trigger
|
||||||
|
BEFORE INSERT OR UPDATE OR DELETE
|
||||||
|
ON public.user_spaces
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.history_trigger_function();
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.user_spaces_users
|
||||||
|
(
|
||||||
|
Id SERIAL PRIMARY KEY,
|
||||||
|
UserSpaceId INT NOT NULL REFERENCES public.user_spaces (Id),
|
||||||
|
UserId INT NOT NULL REFERENCES administration.users (Id),
|
||||||
|
-- for history
|
||||||
|
Deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
EditorId INT NULL REFERENCES administration.users (Id),
|
||||||
|
Created timestamptz NOT NULL DEFAULT NOW(),
|
||||||
|
Updated timestamptz NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.user_spaces_users_history
|
||||||
|
(
|
||||||
|
LIKE public.user_spaces_users
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER user_spaces_users_history_trigger
|
||||||
|
BEFORE INSERT OR UPDATE OR DELETE
|
||||||
|
ON public.user_spaces_users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.history_trigger_function();
|
||||||
|
|
||||||
|
-- add user spaces
|
||||||
|
INSERT INTO public.user_spaces (Name, OwnerId)
|
||||||
|
VALUES ('Default', (SELECT id FROM administration.users ORDER BY id LIMIT 1));
|
||||||
|
|
||||||
|
INSERT INTO public.user_spaces_users (UserSpaceId, UserId)
|
||||||
|
SELECT 1 AS UserSpaceId, u.id AS UserId
|
||||||
|
FROM administration.users u
|
||||||
|
WHERE u.deleted = FALSE;
|
||||||
|
|
||||||
|
-- change other tables
|
||||||
|
ALTER TABLE public.groups
|
||||||
|
ADD COLUMN IF NOT EXISTS UserSpaceId INT NOT NULL REFERENCES public.user_spaces (Id) default 1;
|
||||||
|
|
||||||
|
ALTER TABLE public.groups
|
||||||
|
DROP COLUMN IF EXISTS UserId;
|
||||||
|
|
||||||
|
ALTER TABLE public.groups_history
|
||||||
|
ADD COLUMN IF NOT EXISTS UserSpaceId INT NOT NULL REFERENCES public.user_spaces (Id) default 1;
|
||||||
|
|
||||||
|
ALTER TABLE public.groups_history
|
||||||
|
DROP COLUMN IF EXISTS UserId;
|
||||||
|
|
||||||
|
ALTER TABLE public.short_urls
|
||||||
|
ADD COLUMN IF NOT EXISTS UserSpaceId INT NOT NULL REFERENCES public.user_spaces (Id) default 1;
|
||||||
|
|
||||||
|
ALTER TABLE public.short_urls
|
||||||
|
DROP COLUMN IF EXISTS UserId;
|
||||||
|
|
||||||
|
ALTER TABLE public.short_urls_history
|
||||||
|
ADD COLUMN IF NOT EXISTS UserSpaceId INT NOT NULL REFERENCES public.user_spaces (Id) default 1;
|
||||||
|
|
||||||
|
ALTER TABLE public.short_urls_history
|
||||||
|
DROP COLUMN IF EXISTS UserId;
|
@ -26,6 +26,18 @@ class Permissions(Enum):
|
|||||||
settings = "settings"
|
settings = "settings"
|
||||||
settings_update = "settings.update"
|
settings_update = "settings.update"
|
||||||
|
|
||||||
|
# domains
|
||||||
|
domains = "domains"
|
||||||
|
domains_create = "domains.create"
|
||||||
|
domains_update = "domains.update"
|
||||||
|
domains_delete = "domains.delete"
|
||||||
|
|
||||||
|
# user spaces
|
||||||
|
user_spaces = "user_spaces"
|
||||||
|
user_spaces_create = "user_spaces.create"
|
||||||
|
user_spaces_update = "user_spaces.update"
|
||||||
|
user_spaces_delete = "user_spaces.delete"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Permissions
|
Permissions
|
||||||
"""
|
"""
|
||||||
@ -38,12 +50,6 @@ class Permissions(Enum):
|
|||||||
"""
|
"""
|
||||||
Public
|
Public
|
||||||
"""
|
"""
|
||||||
# domains
|
|
||||||
domains = "domains"
|
|
||||||
domains_create = "domains.create"
|
|
||||||
domains_update = "domains.update"
|
|
||||||
domains_delete = "domains.delete"
|
|
||||||
|
|
||||||
# groups
|
# groups
|
||||||
groups = "groups"
|
groups = "groups"
|
||||||
groups_create = "groups.create"
|
groups_create = "groups.create"
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<p>home works!</p>
|
|
@ -26,8 +26,12 @@ export abstract class FormPageBase<
|
|||||||
protected filterService = inject(FilterService);
|
protected filterService = inject(FilterService);
|
||||||
protected dataService = inject(PageDataService) as S;
|
protected dataService = inject(PageDataService) as S;
|
||||||
|
|
||||||
protected constructor(idKey: string = 'id') {
|
private backRoute: string = '..';
|
||||||
|
|
||||||
|
protected constructor(idKey: string = 'id', backRoute: string = '..') {
|
||||||
const id = this.route.snapshot.params[idKey];
|
const id = this.route.snapshot.params[idKey];
|
||||||
|
this.backRoute = backRoute;
|
||||||
|
|
||||||
this.validateRoute(id);
|
this.validateRoute(id);
|
||||||
|
|
||||||
this.buildForm();
|
this.buildForm();
|
||||||
@ -46,7 +50,7 @@ export abstract class FormPageBase<
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
const backRoute = this.nodeId ? '../..' : '..';
|
const backRoute = this.nodeId ? `${this.backRoute}/..` : this.backRoute;
|
||||||
|
|
||||||
this.router.navigate([backRoute], { relativeTo: this.route }).then(() => {
|
this.router.navigate([backRoute], { relativeTo: this.route }).then(() => {
|
||||||
this.filterService.onLoad.emit();
|
this.filterService.onLoad.emit();
|
||||||
|
@ -4,7 +4,6 @@ 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 { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
|
||||||
|
|
||||||
const log = new Logger('PermissionGuard');
|
const log = new Logger('PermissionGuard');
|
||||||
|
|
||||||
@ -15,18 +14,11 @@ export class PermissionGuard {
|
|||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private auth: AuthService,
|
private auth: AuthService
|
||||||
private features: FeatureFlagService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
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 checkByPerUserSetup = route.data['checkByPerUserSetup'] as boolean;
|
|
||||||
|
|
||||||
const isPerUserSetup = await this.features.get('PerUserSetup');
|
|
||||||
if (checkByPerUserSetup && isPerUserSetup) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!permissions || permissions.length === 0) {
|
if (!permissions || permissions.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
|
19
web/src/app/model/entities/user-space.ts
Normal file
19
web/src/app/model/entities/user-space.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { User } from 'src/app/model/auth/user';
|
||||||
|
import { DbModelWithHistory } from 'src/app/model/entities/db-model';
|
||||||
|
|
||||||
|
export interface UserSpace extends DbModelWithHistory {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
users?: User[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserSpaceCreateInput {
|
||||||
|
name?: string;
|
||||||
|
users?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserSpaceUpdateInput {
|
||||||
|
id: number;
|
||||||
|
name?: string;
|
||||||
|
users?: number[];
|
||||||
|
}
|
@ -7,13 +7,9 @@ import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
|||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'domains',
|
path: '',
|
||||||
loadChildren: () =>
|
pathMatch: 'full',
|
||||||
import('src/app/modules/admin/domains/domains.module').then(
|
redirectTo: 'urls',
|
||||||
m => m.DomainsModule
|
|
||||||
),
|
|
||||||
canActivate: [PermissionGuard],
|
|
||||||
data: { permissions: [PermissionsEnum.domains] },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'groups',
|
path: 'groups',
|
||||||
@ -22,7 +18,7 @@ const routes: Routes = [
|
|||||||
m => m.GroupsModule
|
m => m.GroupsModule
|
||||||
),
|
),
|
||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: { permissions: [PermissionsEnum.groups], checkByPerUserSetup: true },
|
data: { permissions: [PermissionsEnum.groups] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'urls',
|
path: 'urls',
|
||||||
@ -36,9 +32,15 @@ const routes: Routes = [
|
|||||||
PermissionsEnum.shortUrls,
|
PermissionsEnum.shortUrls,
|
||||||
PermissionsEnum.shortUrlsByAssignment,
|
PermissionsEnum.shortUrlsByAssignment,
|
||||||
],
|
],
|
||||||
checkByPerUserSetup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'rooms',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('src/app/modules/admin/user-spaces/user-spaces.module').then(
|
||||||
|
m => m.UserSpacesModule
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'administration',
|
path: 'administration',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
@ -11,6 +11,15 @@ const routes: Routes = [
|
|||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
redirectTo: 'users',
|
redirectTo: 'users',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'domains',
|
||||||
|
loadChildren: () =>
|
||||||
|
import(
|
||||||
|
'src/app/modules/admin/administration/domains/domains.module'
|
||||||
|
).then(m => m.DomainsModule),
|
||||||
|
canActivate: [PermissionGuard],
|
||||||
|
data: { permissions: [PermissionsEnum.domains] },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'users',
|
path: 'users',
|
||||||
title: 'Users | Maxlan',
|
title: 'Users | Maxlan',
|
||||||
|
@ -4,11 +4,11 @@ import { SharedModule } from 'src/app/modules/shared/shared.module';
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { PermissionGuard } from 'src/app/core/guard/permission.guard';
|
import { PermissionGuard } from 'src/app/core/guard/permission.guard';
|
||||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||||
import { DomainsPage } from 'src/app/modules/admin/domains/domains.page';
|
import { DomainsPage } from 'src/app/modules/admin/administration/domains/domains.page';
|
||||||
import { DomainFormPageComponent } from 'src/app/modules/admin/domains/form-page/domain-form-page.component';
|
import { DomainFormPageComponent } from 'src/app/modules/admin/administration/domains/form-page/domain-form-page.component';
|
||||||
import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service';
|
import { DomainsDataService } from 'src/app/modules/admin/administration/domains/domains.data.service';
|
||||||
import { DomainsColumns } from 'src/app/modules/admin/domains/domains.columns';
|
import { DomainsColumns } from 'src/app/modules/admin/administration/domains/domains.columns';
|
||||||
import { HistoryComponent } from 'src/app/modules/admin/domains/history/history.component';
|
import { HistoryComponent } from 'src/app/modules/admin/administration/domains/history/history.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
@ -3,9 +3,9 @@ import { PageBase } from 'src/app/core/base/page-base';
|
|||||||
import { ToastService } from 'src/app/service/toast.service';
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
|
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
|
||||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||||
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';
|
import { Domain } from 'src/app/model/entities/domain';
|
||||||
|
import { DomainsColumns } from 'src/app/modules/admin/administration/domains/domains.columns';
|
||||||
|
import { DomainsDataService } from 'src/app/modules/admin/administration/domains/domains.data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-domains',
|
selector: 'app-domains',
|
@ -7,7 +7,7 @@ import {
|
|||||||
DomainCreateInput,
|
DomainCreateInput,
|
||||||
DomainUpdateInput,
|
DomainUpdateInput,
|
||||||
} from 'src/app/model/entities/domain';
|
} from 'src/app/model/entities/domain';
|
||||||
import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service';
|
import { DomainsDataService } from 'src/app/modules/admin/administration/domains/domains.data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-domain-form-page',
|
selector: 'app-domain-form-page',
|
@ -26,6 +26,8 @@ export class UsersPage extends PageBase<User, UsersDataService, UsersColumns> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onInit(): Promise<void> {}
|
||||||
|
|
||||||
load(silent?: boolean): void {
|
load(silent?: boolean): void {
|
||||||
if (!silent) this.loading = true;
|
if (!silent) this.loading = true;
|
||||||
this.dataService
|
this.dataService
|
||||||
|
@ -27,14 +27,13 @@
|
|||||||
type="text"
|
type="text"
|
||||||
formControlName="name"/>
|
formControlName="name"/>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!isPerUserSetup" class="divider"></div>
|
<div class="divider"></div>
|
||||||
<p-multiSelect
|
<p-multiSelect
|
||||||
*ngIf="!isPerUserSetup"
|
|
||||||
[options]="roles"
|
[options]="roles"
|
||||||
formControlName="roles"
|
formControlName="roles"
|
||||||
optionLabel="name"
|
optionLabel="name"
|
||||||
placeholder="{{ 'user.assign_roles' | translate }}"
|
placeholder="{{ 'user.assign_roles' | translate }}"
|
||||||
display="chip"
|
display="chip"
|
||||||
[showClear]="true" />
|
[showClear]="true"/>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-form-page>
|
</app-form-page>
|
||||||
|
@ -17,17 +17,13 @@ import { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
|||||||
templateUrl: './group-form-page.component.html',
|
templateUrl: './group-form-page.component.html',
|
||||||
styleUrl: './group-form-page.component.scss',
|
styleUrl: './group-form-page.component.scss',
|
||||||
})
|
})
|
||||||
export class GroupFormPageComponent
|
export class GroupFormPageComponent extends FormPageBase<
|
||||||
extends FormPageBase<
|
Group,
|
||||||
Group,
|
GroupCreateInput,
|
||||||
GroupCreateInput,
|
GroupUpdateInput,
|
||||||
GroupUpdateInput,
|
GroupsDataService
|
||||||
GroupsDataService
|
> {
|
||||||
>
|
|
||||||
implements OnInit
|
|
||||||
{
|
|
||||||
roles: Role[] = [];
|
roles: Role[] = [];
|
||||||
isPerUserSetup = true;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private features: FeatureFlagService,
|
private features: FeatureFlagService,
|
||||||
@ -35,15 +31,9 @@ export class GroupFormPageComponent
|
|||||||
private cds: CommonDataService
|
private cds: CommonDataService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
this.cds.getAllRoles().subscribe(roles => {
|
||||||
|
this.roles = roles;
|
||||||
async ngOnInit() {
|
});
|
||||||
this.isPerUserSetup = await this.features.get('PerUserSetup');
|
|
||||||
if (!this.isPerUserSetup) {
|
|
||||||
this.cds.getAllRoles().subscribe(roles => {
|
|
||||||
this.roles = roles;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.nodeId) {
|
if (!this.nodeId) {
|
||||||
this.node = this.new();
|
this.node = this.new();
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
GroupUpdateInput,
|
GroupUpdateInput,
|
||||||
} from 'src/app/model/entities/group';
|
} from 'src/app/model/entities/group';
|
||||||
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
|
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
|
||||||
|
import { SidebarService } from 'src/app/service/sidebar.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GroupsDataService
|
export class GroupsDataService
|
||||||
@ -35,7 +36,8 @@ export class GroupsDataService
|
|||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private spinner: SpinnerService,
|
private spinner: SpinnerService,
|
||||||
private apollo: Apollo
|
private apollo: Apollo,
|
||||||
|
private sidebarService: SidebarService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -74,7 +76,14 @@ export class GroupsDataService
|
|||||||
${DB_MODEL_FRAGMENT}
|
${DB_MODEL_FRAGMENT}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: filter,
|
filter: [
|
||||||
|
{
|
||||||
|
userSpace: {
|
||||||
|
id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(filter ?? []),
|
||||||
|
],
|
||||||
sort: sort,
|
sort: sort,
|
||||||
skip: skip,
|
skip: skip,
|
||||||
take: take,
|
take: take,
|
||||||
@ -186,6 +195,7 @@ export class GroupsDataService
|
|||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
userSpaceId: this.sidebarService.selectedUserSpace$.value?.id,
|
||||||
name: object.name,
|
name: object.name,
|
||||||
roles: object.roles?.map(x => x.id),
|
roles: object.roles?.map(x => x.id),
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,6 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.groupsCreate],
|
permissions: [PermissionsEnum.groupsCreate],
|
||||||
checkByPerUserSetup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -31,7 +30,6 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.groupsUpdate],
|
permissions: [PermissionsEnum.groupsUpdate],
|
||||||
checkByPerUserSetup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,7 +38,6 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.groups],
|
permissions: [PermissionsEnum.groups],
|
||||||
checkByPerUserSetup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1,68 +1,41 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { PageBase } from 'src/app/core/base/page-base';
|
import { PageBase } from 'src/app/core/base/page-base';
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.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 { Group } from 'src/app/model/entities/group';
|
||||||
import { GroupsDataService } from 'src/app/modules/admin/groups/groups.data.service';
|
import { GroupsDataService } from 'src/app/modules/admin/groups/groups.data.service';
|
||||||
import { GroupsColumns } from 'src/app/modules/admin/groups/groups.columns';
|
import { GroupsColumns } from 'src/app/modules/admin/groups/groups.columns';
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { ConfigService } from 'src/app/service/config.service';
|
import { SidebarService } from 'src/app/service/sidebar.service';
|
||||||
import { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-groups',
|
selector: 'app-groups',
|
||||||
templateUrl: './groups.page.html',
|
templateUrl: './groups.page.html',
|
||||||
styleUrl: './groups.page.scss',
|
styleUrl: './groups.page.scss',
|
||||||
})
|
})
|
||||||
export class GroupsPage
|
export class GroupsPage extends PageBase<
|
||||||
extends PageBase<Group, GroupsDataService, GroupsColumns>
|
Group,
|
||||||
implements OnInit
|
GroupsDataService,
|
||||||
{
|
GroupsColumns
|
||||||
|
> {
|
||||||
constructor(
|
constructor(
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private confirmation: ConfirmationDialogService,
|
private confirmation: ConfirmationDialogService,
|
||||||
private auth: AuthService,
|
private sidebar: SidebarService
|
||||||
private config: ConfigService,
|
|
||||||
private features: FeatureFlagService
|
|
||||||
) {
|
) {
|
||||||
super(true);
|
super(true, {
|
||||||
}
|
read: [],
|
||||||
|
create: [],
|
||||||
|
update: [],
|
||||||
|
delete: [],
|
||||||
|
restore: [],
|
||||||
|
});
|
||||||
|
|
||||||
async ngOnInit() {
|
this.sidebar.selectedUserSpace$
|
||||||
this.requiredPermissions = {
|
.pipe(takeUntil(this.unsubscribe$))
|
||||||
read: (await this.features.get('PerUserSetup'))
|
.subscribe(() => {
|
||||||
? []
|
this.load(true);
|
||||||
: [PermissionsEnum.groups],
|
});
|
||||||
create: (await this.features.get('PerUserSetup'))
|
|
||||||
? []
|
|
||||||
: (await this.auth.hasAnyPermissionLazy(
|
|
||||||
this.requiredPermissions.create ?? []
|
|
||||||
))
|
|
||||||
? (this.requiredPermissions.create ?? [])
|
|
||||||
: [],
|
|
||||||
update: (await this.features.get('PerUserSetup'))
|
|
||||||
? []
|
|
||||||
: (await this.auth.hasAnyPermissionLazy(
|
|
||||||
this.requiredPermissions.update ?? []
|
|
||||||
))
|
|
||||||
? (this.requiredPermissions.update ?? [])
|
|
||||||
: [],
|
|
||||||
delete: (await this.features.get('PerUserSetup'))
|
|
||||||
? []
|
|
||||||
: (await this.auth.hasAnyPermissionLazy(
|
|
||||||
this.requiredPermissions.delete ?? []
|
|
||||||
))
|
|
||||||
? (this.requiredPermissions.delete ?? [])
|
|
||||||
: [],
|
|
||||||
restore: (await this.features.get('PerUserSetup'))
|
|
||||||
? []
|
|
||||||
: (await this.auth.hasAnyPermissionLazy(
|
|
||||||
this.requiredPermissions.restore ?? []
|
|
||||||
))
|
|
||||||
? (this.requiredPermissions.restore ?? [])
|
|
||||||
: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
load(silent?: boolean): void {
|
load(silent?: boolean): void {
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
></p-dropdown>
|
></p-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-page-input" *ngIf="!isPerUserSetup">
|
<div class="form-page-input">
|
||||||
<p class="label">{{ 'common.domain' | translate }}</p>
|
<p class="label">{{ 'common.domain' | translate }}</p>
|
||||||
<div
|
<div
|
||||||
class="value">
|
class="value">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
import { FormPageBase } from 'src/app/core/base/form-page-base';
|
import { FormPageBase } from 'src/app/core/base/form-page-base';
|
||||||
@ -10,45 +10,29 @@ import {
|
|||||||
import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
|
import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
|
||||||
import { Group } from 'src/app/model/entities/group';
|
import { Group } from 'src/app/model/entities/group';
|
||||||
import { Domain } from 'src/app/model/entities/domain';
|
import { Domain } from 'src/app/model/entities/domain';
|
||||||
import { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-short-url-form-page',
|
selector: 'app-short-url-form-page',
|
||||||
templateUrl: './short-url-form-page.component.html',
|
templateUrl: './short-url-form-page.component.html',
|
||||||
styleUrl: './short-url-form-page.component.scss',
|
styleUrl: './short-url-form-page.component.scss',
|
||||||
})
|
})
|
||||||
export class ShortUrlFormPageComponent
|
export class ShortUrlFormPageComponent extends FormPageBase<
|
||||||
extends FormPageBase<
|
ShortUrl,
|
||||||
ShortUrl,
|
ShortUrlCreateInput,
|
||||||
ShortUrlCreateInput,
|
ShortUrlUpdateInput,
|
||||||
ShortUrlUpdateInput,
|
ShortUrlsDataService
|
||||||
ShortUrlsDataService
|
> {
|
||||||
>
|
|
||||||
implements OnInit
|
|
||||||
{
|
|
||||||
groups: Group[] = [];
|
groups: Group[] = [];
|
||||||
domains: Domain[] = [];
|
domains: Domain[] = [];
|
||||||
|
|
||||||
isPerUserSetup = true;
|
constructor(private toast: ToastService) {
|
||||||
|
|
||||||
constructor(
|
|
||||||
private features: FeatureFlagService,
|
|
||||||
private toast: ToastService
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this.dataService.getAllGroups().subscribe(groups => {
|
this.dataService.getAllAvailableGroups().subscribe(groups => {
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
});
|
});
|
||||||
}
|
this.dataService.getAllDomains().subscribe(domains => {
|
||||||
|
this.domains = domains;
|
||||||
async ngOnInit() {
|
});
|
||||||
this.isPerUserSetup = await this.features.get('PerUserSetup');
|
|
||||||
|
|
||||||
if (!this.isPerUserSetup) {
|
|
||||||
this.dataService.getAllDomains().subscribe(domains => {
|
|
||||||
this.domains = domains;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.nodeId) {
|
if (!this.nodeId) {
|
||||||
this.node = this.new();
|
this.node = this.new();
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
import { Group } from 'src/app/model/entities/group';
|
import { Group } from 'src/app/model/entities/group';
|
||||||
import { Domain } from 'src/app/model/entities/domain';
|
import { Domain } from 'src/app/model/entities/domain';
|
||||||
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
|
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
|
||||||
|
import { SidebarService } from 'src/app/service/sidebar.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ShortUrlsDataService
|
export class ShortUrlsDataService
|
||||||
@ -37,7 +38,8 @@ export class ShortUrlsDataService
|
|||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private spinner: SpinnerService,
|
private spinner: SpinnerService,
|
||||||
private apollo: Apollo
|
private apollo: Apollo,
|
||||||
|
private sidebarService: SidebarService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -76,7 +78,22 @@ export class ShortUrlsDataService
|
|||||||
${DB_MODEL_FRAGMENT}
|
${DB_MODEL_FRAGMENT}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: [{ group: { deleted: { equal: false } } }, ...(filter ?? [])],
|
filter: [
|
||||||
|
{
|
||||||
|
userSpace: {
|
||||||
|
id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ group: { deleted: { equal: false } } },
|
||||||
|
{
|
||||||
|
group: {
|
||||||
|
userSpace: {
|
||||||
|
id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(filter ?? []),
|
||||||
|
],
|
||||||
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
|
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
@ -111,7 +128,15 @@ export class ShortUrlsDataService
|
|||||||
${DB_MODEL_FRAGMENT}
|
${DB_MODEL_FRAGMENT}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
filter: [{ group: { isNull: true } }, ...(filter ?? [])],
|
filter: [
|
||||||
|
{
|
||||||
|
userSpace: {
|
||||||
|
id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ group: { isNull: true } },
|
||||||
|
...(filter ?? []),
|
||||||
|
],
|
||||||
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
|
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
@ -263,6 +288,7 @@ export class ShortUrlsDataService
|
|||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
userSpaceId: this.sidebarService.selectedUserSpace$.value?.id,
|
||||||
shortUrl: object.shortUrl,
|
shortUrl: object.shortUrl,
|
||||||
targetUrl: object.targetUrl,
|
targetUrl: object.targetUrl,
|
||||||
description: object.description,
|
description: object.description,
|
||||||
@ -365,12 +391,12 @@ export class ShortUrlsDataService
|
|||||||
.pipe(map(result => result.data?.shortUrl.restore ?? false));
|
.pipe(map(result => result.data?.shortUrl.restore ?? false));
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllGroups() {
|
getAllAvailableGroups() {
|
||||||
return this.apollo
|
return this.apollo
|
||||||
.query<{ groups: QueryResult<Group> }>({
|
.query<{ groups: QueryResult<Group> }>({
|
||||||
query: gql`
|
query: gql`
|
||||||
query getGroups {
|
query getGroups($filter: [GroupFilter]) {
|
||||||
groups {
|
groups(filter: $filter) {
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -378,6 +404,16 @@ export class ShortUrlsDataService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
variables: {
|
||||||
|
filter: [
|
||||||
|
{
|
||||||
|
userSpace: {
|
||||||
|
id: { equal: this.sidebarService.selectedUserSpace$.value?.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ deleted: { equal: false } },
|
||||||
|
],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError(err => {
|
catchError(err => {
|
||||||
|
@ -22,7 +22,6 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.shortUrlsCreate],
|
permissions: [PermissionsEnum.shortUrlsCreate],
|
||||||
checkByPerUserSetup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -31,7 +30,6 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.shortUrlsUpdate],
|
permissions: [PermissionsEnum.shortUrlsUpdate],
|
||||||
checkByPerUserSetup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,7 +38,6 @@ const routes: Routes = [
|
|||||||
canActivate: [PermissionGuard],
|
canActivate: [PermissionGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [PermissionsEnum.shortUrls],
|
permissions: [PermissionsEnum.shortUrls],
|
||||||
checkByPerUserSetup: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
(onClick)="open(url.shortUrl)"></p-button>
|
(onClick)="open(url.shortUrl)"></p-button>
|
||||||
|
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="hasPermissions.update"
|
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-pencil"
|
icon="pi pi-pencil"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
@ -44,7 +43,6 @@
|
|||||||
[disabled]="url.deleted"
|
[disabled]="url.deleted"
|
||||||
routerLink="edit/{{ url.id }}"></p-button>
|
routerLink="edit/{{ url.id }}"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="hasPermissions.delete"
|
|
||||||
class="icon-btn btn danger-icon-btn"
|
class="icon-btn btn danger-icon-btn"
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
@ -52,7 +50,7 @@
|
|||||||
[disabled]="url.deleted"
|
[disabled]="url.deleted"
|
||||||
(click)="delete(url)"></p-button>
|
(click)="delete(url)"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="url.deleted && hasPermissions.restore"
|
*ngIf="url.deleted"
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-undo"
|
icon="pi pi-undo"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
@ -74,7 +72,6 @@
|
|||||||
<div><h1>{{ 'short_url.short_url' | translate }}</h1></div>
|
<div><h1>{{ 'short_url.short_url' | translate }}</h1></div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<p-button
|
<p-button
|
||||||
*ngIf="hasPermissions.create"
|
|
||||||
class="icon-btn btn"
|
class="icon-btn btn"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
tooltipPosition="left"
|
tooltipPosition="left"
|
||||||
|
@ -2,7 +2,6 @@ import { Component, OnInit, ViewChild } from '@angular/core';
|
|||||||
import { PageBase } from 'src/app/core/base/page-base';
|
import { PageBase } from 'src/app/core/base/page-base';
|
||||||
import { ToastService } from 'src/app/service/toast.service';
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
|
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
|
||||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
|
||||||
import { ShortUrl } from 'src/app/model/entities/short-url';
|
import { ShortUrl } from 'src/app/model/entities/short-url';
|
||||||
import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
|
import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
|
||||||
import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns';
|
import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns';
|
||||||
@ -12,17 +11,19 @@ import QrCodeWithLogo from 'qrcode-with-logos';
|
|||||||
import { FileUpload, FileUploadHandlerEvent } from 'primeng/fileupload';
|
import { FileUpload, FileUploadHandlerEvent } from 'primeng/fileupload';
|
||||||
import { ConfigService } from 'src/app/service/config.service';
|
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 { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
import { SidebarService } from 'src/app/service/sidebar.service';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-short-urls',
|
selector: 'app-short-urls',
|
||||||
templateUrl: './short-urls.page.html',
|
templateUrl: './short-urls.page.html',
|
||||||
styleUrl: './short-urls.page.scss',
|
styleUrl: './short-urls.page.scss',
|
||||||
})
|
})
|
||||||
export class ShortUrlsPage
|
export class ShortUrlsPage extends PageBase<
|
||||||
extends PageBase<ShortUrl, ShortUrlsDataService, ShortUrlsColumns>
|
ShortUrl,
|
||||||
implements OnInit
|
ShortUrlsDataService,
|
||||||
{
|
ShortUrlsColumns
|
||||||
|
> {
|
||||||
@ViewChild('imageUpload') imageUpload!: FileUpload;
|
@ViewChild('imageUpload') imageUpload!: FileUpload;
|
||||||
|
|
||||||
shortUrlsWithoutGroup: ShortUrl[] = [];
|
shortUrlsWithoutGroup: ShortUrl[] = [];
|
||||||
@ -56,53 +57,38 @@ export class ShortUrlsPage
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hasPermissions = {
|
|
||||||
read: false,
|
|
||||||
create: false,
|
|
||||||
update: false,
|
|
||||||
delete: false,
|
|
||||||
restore: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private confirmation: ConfirmationDialogService,
|
private confirmation: ConfirmationDialogService,
|
||||||
private auth: AuthService,
|
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private features: FeatureFlagService
|
private sidebar: SidebarService
|
||||||
) {
|
) {
|
||||||
super(true, {
|
super(true, {
|
||||||
read: [PermissionsEnum.shortUrls],
|
read: [],
|
||||||
create: [PermissionsEnum.shortUrlsCreate],
|
create: [],
|
||||||
update: [PermissionsEnum.shortUrlsUpdate],
|
update: [],
|
||||||
delete: [PermissionsEnum.shortUrlsDelete],
|
delete: [],
|
||||||
restore: [PermissionsEnum.shortUrlsDelete],
|
restore: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.sidebar.selectedUserSpace$
|
||||||
|
.pipe(takeUntil(this.unsubscribe$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.load(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
load(silent?: boolean): void {
|
||||||
this.hasPermissions = {
|
if (!silent) this.loading = true;
|
||||||
read:
|
this.dataService
|
||||||
(await this.auth.hasAnyPermissionLazy(
|
.load(this.filter, this.sort, this.skip, this.take)
|
||||||
this.requiredPermissions.read ?? []
|
.subscribe(result => {
|
||||||
)) || (await this.features.get('PerUserSetup')),
|
this.result = result;
|
||||||
create:
|
this.shortUrlsWithoutGroup = this.getShortUrlsWithoutGroup();
|
||||||
(await this.auth.hasAnyPermissionLazy(
|
this.groupedShortUrls = this.getShortUrlsWithGroup();
|
||||||
this.requiredPermissions.create ?? []
|
this.resolveColumns();
|
||||||
)) || (await this.features.get('PerUserSetup')),
|
this.loading = false;
|
||||||
update:
|
});
|
||||||
(await this.auth.hasAnyPermissionLazy(
|
|
||||||
this.requiredPermissions.update ?? []
|
|
||||||
)) || (await this.features.get('PerUserSetup')),
|
|
||||||
delete:
|
|
||||||
(await this.auth.hasAnyPermissionLazy(
|
|
||||||
this.requiredPermissions.delete ?? []
|
|
||||||
)) || (await this.features.get('PerUserSetup')),
|
|
||||||
restore:
|
|
||||||
(await this.auth.hasAnyPermissionLazy(
|
|
||||||
this.requiredPermissions.restore ?? []
|
|
||||||
)) || (await this.features.get('PerUserSetup')),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveColumns() {
|
resolveColumns() {
|
||||||
@ -134,19 +120,6 @@ export class ShortUrlsPage
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
load(silent?: boolean): void {
|
|
||||||
if (!silent) this.loading = true;
|
|
||||||
this.dataService
|
|
||||||
.load(this.filter, this.sort, this.skip, this.take)
|
|
||||||
.subscribe(result => {
|
|
||||||
this.result = result;
|
|
||||||
this.shortUrlsWithoutGroup = this.getShortUrlsWithoutGroup();
|
|
||||||
this.groupedShortUrls = this.getShortUrlsWithGroup();
|
|
||||||
this.resolveColumns();
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(group: ShortUrl): void {
|
delete(group: ShortUrl): void {
|
||||||
this.confirmation.confirmDialog({
|
this.confirmation.confirmDialog({
|
||||||
header: 'dialog.delete.header',
|
header: 'dialog.delete.header',
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<app-form-page
|
||||||
|
*ngIf="node"
|
||||||
|
[formGroup]="form"
|
||||||
|
[isUpdate]="isUpdate"
|
||||||
|
(onSave)="save()"
|
||||||
|
(onClose)="close()">
|
||||||
|
<ng-template formPageHeader let-isUpdate>
|
||||||
|
<h2>
|
||||||
|
{{ 'common.user_space' | translate }}
|
||||||
|
{{
|
||||||
|
(isUpdate ? 'sidebar.header.update' : 'sidebar.header.create')
|
||||||
|
| translate
|
||||||
|
}}
|
||||||
|
</h2>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template formPageContent>
|
||||||
|
<div class="form-page-input">
|
||||||
|
<p class="label">{{ 'common.id' | translate }}</p>
|
||||||
|
<input pInputText class="value" type="number" formControlName="id"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-page-input">
|
||||||
|
<p class="label">{{ 'common.name' | translate }}</p>
|
||||||
|
<input
|
||||||
|
pInputText
|
||||||
|
class="value"
|
||||||
|
type="text"
|
||||||
|
formControlName="name"/>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</app-form-page>
|
@ -0,0 +1,50 @@
|
|||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { RoleFormPageComponent } from "src/app/modules/admin/administration/roles/form-page/role-form-page.component";
|
||||||
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
|
import { TranslateModule } from "@ngx-translate/core";
|
||||||
|
import { AuthService } from "src/app/service/auth.service";
|
||||||
|
import { ErrorHandlingService } from "src/app/service/error-handling.service";
|
||||||
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
|
import { ConfirmationService, MessageService } from "primeng/api";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
import { ApiKeysDataService } from "src/app/modules/admin/administration/api-keys/api-keys.data.service";
|
||||||
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
|
||||||
|
describe("ApiKeyFormpageComponent", () => {
|
||||||
|
let component: RoleFormPageComponent;
|
||||||
|
let fixture: ComponentFixture<RoleFormPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [RoleFormPageComponent],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
SharedModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
ErrorHandlingService,
|
||||||
|
ToastService,
|
||||||
|
MessageService,
|
||||||
|
ConfirmationService,
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
snapshot: { params: of({}) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ApiKeysDataService,
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(RoleFormPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,91 @@
|
|||||||
|
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 {
|
||||||
|
UserSpace,
|
||||||
|
UserSpaceCreateInput,
|
||||||
|
UserSpaceUpdateInput,
|
||||||
|
} from 'src/app/model/entities/user-space';
|
||||||
|
import { UserSpacesDataService } from 'src/app/modules/admin/user-spaces/user-spaces.data.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-space-form-page',
|
||||||
|
templateUrl: './user-space-form-page.component.html',
|
||||||
|
styleUrl: './user-space-form-page.component.scss',
|
||||||
|
})
|
||||||
|
export class UserSpaceFormPageComponent extends FormPageBase<
|
||||||
|
UserSpace,
|
||||||
|
UserSpaceCreateInput,
|
||||||
|
UserSpaceUpdateInput,
|
||||||
|
UserSpacesDataService
|
||||||
|
> {
|
||||||
|
constructor(private toast: ToastService) {
|
||||||
|
super(undefined, '../..');
|
||||||
|
if (!this.nodeId) {
|
||||||
|
this.node = this.new();
|
||||||
|
this.setForm(this.node);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataService.loadById(this.nodeId).subscribe(node => {
|
||||||
|
this.node = node;
|
||||||
|
this.setForm(this.node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
new(): UserSpace {
|
||||||
|
return {} as UserSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildForm() {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
id: new FormControl<number | undefined>(undefined),
|
||||||
|
name: new FormControl<string | undefined>(undefined, [
|
||||||
|
Validators.required,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
this.form.controls['id'].disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
setForm(node?: UserSpace) {
|
||||||
|
this.form.controls['id'].setValue(node?.id);
|
||||||
|
this.form.controls['name'].setValue(node?.name);
|
||||||
|
|
||||||
|
this.spinner.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateInput(): UserSpaceCreateInput {
|
||||||
|
return {
|
||||||
|
name: this.form.controls['name'].value ?? undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdateInput(): UserSpaceUpdateInput {
|
||||||
|
if (!this.node?.id) {
|
||||||
|
throw new Error('Node id is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.form.controls['id'].value,
|
||||||
|
name: this.form.controls['name'].value ?? undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
create(object: UserSpaceCreateInput): void {
|
||||||
|
this.dataService.create(object).subscribe(() => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.success('action.created');
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(object: UserSpaceUpdateInput): void {
|
||||||
|
this.dataService.update(object).subscribe(() => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.success('action.created');
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,292 @@
|
|||||||
|
import { Injectable, Provider } from '@angular/core';
|
||||||
|
import { merge, 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, SortOrder } 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_HISTORY_MODEL_FRAGMENT,
|
||||||
|
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 {
|
||||||
|
UserSpace,
|
||||||
|
UserSpaceCreateInput,
|
||||||
|
UserSpaceUpdateInput,
|
||||||
|
} from 'src/app/model/entities/user-space';
|
||||||
|
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserSpacesDataService
|
||||||
|
extends PageDataService<UserSpace>
|
||||||
|
implements
|
||||||
|
Create<UserSpace, UserSpaceCreateInput>,
|
||||||
|
Update<UserSpace, UserSpaceUpdateInput>,
|
||||||
|
Delete<UserSpace>,
|
||||||
|
Restore<UserSpace>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private spinner: SpinnerService,
|
||||||
|
private apollo: Apollo
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
load(
|
||||||
|
filter?: Filter[] | undefined,
|
||||||
|
sort?: Sort[] | undefined,
|
||||||
|
skip?: number | undefined,
|
||||||
|
take?: number | undefined
|
||||||
|
): Observable<QueryResult<UserSpace>> {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ userSpaces: QueryResult<UserSpace> }>({
|
||||||
|
query: gql`
|
||||||
|
query getUserSpaces(
|
||||||
|
$filter: [UserSpaceFilter]
|
||||||
|
$sort: [UserSpaceSort]
|
||||||
|
) {
|
||||||
|
userSpaces(filter: $filter, sort: $sort) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
filter: [filter],
|
||||||
|
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(map(result => result.data.userSpaces));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadById(id: number): Observable<UserSpace> {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ userSpaces: QueryResult<UserSpace> }>({
|
||||||
|
query: gql`
|
||||||
|
query getUserSpace($id: Int) {
|
||||||
|
userSpaces(filter: { id: { equal: $id } }) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data.userSpaces.nodes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadHistory(id: number) {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ userSpaces: QueryResult<UserSpace> }>({
|
||||||
|
query: gql`
|
||||||
|
query getUserSpaceHistory($id: Int) {
|
||||||
|
userSpaces(filter: { id: { equal: $id } }) {
|
||||||
|
count
|
||||||
|
totalCount
|
||||||
|
nodes {
|
||||||
|
history {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_HISTORY_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_HISTORY_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.userSpaces?.nodes?.[0]?.history ?? []));
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(): Observable<void> {
|
||||||
|
return merge(
|
||||||
|
this.apollo
|
||||||
|
.subscribe<{ userSpaceChange: void }>({
|
||||||
|
query: gql`
|
||||||
|
subscription onUserSpaceChange {
|
||||||
|
userSpaceChange
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
.pipe(map(result => result.data?.userSpaceChange)),
|
||||||
|
|
||||||
|
this.apollo
|
||||||
|
.subscribe<{ groupChange: void }>({
|
||||||
|
query: gql`
|
||||||
|
subscription onGroupChange {
|
||||||
|
groupChange
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
.pipe(map(result => result.data?.groupChange))
|
||||||
|
).pipe(map(() => {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
create(object: UserSpaceCreateInput): Observable<UserSpace | undefined> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ userSpace: { create: UserSpace } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation createUserSpace($input: UserSpaceCreateInput!) {
|
||||||
|
userSpace {
|
||||||
|
create(input: $input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
name: object.name,
|
||||||
|
users: object.users,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.userSpace.create));
|
||||||
|
}
|
||||||
|
|
||||||
|
update(object: UserSpaceUpdateInput): Observable<UserSpace | undefined> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ userSpace: { update: UserSpace } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation updateUserSpace($input: UserSpaceUpdateInput!) {
|
||||||
|
userSpace {
|
||||||
|
update(input: $input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: object.id,
|
||||||
|
name: object.name,
|
||||||
|
users: object.users,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.userSpace.update));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(object: UserSpace): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ userSpace: { delete: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation deleteUserSpace($id: Int!) {
|
||||||
|
userSpace {
|
||||||
|
delete(id: $id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: object.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.userSpace.delete ?? false));
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(object: UserSpace): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ userSpace: { restore: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation restoreUserSpace($id: Int!) {
|
||||||
|
userSpace {
|
||||||
|
restore(id: $id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: object.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.userSpace.restore ?? false));
|
||||||
|
}
|
||||||
|
|
||||||
|
static provide(): Provider[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PageDataService,
|
||||||
|
useClass: UserSpacesDataService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PageWithHistoryDataService,
|
||||||
|
useClass: UserSpacesDataService,
|
||||||
|
},
|
||||||
|
UserSpacesDataService,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
24
web/src/app/modules/admin/user-spaces/user-spaces.module.ts
Normal file
24
web/src/app/modules/admin/user-spaces/user-spaces.module.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||||
|
import { UserSpacesDataService } from 'src/app/modules/admin/user-spaces/user-spaces.data.service';
|
||||||
|
import { UserSpaceFormPageComponent } from 'src/app/modules/admin/user-spaces/form-page/user-space-form-page.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
component: UserSpaceFormPageComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:id',
|
||||||
|
component: UserSpaceFormPageComponent,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [UserSpaceFormPageComponent],
|
||||||
|
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
|
||||||
|
providers: [UserSpacesDataService.provide()],
|
||||||
|
})
|
||||||
|
export class UserSpacesModule {}
|
@ -1,49 +1,51 @@
|
|||||||
<ng-template #menuBtn let-element>
|
<ng-template #menuBtn let-element>
|
||||||
<a
|
<a
|
||||||
class="flex w-full gap-5 items-center justify-center p-2 rounded-xl hover:bg hover:cursor-pointer"
|
class="flex w-full gap-5 items-center justify-center p-2 rounded-xl hover:bg hover:cursor-pointer"
|
||||||
(click)="(element.command)"
|
(click)="element.command ? element.command() : null"
|
||||||
[routerLink]="element.routerLink">
|
[routerLink]="element.routerLink">
|
||||||
<div class=""><span [class]="element.icon"></span></div>
|
<div class=""><span [class]="element.icon"></span></div>
|
||||||
<div class="flex flex-1 font-bold justify-start">
|
<div class="flex flex-1 font-bold justify-start">
|
||||||
{{ element.label | translate }}
|
{{ element.label | translate }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="(element.items ?? []).length > 0" class="hover:highlight">
|
<div *ngIf="(element.items ?? []).length > 0" class="hover:highlight">
|
||||||
<button *ngIf="element.expanded" (click)="collapse(element)">
|
<button *ngIf="element.expanded" (click)="collapse(element)">
|
||||||
<span class="pi pi-angle-down"></span>
|
<span class="pi pi-angle-down"></span>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="!element.expanded" (click)="expand(element)">
|
<button *ngIf="!element.expanded" (click)="expand(element)">
|
||||||
<span class="pi pi-angle-right"></span>
|
<span class="pi pi-angle-right"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-col gap-2 justify-between w-full p-1.5 overflow-hidden max-w-56">
|
class="flex flex-col gap-2 justify-between w-full p-1.5 overflow-hidden max-w-56">
|
||||||
<ng-container *ngFor="let element of elements">
|
<ng-container *ngFor="let element of elements">
|
||||||
<div *ngIf="element.visible !== false">
|
<span *ngIf="debug"><{{ element.label }} - {{ element.visible }} | {{ element.command }}></span>
|
||||||
<ng-template
|
<div *ngIf="element.visible !== false">
|
||||||
*ngTemplateOutlet="
|
<ng-template
|
||||||
|
*ngTemplateOutlet="
|
||||||
menuBtn;
|
menuBtn;
|
||||||
context: { $implicit: element }
|
context: { $implicit: element }
|
||||||
"></ng-template>
|
"></ng-template>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex"
|
class="flex"
|
||||||
*ngIf="element.expanded && element.items && element.items.length > 0">
|
*ngIf="element.expanded && element.items && element.items.length > 0">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col gap-2 justify-between w-full p-1.5 overflow-hidden max-w-56">
|
class="flex flex-col gap-2 justify-between w-full p-1.5 overflow-hidden max-w-56">
|
||||||
<ng-container *ngFor="let item of element.items">
|
<ng-container *ngFor="let item of element.items">
|
||||||
<div *ngIf="item.visible !== false">
|
<span *ngIf="debug"><{{ item.label }} - {{ item.visible }} | {{ item.command }}></span>
|
||||||
<ng-template
|
<div *ngIf="item.visible !== false">
|
||||||
*ngTemplateOutlet="
|
<ng-template
|
||||||
|
*ngTemplateOutlet="
|
||||||
menuBtn;
|
menuBtn;
|
||||||
context: { $implicit: item }
|
context: { $implicit: item }
|
||||||
"></ng-template>
|
"></ng-template>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ng-container>
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,32 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input, OnDestroy } from '@angular/core';
|
||||||
import { MenuElement } from 'src/app/model/view/menu-element';
|
import { MenuElement } from 'src/app/model/view/menu-element';
|
||||||
|
import { GuiService } from 'src/app/service/gui.service';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-side-menu',
|
selector: 'app-side-menu',
|
||||||
templateUrl: './side-menu.component.html',
|
templateUrl: './side-menu.component.html',
|
||||||
styleUrl: './side-menu.component.scss',
|
styleUrl: './side-menu.component.scss',
|
||||||
})
|
})
|
||||||
export class SideMenuComponent {
|
export class SideMenuComponent implements OnDestroy {
|
||||||
@Input() elements: MenuElement[] = [];
|
@Input() elements: MenuElement[] = [];
|
||||||
|
|
||||||
|
debug = false;
|
||||||
|
|
||||||
|
unsubscribe$ = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(private gui: GuiService) {
|
||||||
|
this.gui.debug$.pipe(takeUntil(this.unsubscribe$)).subscribe(debug => {
|
||||||
|
this.debug = debug;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.unsubscribe$.next();
|
||||||
|
this.unsubscribe$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
expand(element: MenuElement): void {
|
expand(element: MenuElement): void {
|
||||||
element.expanded = true;
|
element.expanded = true;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ export class GuiService {
|
|||||||
hideGui$ = new BehaviorSubject<boolean>(false);
|
hideGui$ = new BehaviorSubject<boolean>(false);
|
||||||
theme$ = new BehaviorSubject<string>(this.config.settings.themes[0].name);
|
theme$ = new BehaviorSubject<string>(this.config.settings.themes[0].name);
|
||||||
|
|
||||||
|
debug$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private apollo: Apollo,
|
private apollo: Apollo,
|
||||||
|
69
web/src/app/service/sidebar.data.service.ts
Normal file
69
web/src/app/service/sidebar.data.service.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Apollo, gql } from 'apollo-angular';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
import { UserSpace } from 'src/app/model/entities/user-space';
|
||||||
|
import { QueryResult } from 'src/app/model/entities/query-result';
|
||||||
|
import { SpinnerService } from 'src/app/service/spinner.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SidebarDataService {
|
||||||
|
constructor(
|
||||||
|
private apollo: Apollo,
|
||||||
|
private spinner: SpinnerService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getAssignedUserSpaces() {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ assignedUserSpaces: QueryResult<UserSpace> }>({
|
||||||
|
query: gql`
|
||||||
|
query getAssignedUserSpaces {
|
||||||
|
assignedUserSpaces {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
.pipe(map(result => result.data?.assignedUserSpaces.nodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(): Observable<void> {
|
||||||
|
return this.apollo
|
||||||
|
.subscribe<{ groupChange: void }>({
|
||||||
|
query: gql`
|
||||||
|
subscription onUserSpaceChange {
|
||||||
|
userSpaceChange
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
.pipe(map(result => result.data?.groupChange));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(object: UserSpace): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ userSpace: { delete: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation deleteUserSpace($id: Int!) {
|
||||||
|
userSpace {
|
||||||
|
delete(id: $id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: object.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(err => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(map(result => result.data?.userSpace.delete ?? false));
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,11 @@ import { BehaviorSubject } from 'rxjs';
|
|||||||
import { MenuElement } from 'src/app/model/view/menu-element';
|
import { MenuElement } from 'src/app/model/view/menu-element';
|
||||||
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 { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
import { SidebarDataService } from 'src/app/service/sidebar.data.service';
|
||||||
|
import { UserSpace } from 'src/app/model/entities/user-space';
|
||||||
|
import { SpinnerService } from 'src/app/service/spinner.service';
|
||||||
|
import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -12,13 +16,50 @@ export class SidebarService {
|
|||||||
visible$ = new BehaviorSubject<boolean>(true);
|
visible$ = new BehaviorSubject<boolean>(true);
|
||||||
elements$ = new BehaviorSubject<MenuElement[]>([]);
|
elements$ = new BehaviorSubject<MenuElement[]>([]);
|
||||||
|
|
||||||
|
selectedUserSpace$ = new BehaviorSubject<UserSpace | undefined | null>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
userSpaces: UserSpace[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private auth: AuthService,
|
private auth: AuthService,
|
||||||
private featureFlags: FeatureFlagService
|
private data: SidebarDataService,
|
||||||
|
private spinner: SpinnerService,
|
||||||
|
private router: Router,
|
||||||
|
private confirmation: ConfirmationDialogService
|
||||||
) {
|
) {
|
||||||
|
this.loadUserSpaces();
|
||||||
this.auth.user$.subscribe(async () => {
|
this.auth.user$.subscribe(async () => {
|
||||||
await this.setElements();
|
await this.setElements();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.data.onChange().subscribe(() => {
|
||||||
|
this.loadUserSpaces();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.selectedUserSpace$.subscribe(async value => {
|
||||||
|
// skip initial value
|
||||||
|
if (value === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSelectedUserSpaceIdToLS(value ? value.id : 0);
|
||||||
|
await this.setElements();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUserSpaces() {
|
||||||
|
this.data.getAssignedUserSpaces().subscribe(async userSpaces => {
|
||||||
|
this.userSpaces = userSpaces;
|
||||||
|
|
||||||
|
const storedId = this.getSelectedUserSpaceIdFromLS();
|
||||||
|
|
||||||
|
const selectedUserSpace = userSpaces.find(x => x.id === storedId);
|
||||||
|
if (selectedUserSpace) {
|
||||||
|
this.selectedUserSpace$.next(selectedUserSpace);
|
||||||
|
}
|
||||||
|
await this.setElements();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
@ -33,33 +74,93 @@ export class SidebarService {
|
|||||||
async setElements() {
|
async setElements() {
|
||||||
const elements: MenuElement[] = [
|
const elements: MenuElement[] = [
|
||||||
{
|
{
|
||||||
label: 'common.domains',
|
label: 'sidebar.user_spaces',
|
||||||
icon: 'pi pi-sitemap',
|
icon: 'pi pi-list',
|
||||||
routerLink: ['/admin/domains'],
|
expanded: true,
|
||||||
visible: await this.auth.hasAnyPermissionLazy([
|
items: [
|
||||||
PermissionsEnum.domains,
|
{
|
||||||
]),
|
label: 'sidebar.user_space_add',
|
||||||
|
icon: 'pi pi-plus',
|
||||||
|
routerLink: ['/admin/rooms/create'],
|
||||||
|
},
|
||||||
|
...(this.userSpaces
|
||||||
|
?.filter(x => x.name != this.selectedUserSpace$.value?.name)
|
||||||
|
.map(x => {
|
||||||
|
return {
|
||||||
|
label: x.name,
|
||||||
|
icon: 'pi pi-hashtag',
|
||||||
|
command: async () => {
|
||||||
|
this.spinner.show();
|
||||||
|
this.selectedUserSpace$.next(x);
|
||||||
|
await this.setElements();
|
||||||
|
await this.router.navigate(['/admin/urls']);
|
||||||
|
},
|
||||||
|
} as MenuElement;
|
||||||
|
}) ?? []),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'common.groups',
|
label: this.selectedUserSpace$.value?.name || '',
|
||||||
icon: 'pi pi-tags',
|
icon: 'pi pi-hashtag',
|
||||||
routerLink: ['/admin/groups'],
|
visible: !!this.selectedUserSpace$.value,
|
||||||
visible:
|
expanded: true,
|
||||||
(await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups])) ||
|
items: [
|
||||||
(await this.featureFlags.get('PerUserSetup')),
|
{
|
||||||
},
|
label: 'sidebar.user_space_edit',
|
||||||
{
|
icon: 'pi pi-pencil',
|
||||||
label: 'common.urls',
|
routerLink: [
|
||||||
icon: 'pi pi-tag',
|
`/admin/rooms/edit/${this.selectedUserSpace$.value?.id}`,
|
||||||
routerLink: ['/admin/urls'],
|
],
|
||||||
visible:
|
},
|
||||||
(await this.auth.hasAnyPermissionLazy([
|
{
|
||||||
PermissionsEnum.shortUrls,
|
label: 'common.groups',
|
||||||
PermissionsEnum.shortUrlsByAssignment,
|
icon: 'pi pi-tags',
|
||||||
])) || (await this.featureFlags.get('PerUserSetup')),
|
routerLink: ['/admin/groups'],
|
||||||
|
visible:
|
||||||
|
this.selectedUserSpace$.value !== null &&
|
||||||
|
(await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.urls',
|
||||||
|
icon: 'pi pi-tag',
|
||||||
|
routerLink: ['/admin/urls'],
|
||||||
|
visible:
|
||||||
|
this.selectedUserSpace$.value !== null &&
|
||||||
|
(await this.auth.hasAnyPermissionLazy([
|
||||||
|
PermissionsEnum.shortUrls,
|
||||||
|
PermissionsEnum.shortUrlsByAssignment,
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'sidebar.user_space_delete',
|
||||||
|
icon: 'pi pi-trash',
|
||||||
|
command: () => {
|
||||||
|
this.confirmation.confirmDialog({
|
||||||
|
header: 'dialog.delete.header',
|
||||||
|
message: 'dialog.delete.message',
|
||||||
|
accept: () => {
|
||||||
|
if (!this.selectedUserSpace$.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spinner.show();
|
||||||
|
this.data
|
||||||
|
.delete(this.selectedUserSpace$.value)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.selectedUserSpace$.next(null);
|
||||||
|
this.spinner.hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
messageParams: { entity: this.selectedUserSpace$.value?.name },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
await this.sectionAdmin(),
|
await this.sectionAdmin(),
|
||||||
];
|
];
|
||||||
|
|
||||||
this.elements$.next(elements);
|
this.elements$.next(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +172,14 @@ export class SidebarService {
|
|||||||
expanded: true,
|
expanded: true,
|
||||||
isSection: true,
|
isSection: true,
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
label: 'common.domains',
|
||||||
|
icon: 'pi pi-sitemap',
|
||||||
|
routerLink: ['/admin/administration/domains'],
|
||||||
|
visible: await this.auth.hasAnyPermissionLazy([
|
||||||
|
PermissionsEnum.domains,
|
||||||
|
]),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'sidebar.users',
|
label: 'sidebar.users',
|
||||||
icon: 'pi pi-user',
|
icon: 'pi pi-user',
|
||||||
@ -114,4 +223,13 @@ export class SidebarService {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getSelectedUserSpaceIdFromLS() {
|
||||||
|
const storedId = localStorage.getItem('sidebar_SelectedUserSpaceId');
|
||||||
|
return storedId ? +storedId : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setSelectedUserSpaceIdToLS(id: number) {
|
||||||
|
localStorage.setItem('sidebar_SelectedUserSpaceId', id.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"updated": "Bearbeitet",
|
"updated": "Bearbeitet",
|
||||||
"upload": "Hochladen",
|
"upload": "Hochladen",
|
||||||
"urls": "URLs",
|
"urls": "URLs",
|
||||||
|
"user_space": "Team",
|
||||||
"warning": "Warnung"
|
"warning": "Warnung"
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
@ -239,6 +240,10 @@
|
|||||||
},
|
},
|
||||||
"roles": "Rollen",
|
"roles": "Rollen",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
|
"user_space_add": "Team erstellen",
|
||||||
|
"user_space_delete": "Team löschen",
|
||||||
|
"user_space_edit": "Team bearbeiten",
|
||||||
|
"user_spaces": "Teams",
|
||||||
"users": "Benutzer"
|
"users": "Benutzer"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"updated": "Updated",
|
"updated": "Updated",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"urls": "URLs",
|
"urls": "URLs",
|
||||||
|
"user_space": "Team",
|
||||||
"warning": "Warning"
|
"warning": "Warning"
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
@ -239,6 +240,10 @@
|
|||||||
},
|
},
|
||||||
"roles": "Roles",
|
"roles": "Roles",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
|
"user_space_add": "Add Team",
|
||||||
|
"user_space_delete": "Delete Team",
|
||||||
|
"user_space_edit": "Edit Team",
|
||||||
|
"user_spaces": "Teams",
|
||||||
"users": "Users"
|
"users": "Users"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
|
Loading…
Reference in New Issue
Block a user