Added user spaces

This commit is contained in:
Sven Heidemann 2025-04-18 13:56:15 +02:00
parent 9ddd85d36a
commit 75b4ec2ea1
19 changed files with 248 additions and 72 deletions

View File

@ -120,7 +120,7 @@ class QueryABC(ObjectType):
skip = None skip = None
if field.default_filter: if field.default_filter:
filters.append(field.default_filter(*args, **kwargs)) filters.append(await field.default_filter(*args, **kwargs))
if field.filter_type and "filter" in kwargs: if field.filter_type and "filter" in kwargs:
in_filters = kwargs["filter"] in_filters = kwargs["filter"]

View File

@ -11,3 +11,6 @@ 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("isNull", bool)
self.add_field("isNotNull", bool)

View File

@ -39,8 +39,8 @@ input StringFilter {
startsWith: String startsWith: String
endsWith: String endsWith: String
isNull: String isNull: Boolean
isNotNull: String isNotNull: Boolean
} }
input IntFilter { input IntFilter {
@ -51,8 +51,8 @@ input IntFilter {
less: Int less: Int
lessOrEqual: Int lessOrEqual: Int
isNull: Int isNull: Boolean
isNotNull: Int isNotNull: Boolean
in: [Int] in: [Int]
notIn: [Int] notIn: [Int]
} }
@ -61,8 +61,8 @@ input BooleanFilter {
equal: Boolean equal: Boolean
notEqual: Int notEqual: Int
isNull: Int isNull: Boolean
isNotNull: Int isNotNull: Boolean
} }
input DateFilter { input DateFilter {
@ -78,8 +78,8 @@ input DateFilter {
contains: String contains: String
notContains: String notContains: String
isNull: String isNull: Boolean
isNotNull: String isNotNull: Boolean
in: [String] in: [String]
notIn: [String] notIn: [String]

View File

@ -61,6 +61,9 @@ input GroupFilter {
editor: IntFilter editor: IntFilter
created: DateFilter created: DateFilter
updated: DateFilter updated: DateFilter
isNull: Boolean
isNotNull: Boolean
} }
type GroupMutation { type GroupMutation {

View File

@ -1,8 +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.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 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 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
@ -75,6 +78,11 @@ class GroupMutation(MutationABC):
group = Group( group = Group(
0, 0,
obj.name, obj.name,
(
(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)

View File

@ -1,6 +1,9 @@
from api.route import Route
from api_graphql.abc.mutation_abc import MutationABC from api_graphql.abc.mutation_abc import MutationABC
from api_graphql.input.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 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 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
@ -57,6 +60,11 @@ class ShortUrlMutation(MutationABC):
obj.group_id, obj.group_id,
obj.domain_id, obj.domain_id,
obj.loading_screen, obj.loading_screen,
(
(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)

View File

@ -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 group_by_assignment_resolver from api_graphql.require_any_resolvers import by_assignment_resolver
from data.schemas.public.group import Group from data.schemas.public.group import Group
from data.schemas.public.group_dao import groupDao from data.schemas.public.group_dao import groupDao
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
@ -21,7 +21,7 @@ class GroupHistoryQuery(DbHistoryModelQueryABC):
[ [
Permissions.groups, Permissions.groups,
], ],
[group_by_assignment_resolver], [by_assignment_resolver],
) )
) )
self.set_field( self.set_field(

View File

@ -1,6 +1,6 @@
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 group_by_assignment_resolver from api_graphql.require_any_resolvers import by_assignment_resolver
from data.schemas.public.group import Group from data.schemas.public.group import Group
from data.schemas.public.group_dao import groupDao from data.schemas.public.group_dao import groupDao
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
@ -21,7 +21,7 @@ class GroupQuery(DbModelQueryABC):
[ [
Permissions.groups, Permissions.groups,
], ],
[group_by_assignment_resolver], [by_assignment_resolver],
) )
) )
self.set_field("roles", self._get_roles) self.set_field("roles", self._get_roles)

View File

@ -11,7 +11,12 @@ 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.require_any_resolvers import group_by_assignment_resolver from api_graphql.require_any_resolvers import (
by_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
@ -109,7 +114,8 @@ class Query(QueryABC):
] ]
) )
) )
self.field(
group_field = (
DaoFieldBuilder("groups") DaoFieldBuilder("groups")
.with_dao(groupDao) .with_dao(groupDao)
.with_filter(GroupFilter) .with_filter(GroupFilter)
@ -120,15 +126,33 @@ class Query(QueryABC):
Permissions.short_urls_create, Permissions.short_urls_create,
Permissions.short_urls_update, Permissions.short_urls_update,
], ],
[group_by_assignment_resolver], [by_assignment_resolver, by_user_setup_resolver],
) )
) )
if FeatureFlags.get_default(FeatureFlagsEnum.per_user_setup):
group_field = group_field.with_default_filter(self._resolve_default_user_filter)
self.field( 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([Permissions.short_urls], [group_by_assignment_resolver]) .with_require_any(
[Permissions.short_urls],
[by_assignment_resolver, by_user_setup_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(
@ -202,3 +226,7 @@ class Query(QueryABC):
if "key" in kwargs: if "key" in kwargs:
return [await featureFlagDao.find_by_key(kwargs["key"])] return [await featureFlagDao.find_by_key(kwargs["key"])]
return await featureFlagDao.get_all() return await featureFlagDao.get_all()
@staticmethod
async def _resolve_default_user_filter(*args, **kwargs) -> dict:
return {"user": {"id": {"equal": (await Route.get_user()).id}}}

View File

@ -1,10 +1,12 @@
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 service.permission.permissions_enum import Permissions from service.permission.permissions_enum import Permissions
async def group_by_assignment_resolver(ctx: QueryContext) -> bool: async def by_assignment_resolver(ctx: QueryContext) -> bool:
if not isinstance(ctx.data, CollectionResult): if not isinstance(ctx.data, CollectionResult):
return False return False
@ -19,12 +21,19 @@ async def group_by_assignment_resolver(ctx: QueryContext) -> bool:
and all(r.id in role_ids for r in roles) and all(r.id in role_ids for r in roles)
] ]
ctx.data.nodes = [ return all(
node (await node.group) is not None and (await node.group).id in filtered_groups
for node in ctx.data.nodes for node in ctx.data.nodes
if (await node.group) is not None )
and (await node.group).id in filtered_groups
]
return True
return True 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_setup_id == ctx.user.id for x in ctx.data.nodes)

View File

@ -1,10 +1,14 @@
from core.configuration.feature_flags_enum import FeatureFlagsEnum from core.configuration.feature_flags_enum import FeatureFlagsEnum
from core.environment import Environment
from data.schemas.system.feature_flag_dao import featureFlagDao from data.schemas.system.feature_flag_dao import featureFlagDao
class FeatureFlags: class FeatureFlags:
_flags = { _flags = {
FeatureFlagsEnum.version_endpoint.value: True, # 15.01.2025 FeatureFlagsEnum.version_endpoint.value: True, # 15.01.2025
FeatureFlagsEnum.per_user_setup.value: Environment.get(
"PER_USER_SETUP", bool, False
), # 18.04.2025
} }
@staticmethod @staticmethod
@ -15,6 +19,6 @@ class FeatureFlags:
async def has_feature(key: FeatureFlagsEnum) -> bool: async def has_feature(key: FeatureFlagsEnum) -> bool:
value = await featureFlagDao.find_by_key(key.value) value = await featureFlagDao.find_by_key(key.value)
if value is None: if value is None:
return False return FeatureFlags.get_default(key)
return value.value return value.value

View File

@ -2,5 +2,5 @@ from enum import Enum
class FeatureFlagsEnum(Enum): class FeatureFlagsEnum(Enum):
# modules
version_endpoint = "VersionEndpoint" version_endpoint = "VersionEndpoint"
per_user_setup = "PerUserSetup"

View File

@ -18,6 +18,22 @@ T_DBM = TypeVar("T_DBM", bound=DbModelABC)
class DataAccessObjectABC(ABC, Database, Generic[T_DBM]): class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
_external_fields: dict[str, ExternalDataTempTableBuilder] = {} _external_fields: dict[str, ExternalDataTempTableBuilder] = {}
_operators = [
"equal",
"notEqual",
"greater",
"greaterOrEqual",
"less",
"lessOrEqual",
"isNull",
"isNotNull",
"contains",
"notContains",
"startsWith",
"endsWith",
"in",
"notIn",
]
@abstractmethod @abstractmethod
def __init__(self, source: str, model_type: Type[T_DBM], table_name: str): def __init__(self, source: str, model_type: Type[T_DBM], table_name: str):
@ -646,7 +662,9 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
if attr in self.__foreign_tables: if attr in self.__foreign_tables:
foreign_table = self.__foreign_tables[attr] foreign_table = self.__foreign_tables[attr]
cons, eftd = self._build_foreign_conditions(foreign_table, values) cons, eftd = self._build_foreign_conditions(
attr, foreign_table, values
)
if eftd: if eftd:
external_field_table_deps.extend(eftd) external_field_table_deps.extend(eftd)
@ -670,6 +688,8 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
isinstance(values, dict) or isinstance(values, list) isinstance(values, dict) or isinstance(values, list)
) and not attr in self.__foreign_tables: ) and not attr in self.__foreign_tables:
db_name = f"{self._table_name}.{self.__db_names[attr]}" db_name = f"{self._table_name}.{self.__db_names[attr]}"
elif attr in self._operators:
db_name = f"{self._table_name}.{self.__db_names[attr]}"
else: else:
db_name = self.__db_names[attr] db_name = self.__db_names[attr]
@ -691,6 +711,8 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
self._get_value_validation_sql(db_name, value) self._get_value_validation_sql(db_name, value)
) )
f_conditions.append(f"({' OR '.join(sub_conditions)})") f_conditions.append(f"({' OR '.join(sub_conditions)})")
elif attr in self._operators:
conditions.append(f"{self._build_condition(db_name, attr, values)}")
else: else:
f_conditions.append(self._get_value_validation_sql(db_name, values)) f_conditions.append(self._get_value_validation_sql(db_name, values))
@ -711,10 +733,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
return conditions return conditions
def _build_foreign_conditions( def _build_foreign_conditions(
self, table: str, values: dict self, base_attr: str, table: str, values: dict
) -> (list[str], list[str]): ) -> (list[str], list[str]):
""" """
Build SQL conditions for foreign key references Build SQL conditions for foreign key references
:param base_attr: Base attribute name
:param table: Foreign table name :param table: Foreign table name
:param values: Filter values :param values: Filter values
:return: List of conditions, List of external field tables :return: List of conditions, List of external field tables
@ -728,7 +751,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
if attr in self.__foreign_tables: if attr in self.__foreign_tables:
foreign_table = self.__foreign_tables[attr] foreign_table = self.__foreign_tables[attr]
sub_conditions, eftd = self._build_foreign_conditions( sub_conditions, eftd = self._build_foreign_conditions(
foreign_table, sub_values attr, foreign_table, sub_values
) )
if len(eftd) > 0: if len(eftd) > 0:
external_field_table_deps.extend(eftd) external_field_table_deps.extend(eftd)
@ -749,6 +772,8 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
] ]
db_name = f"{external_fields_table.table_name}.{attr}" db_name = f"{external_fields_table.table_name}.{attr}"
external_field_table_deps.append(external_fields_table.table_name) external_field_table_deps.append(external_fields_table.table_name)
elif attr in self._operators:
db_name = f"{self._table_name}.{self.__foreign_table_keys[base_attr]}"
else: else:
db_name = f"{table}.{attr.lower().replace('_', '')}" db_name = f"{table}.{attr.lower().replace('_', '')}"
@ -770,6 +795,8 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
self._get_value_validation_sql(db_name, value) self._get_value_validation_sql(db_name, value)
) )
conditions.append(f"({' OR '.join(sub_conditions)})") conditions.append(f"({' OR '.join(sub_conditions)})")
elif attr in self._operators:
conditions.append(f"{self._build_condition(db_name, attr, sub_values)}")
else: else:
conditions.append(self._get_value_validation_sql(db_name, sub_values)) conditions.append(self._get_value_validation_sql(db_name, sub_values))
@ -815,7 +842,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
def _get_value_validation_sql(self, field: str, value: Any): def _get_value_validation_sql(self, field: str, value: Any):
value = self._get_value_sql(value) value = self._get_value_sql(value)
field_selector = f"{self._table_name}.{field}" field_selector = (
f"{self._table_name}.{field}"
if not field.startswith(self._table_name)
else field
)
if field in self.__foreign_tables: if field in self.__foreign_tables:
field_selector = self.__db_names[field] field_selector = self.__db_names[field]
@ -850,9 +881,9 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
elif operator == "lessOrEqual": elif operator == "lessOrEqual":
return f"{db_name} <= {sql_value}" return f"{db_name} <= {sql_value}"
elif operator == "isNull": elif operator == "isNull":
return f"{db_name} IS NULL" return f"{db_name} IS NULL" if sql_value else f"{db_name} IS NOT NULL"
elif operator == "isNotNull": elif operator == "isNotNull":
return f"{db_name} IS NOT NULL" return f"{db_name} IS NOT NULL" if sql_value else f"{db_name} IS NULL"
elif operator == "contains": elif operator == "contains":
return f"{db_name} LIKE '%{value}%'" return f"{db_name} LIKE '%{value}%'"
elif operator == "notContains": elif operator == "notContains":

View File

@ -1,6 +1,8 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from async_property import async_property
from core.database.abc.db_model_abc import DbModelABC from core.database.abc.db_model_abc import DbModelABC
from core.typing import SerialId from core.typing import SerialId
@ -10,6 +12,7 @@ class Group(DbModelABC):
self, self,
id: SerialId, id: SerialId,
name: str, name: str,
user_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,
@ -17,6 +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
@property @property
def name(self) -> str: def name(self) -> str:
@ -25,3 +29,17 @@ class Group(DbModelABC):
@name.setter @name.setter
def name(self, value: str): def name(self, value: str):
self._name = value self._name = value
@property
def user_id(self) -> Optional[SerialId]:
return self._user_id
@async_property
async def user(self):
if self._user_id is None:
return None
from data.schemas.administration.user_dao import userDao
user = await userDao.get_by_id(self.user_id)
return user

View File

@ -11,6 +11,9 @@ 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.reference("user", "id", Group.user_id, "administration.users")
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(
f"SELECT * FROM {self._table_name} WHERE Name = '{name}'" f"SELECT * FROM {self._table_name} WHERE Name = '{name}'"

View File

@ -18,6 +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,
deleted: bool = False, deleted: bool = False,
editor_id: Optional[SerialId] = None, editor_id: Optional[SerialId] = None,
created: Optional[datetime] = None, created: Optional[datetime] = None,
@ -34,6 +35,8 @@ class ShortUrl(DbModelABC):
loading_screen = False loading_screen = False
self._loading_screen = loading_screen self._loading_screen = loading_screen
self._user_id = user_id
@property @property
def short_url(self) -> str: def short_url(self) -> str:
return self._short_url return self._short_url
@ -106,6 +109,20 @@ class ShortUrl(DbModelABC):
def loading_screen(self, value: Optional[str]): def loading_screen(self, value: Optional[str]):
self._loading_screen = value self._loading_screen = value
@property
def user_id(self) -> Optional[SerialId]:
return self._user_id
@async_property
async def user(self):
if self._user_id is None:
return None
from data.schemas.administration.user_dao import userDao
user = await userDao.get_by_id(self.user_id)
return user
def to_dto(self) -> dict: def to_dto(self) -> dict:
return { return {
"id": self.id, "id": self.id,

View File

@ -18,5 +18,8 @@ class ShortUrlDao(DbModelDaoABC[ShortUrl]):
self.reference("domain", "id", ShortUrl.domain_id, "public.domains") self.reference("domain", "id", ShortUrl.domain_id, "public.domains")
self.attribute(ShortUrl.loading_screen, bool) self.attribute(ShortUrl.loading_screen, bool)
self.attribute(ShortUrl.user_id, int)
self.reference("user", "id", ShortUrl.user_id, "administration.users")
shortUrlDao = ShortUrlDao() shortUrlDao = ShortUrlDao()

View File

@ -0,0 +1,11 @@
ALTER TABLE public.groups
ADD COLUMN IF NOT EXISTS UserId INT NULL REFERENCES administration.users (Id);
ALTER TABLE public.groups_history
ADD COLUMN IF NOT EXISTS UserId INT NULL REFERENCES administration.users (Id);
ALTER TABLE public.short_urls
ADD COLUMN IF NOT EXISTS UserId INT NULL REFERENCES administration.users (Id);
ALTER TABLE public.short_urls_history
ADD COLUMN IF NOT EXISTS UserId INT NULL REFERENCES administration.users (Id);

View File

@ -1,5 +1,5 @@
import { Injectable, Provider } from '@angular/core'; import { Injectable, Provider } from '@angular/core';
import { merge, Observable } from 'rxjs'; import { forkJoin, merge, Observable } from 'rxjs';
import { import {
Create, Create,
Delete, Delete,
@ -48,13 +48,10 @@ export class ShortUrlsDataService
skip?: number | undefined, skip?: number | undefined,
take?: number | undefined take?: number | undefined
): Observable<QueryResult<ShortUrl>> { ): Observable<QueryResult<ShortUrl>> {
return this.apollo const query1 = this.apollo.query<{ shortUrls: QueryResult<ShortUrl> }>({
.query<{ shortUrls: QueryResult<ShortUrl> }>({
query: gql` query: gql`
query getShortUrls($filter: [ShortUrlFilter], $sort: [ShortUrlSort]) { query getShortUrls($filter: [ShortUrlFilter], $sort: [ShortUrlSort]) {
shortUrls(filter: $filter, sort: $sort) { shortUrls(filter: $filter, sort: $sort) {
count
totalCount
nodes { nodes {
id id
shortUrl shortUrl
@ -70,28 +67,61 @@ export class ShortUrlsDataService
id id
name name
} }
...DB_MODEL
} }
} }
} }
${DB_MODEL_FRAGMENT}
`, `,
variables: { variables: {
filter: [{ group: { deleted: { equal: false } } }, ...(filter ?? [])], filter: [{ group: { deleted: { equal: false } } }, ...(filter ?? [])],
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])], sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
skip: skip, skip,
take: take, take,
}, },
});
const query2 = this.apollo.query<{ shortUrls: QueryResult<ShortUrl> }>({
query: gql`
query getShortUrls($filter: [ShortUrlFilter], $sort: [ShortUrlSort]) {
shortUrls(filter: $filter, sort: $sort) {
nodes {
id
shortUrl
targetUrl
description
loadingScreen
visits
group {
id
name
}
domain {
id
name
}
}
}
}
`,
variables: {
filter: [{ group: { isNull: true } }, ...(filter ?? [])],
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
skip,
take,
},
});
return forkJoin([query1, query2]).pipe(
map(([result1, result2]) => {
const nodes = [
...result1.data.shortUrls.nodes,
...result2.data.shortUrls.nodes,
];
const uniqueNodes = Array.from(
new Map(nodes.map(node => [node.id, node])).values()
);
return { ...result1.data.shortUrls, nodes: uniqueNodes };
}) })
.pipe( );
catchError(err => {
this.spinner.hide();
throw err;
})
)
.pipe(map(result => result.data.shortUrls));
} }
loadById(id: number): Observable<ShortUrl> { loadById(id: number): Observable<ShortUrl> {