Compare commits
2 Commits
dev
...
#15_user_s
Author | SHA1 | Date | |
---|---|---|---|
0ed3cb846d | |||
d190d2f218 |
@ -120,7 +120,7 @@ class QueryABC(ObjectType):
|
||||
skip = None
|
||||
|
||||
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:
|
||||
in_filters = kwargs["filter"]
|
||||
|
@ -11,3 +11,6 @@ class GroupFilter(DbModelFilterABC):
|
||||
|
||||
self.add_field("name", StringFilter)
|
||||
self.add_field("description", StringFilter)
|
||||
|
||||
self.add_field("isNull", bool)
|
||||
self.add_field("isNotNull", bool)
|
||||
|
@ -39,8 +39,8 @@ input StringFilter {
|
||||
startsWith: String
|
||||
endsWith: String
|
||||
|
||||
isNull: String
|
||||
isNotNull: String
|
||||
isNull: Boolean
|
||||
isNotNull: Boolean
|
||||
}
|
||||
|
||||
input IntFilter {
|
||||
@ -51,8 +51,8 @@ input IntFilter {
|
||||
less: Int
|
||||
lessOrEqual: Int
|
||||
|
||||
isNull: Int
|
||||
isNotNull: Int
|
||||
isNull: Boolean
|
||||
isNotNull: Boolean
|
||||
in: [Int]
|
||||
notIn: [Int]
|
||||
}
|
||||
@ -61,8 +61,8 @@ input BooleanFilter {
|
||||
equal: Boolean
|
||||
notEqual: Int
|
||||
|
||||
isNull: Int
|
||||
isNotNull: Int
|
||||
isNull: Boolean
|
||||
isNotNull: Boolean
|
||||
}
|
||||
|
||||
input DateFilter {
|
||||
@ -78,8 +78,8 @@ input DateFilter {
|
||||
contains: String
|
||||
notContains: String
|
||||
|
||||
isNull: String
|
||||
isNotNull: String
|
||||
isNull: Boolean
|
||||
isNotNull: Boolean
|
||||
|
||||
in: [String]
|
||||
notIn: [String]
|
||||
|
@ -61,6 +61,9 @@ input GroupFilter {
|
||||
editor: IntFilter
|
||||
created: DateFilter
|
||||
updated: DateFilter
|
||||
|
||||
isNull: Boolean
|
||||
isNotNull: Boolean
|
||||
}
|
||||
|
||||
type GroupMutation {
|
||||
|
@ -1,8 +1,11 @@
|
||||
from typing import Optional
|
||||
|
||||
from api.route import Route
|
||||
from api_graphql.abc.mutation_abc import MutationABC
|
||||
from api_graphql.input.group_create_input import GroupCreateInput
|
||||
from api_graphql.input.group_update_input import GroupUpdateInput
|
||||
from core.configuration.feature_flags import FeatureFlags
|
||||
from core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from core.logger import APILogger
|
||||
from data.schemas.public.group import Group
|
||||
from data.schemas.public.group_dao import groupDao
|
||||
@ -75,6 +78,11 @@ class GroupMutation(MutationABC):
|
||||
group = Group(
|
||||
0,
|
||||
obj.name,
|
||||
(
|
||||
(await Route.get_user()).id
|
||||
if await FeatureFlags.has_feature(FeatureFlagsEnum.per_user_setup)
|
||||
else None
|
||||
),
|
||||
)
|
||||
gid = await groupDao.create(group)
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
from api.route import Route
|
||||
from api_graphql.abc.mutation_abc import MutationABC
|
||||
from api_graphql.input.short_url_create_input import ShortUrlCreateInput
|
||||
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 data.schemas.public.domain_dao import domainDao
|
||||
from data.schemas.public.group_dao import groupDao
|
||||
@ -57,6 +60,11 @@ class ShortUrlMutation(MutationABC):
|
||||
obj.group_id,
|
||||
obj.domain_id,
|
||||
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)
|
||||
return await shortUrlDao.get_by_id(nid)
|
||||
|
@ -1,7 +1,9 @@
|
||||
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_setting_input import UserSettingInput
|
||||
from core.logger import APILogger
|
||||
from core.string import first_to_lower
|
||||
from data.schemas.public.user_setting import UserSetting
|
||||
from data.schemas.public.user_setting_dao import userSettingsDao
|
||||
from data.schemas.system.setting_dao import settingsDao
|
||||
@ -13,13 +15,20 @@ logger = APILogger(__name__)
|
||||
class UserSettingMutation(MutationABC):
|
||||
def __init__(self):
|
||||
MutationABC.__init__(self, "UserSetting")
|
||||
self.mutation(
|
||||
"change",
|
||||
self.resolve_change,
|
||||
UserSettingInput,
|
||||
require_any_permission=[Permissions.settings_update],
|
||||
self.field(
|
||||
MutationFieldBuilder("change")
|
||||
.with_resolver(self.resolve_change)
|
||||
.with_change_broadcast(
|
||||
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
|
||||
)
|
||||
.with_input(UserSettingInput, "input")
|
||||
.with_require_any([], [self._x])
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def _x(ctx):
|
||||
return ctx.data.user_id == (await Route.get_user()).id
|
||||
|
||||
@staticmethod
|
||||
async def resolve_change(obj: UserSettingInput, *_):
|
||||
logger.debug(f"create new setting: {input}")
|
||||
|
@ -1,6 +1,6 @@
|
||||
from api_graphql.abc.db_history_model_query_abc import DbHistoryModelQueryABC
|
||||
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
||||
from api_graphql.require_any_resolvers import group_by_assignment_resolver
|
||||
from api_graphql.require_any_resolvers import by_assignment_resolver
|
||||
from data.schemas.public.group import Group
|
||||
from data.schemas.public.group_dao import groupDao
|
||||
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
||||
@ -21,7 +21,7 @@ class GroupHistoryQuery(DbHistoryModelQueryABC):
|
||||
[
|
||||
Permissions.groups,
|
||||
],
|
||||
[group_by_assignment_resolver],
|
||||
[by_assignment_resolver],
|
||||
)
|
||||
)
|
||||
self.set_field(
|
||||
|
@ -1,6 +1,6 @@
|
||||
from api_graphql.abc.db_model_query_abc import DbModelQueryABC
|
||||
from api_graphql.field.resolver_field_builder import ResolverFieldBuilder
|
||||
from api_graphql.require_any_resolvers import group_by_assignment_resolver
|
||||
from api_graphql.require_any_resolvers import by_assignment_resolver
|
||||
from data.schemas.public.group import Group
|
||||
from data.schemas.public.group_dao import groupDao
|
||||
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
|
||||
@ -21,7 +21,7 @@ class GroupQuery(DbModelQueryABC):
|
||||
[
|
||||
Permissions.groups,
|
||||
],
|
||||
[group_by_assignment_resolver],
|
||||
[by_assignment_resolver],
|
||||
)
|
||||
)
|
||||
self.set_field("roles", self._get_roles)
|
||||
|
@ -11,7 +11,12 @@ from api_graphql.filter.permission_filter import PermissionFilter
|
||||
from api_graphql.filter.role_filter import RoleFilter
|
||||
from api_graphql.filter.short_url_filter import ShortUrlFilter
|
||||
from api_graphql.filter.user_filter import UserFilter
|
||||
from api_graphql.require_any_resolvers import group_by_assignment_resolver
|
||||
from 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_dao import apiKeyDao
|
||||
from data.schemas.administration.user import User
|
||||
@ -109,7 +114,8 @@ class Query(QueryABC):
|
||||
]
|
||||
)
|
||||
)
|
||||
self.field(
|
||||
|
||||
group_field = (
|
||||
DaoFieldBuilder("groups")
|
||||
.with_dao(groupDao)
|
||||
.with_filter(GroupFilter)
|
||||
@ -120,15 +126,33 @@ class Query(QueryABC):
|
||||
Permissions.short_urls_create,
|
||||
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(
|
||||
group_field
|
||||
)
|
||||
|
||||
short_url_field = (
|
||||
DaoFieldBuilder("shortUrls")
|
||||
.with_dao(shortUrlDao)
|
||||
.with_filter(ShortUrlFilter)
|
||||
.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(
|
||||
@ -202,3 +226,7 @@ class Query(QueryABC):
|
||||
if "key" in kwargs:
|
||||
return [await featureFlagDao.find_by_key(kwargs["key"])]
|
||||
return await featureFlagDao.get_all()
|
||||
|
||||
@staticmethod
|
||||
async def _resolve_default_user_filter(*args, **kwargs) -> dict:
|
||||
return {"user": {"id": {"equal": (await Route.get_user()).id}}}
|
||||
|
@ -1,10 +1,12 @@
|
||||
from api_graphql.service.collection_result import CollectionResult
|
||||
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 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):
|
||||
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)
|
||||
]
|
||||
|
||||
ctx.data.nodes = [
|
||||
node
|
||||
return all(
|
||||
(await node.group) is not None and (await node.group).id in filtered_groups
|
||||
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)
|
||||
|
@ -1,20 +1,38 @@
|
||||
from typing import Union
|
||||
|
||||
from core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from core.environment import Environment
|
||||
from data.schemas.system.feature_flag_dao import featureFlagDao
|
||||
|
||||
|
||||
class FeatureFlags:
|
||||
_flags = {
|
||||
FeatureFlagsEnum.version_endpoint.value: True, # 15.01.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 = [
|
||||
FeatureFlagsEnum.per_user_setup.value,
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def overwrite_flag(key: str):
|
||||
return key in FeatureFlags._overwrite_flags
|
||||
|
||||
@staticmethod
|
||||
def get_default(key: FeatureFlagsEnum) -> bool:
|
||||
return FeatureFlags._flags[key.value]
|
||||
|
||||
@staticmethod
|
||||
async def has_feature(key: FeatureFlagsEnum) -> bool:
|
||||
value = await featureFlagDao.find_by_key(key.value)
|
||||
if value is None:
|
||||
return False
|
||||
async def has_feature(key: Union[str, FeatureFlagsEnum]) -> bool:
|
||||
key_value = key.value if isinstance(key, FeatureFlagsEnum) else key
|
||||
|
||||
return value.value
|
||||
value = await featureFlagDao.find_by_key(key_value)
|
||||
return (
|
||||
value.value
|
||||
if value
|
||||
else FeatureFlags.get_default(FeatureFlagsEnum(key_value))
|
||||
)
|
||||
|
@ -2,5 +2,6 @@ from enum import Enum
|
||||
|
||||
|
||||
class FeatureFlagsEnum(Enum):
|
||||
# modules
|
||||
version_endpoint = "VersionEndpoint"
|
||||
technical_demo_banner = "TechnicalDemoBanner"
|
||||
per_user_setup = "PerUserSetup"
|
||||
|
@ -18,6 +18,22 @@ T_DBM = TypeVar("T_DBM", bound=DbModelABC)
|
||||
|
||||
class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
_external_fields: dict[str, ExternalDataTempTableBuilder] = {}
|
||||
_operators = [
|
||||
"equal",
|
||||
"notEqual",
|
||||
"greater",
|
||||
"greaterOrEqual",
|
||||
"less",
|
||||
"lessOrEqual",
|
||||
"isNull",
|
||||
"isNotNull",
|
||||
"contains",
|
||||
"notContains",
|
||||
"startsWith",
|
||||
"endsWith",
|
||||
"in",
|
||||
"notIn",
|
||||
]
|
||||
|
||||
@abstractmethod
|
||||
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:
|
||||
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:
|
||||
external_field_table_deps.extend(eftd)
|
||||
|
||||
@ -670,6 +688,8 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
isinstance(values, dict) or isinstance(values, list)
|
||||
) and not attr in self.__foreign_tables:
|
||||
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:
|
||||
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)
|
||||
)
|
||||
f_conditions.append(f"({' OR '.join(sub_conditions)})")
|
||||
elif attr in self._operators:
|
||||
conditions.append(f"{self._build_condition(db_name, attr, values)}")
|
||||
else:
|
||||
f_conditions.append(self._get_value_validation_sql(db_name, values))
|
||||
|
||||
@ -711,10 +733,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
return conditions
|
||||
|
||||
def _build_foreign_conditions(
|
||||
self, table: str, values: dict
|
||||
self, base_attr: str, table: str, values: dict
|
||||
) -> (list[str], list[str]):
|
||||
"""
|
||||
Build SQL conditions for foreign key references
|
||||
:param base_attr: Base attribute name
|
||||
:param table: Foreign table name
|
||||
:param values: Filter values
|
||||
: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:
|
||||
foreign_table = self.__foreign_tables[attr]
|
||||
sub_conditions, eftd = self._build_foreign_conditions(
|
||||
foreign_table, sub_values
|
||||
attr, foreign_table, sub_values
|
||||
)
|
||||
if len(eftd) > 0:
|
||||
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}"
|
||||
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:
|
||||
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)
|
||||
)
|
||||
conditions.append(f"({' OR '.join(sub_conditions)})")
|
||||
elif attr in self._operators:
|
||||
conditions.append(f"{self._build_condition(db_name, attr, sub_values)}")
|
||||
else:
|
||||
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):
|
||||
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:
|
||||
field_selector = self.__db_names[field]
|
||||
|
||||
@ -850,9 +881,9 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
elif operator == "lessOrEqual":
|
||||
return f"{db_name} <= {sql_value}"
|
||||
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":
|
||||
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":
|
||||
return f"{db_name} LIKE '%{value}%'"
|
||||
elif operator == "notContains":
|
||||
|
@ -1,6 +1,8 @@
|
||||
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
|
||||
|
||||
@ -10,6 +12,7 @@ class Group(DbModelABC):
|
||||
self,
|
||||
id: SerialId,
|
||||
name: str,
|
||||
user_id: Optional[SerialId] = None,
|
||||
deleted: bool = False,
|
||||
editor_id: Optional[SerialId] = None,
|
||||
created: Optional[datetime] = None,
|
||||
@ -17,6 +20,7 @@ class Group(DbModelABC):
|
||||
):
|
||||
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
||||
self._name = name
|
||||
self._user_id = user_id
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@ -25,3 +29,17 @@ class Group(DbModelABC):
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
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
|
||||
|
@ -11,6 +11,9 @@ class GroupDao(DbModelDaoABC[Group]):
|
||||
DbModelDaoABC.__init__(self, __name__, Group, "public.groups")
|
||||
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:
|
||||
result = await self._db.select_map(
|
||||
f"SELECT * FROM {self._table_name} WHERE Name = '{name}'"
|
||||
|
@ -18,6 +18,7 @@ class ShortUrl(DbModelABC):
|
||||
group_id: Optional[SerialId],
|
||||
domain_id: Optional[SerialId],
|
||||
loading_screen: Optional[str] = None,
|
||||
user_id: Optional[SerialId] = None,
|
||||
deleted: bool = False,
|
||||
editor_id: Optional[SerialId] = None,
|
||||
created: Optional[datetime] = None,
|
||||
@ -34,6 +35,8 @@ class ShortUrl(DbModelABC):
|
||||
loading_screen = False
|
||||
self._loading_screen = loading_screen
|
||||
|
||||
self._user_id = user_id
|
||||
|
||||
@property
|
||||
def short_url(self) -> str:
|
||||
return self._short_url
|
||||
@ -106,6 +109,20 @@ class ShortUrl(DbModelABC):
|
||||
def loading_screen(self, value: Optional[str]):
|
||||
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:
|
||||
return {
|
||||
"id": self.id,
|
||||
|
@ -18,5 +18,8 @@ class ShortUrlDao(DbModelDaoABC[ShortUrl]):
|
||||
self.reference("domain", "id", ShortUrl.domain_id, "public.domains")
|
||||
self.attribute(ShortUrl.loading_screen, bool)
|
||||
|
||||
self.attribute(ShortUrl.user_id, int)
|
||||
self.reference("user", "id", ShortUrl.user_id, "administration.users")
|
||||
|
||||
|
||||
shortUrlDao = ShortUrlDao()
|
||||
|
11
api/src/data/scripts/2025-04-18-12-15-user-spaces.sql
Normal file
11
api/src/data/scripts/2025-04-18-12-15-user-spaces.sql
Normal 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);
|
@ -21,6 +21,7 @@ class FeatureFlagsSeeder(DataSeederABC):
|
||||
x.value: FeatureFlags.get_default(x) for x in FeatureFlagsEnum
|
||||
}
|
||||
|
||||
# Create new feature flags
|
||||
to_create = [
|
||||
FeatureFlag(0, x, possible_feature_flags[x])
|
||||
for x in possible_feature_flags.keys()
|
||||
@ -31,6 +32,19 @@ class FeatureFlagsSeeder(DataSeederABC):
|
||||
to_create_dicts = {x.key: x.value for x in to_create}
|
||||
logger.debug(f"Created feature flags: {to_create_dicts}")
|
||||
|
||||
# Update existing feature flags if they can be overwritten and have a different value
|
||||
to_update = [
|
||||
FeatureFlag(x.id, x.key, possible_feature_flags[x.key])
|
||||
for x in feature_flags
|
||||
if FeatureFlags.overwrite_flag(x.key)
|
||||
and x.value != possible_feature_flags[x.key]
|
||||
]
|
||||
if len(to_update) > 0:
|
||||
await featureFlagDao.update_many(to_update)
|
||||
to_update_dicts = {x.key: x.value for x in to_update}
|
||||
logger.debug(f"Updated feature flags: {to_update_dicts}")
|
||||
|
||||
# Delete feature flags that are no longer defined
|
||||
to_delete = [
|
||||
x for x in feature_flags if x.key not in possible_feature_flags.keys()
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable, Provider } from '@angular/core';
|
||||
import { merge, Observable } from 'rxjs';
|
||||
import { forkJoin, merge, Observable } from 'rxjs';
|
||||
import {
|
||||
Create,
|
||||
Delete,
|
||||
@ -48,50 +48,80 @@ export class ShortUrlsDataService
|
||||
skip?: number | undefined,
|
||||
take?: number | undefined
|
||||
): Observable<QueryResult<ShortUrl>> {
|
||||
return this.apollo
|
||||
.query<{ shortUrls: QueryResult<ShortUrl> }>({
|
||||
query: gql`
|
||||
query getShortUrls($filter: [ShortUrlFilter], $sort: [ShortUrlSort]) {
|
||||
shortUrls(filter: $filter, sort: $sort) {
|
||||
count
|
||||
totalCount
|
||||
nodes {
|
||||
const query1 = 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
|
||||
shortUrl
|
||||
targetUrl
|
||||
description
|
||||
loadingScreen
|
||||
visits
|
||||
group {
|
||||
id
|
||||
name
|
||||
}
|
||||
domain {
|
||||
id
|
||||
name
|
||||
}
|
||||
|
||||
...DB_MODEL
|
||||
name
|
||||
}
|
||||
domain {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
filter: [{ group: { deleted: { equal: false } } }, ...(filter ?? [])],
|
||||
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
|
||||
skip,
|
||||
take,
|
||||
},
|
||||
});
|
||||
|
||||
${DB_MODEL_FRAGMENT}
|
||||
`,
|
||||
variables: {
|
||||
filter: [{ group: { deleted: { equal: false } } }, ...(filter ?? [])],
|
||||
sort: [{ id: SortOrder.DESC }, ...(sort ?? [])],
|
||||
skip: skip,
|
||||
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> {
|
||||
|
@ -3,6 +3,7 @@ import { BehaviorSubject } from 'rxjs';
|
||||
import { MenuElement } from 'src/app/model/view/menu-element';
|
||||
import { AuthService } from 'src/app/service/auth.service';
|
||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
||||
import { FeatureFlagService } from 'src/app/service/feature-flag.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -11,7 +12,10 @@ export class SidebarService {
|
||||
visible$ = new BehaviorSubject<boolean>(true);
|
||||
elements$ = new BehaviorSubject<MenuElement[]>([]);
|
||||
|
||||
constructor(private auth: AuthService) {
|
||||
constructor(
|
||||
private auth: AuthService,
|
||||
private featureFlags: FeatureFlagService
|
||||
) {
|
||||
this.auth.user$.subscribe(async () => {
|
||||
await this.setElements();
|
||||
});
|
||||
@ -40,16 +44,19 @@ export class SidebarService {
|
||||
label: 'common.groups',
|
||||
icon: 'pi pi-tags',
|
||||
routerLink: ['/admin/groups'],
|
||||
visible: await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups]),
|
||||
visible:
|
||||
(await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups])) ||
|
||||
(await this.featureFlags.get('PerUserSetup')),
|
||||
},
|
||||
{
|
||||
label: 'common.urls',
|
||||
icon: 'pi pi-tag',
|
||||
routerLink: ['/admin/urls'],
|
||||
visible: await this.auth.hasAnyPermissionLazy([
|
||||
PermissionsEnum.shortUrls,
|
||||
PermissionsEnum.shortUrlsByAssignment,
|
||||
]),
|
||||
visible:
|
||||
(await this.auth.hasAnyPermissionLazy([
|
||||
PermissionsEnum.shortUrls,
|
||||
PermissionsEnum.shortUrlsByAssignment,
|
||||
])) || (await this.featureFlags.get('PerUserSetup')),
|
||||
},
|
||||
await this.sectionAdmin(),
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user