diff --git a/api/src/api_graphql/abc/query_abc.py b/api/src/api_graphql/abc/query_abc.py index 0ea4d6b..c3d2ca6 100644 --- a/api/src/api_graphql/abc/query_abc.py +++ b/api/src/api_graphql/abc/query_abc.py @@ -213,7 +213,6 @@ class QueryABC(ObjectType): f"{field.name}: {field.input_type.__name__} {kwargs[field.input_key]}" ) input_obj = field.input_type(kwargs[field.input_key]) - del kwargs[field.input_key] return await resolver_wrapper(input_obj, mutation, info, **kwargs) diff --git a/api/src/api_graphql/field/mutation_field_builder.py b/api/src/api_graphql/field/mutation_field_builder.py index bf33dda..b6b7576 100644 --- a/api/src/api_graphql/field/mutation_field_builder.py +++ b/api/src/api_graphql/field/mutation_field_builder.py @@ -58,7 +58,7 @@ class MutationFieldBuilder(FieldBuilderABC): self._resolver = resolver_wrapper return self - def with_input(self, input_type: Type[InputABC], input_key: str = None) -> Self: + def with_input(self, input_type: Type[InputABC], input_key: str = "input") -> Self: self._input_type = input_type self._input_key = input_key return self diff --git a/api/src/api_graphql/mutation.py b/api/src/api_graphql/mutation.py index 59b2a5c..2514018 100644 --- a/api/src/api_graphql/mutation.py +++ b/api/src/api_graphql/mutation.py @@ -46,11 +46,14 @@ class Mutation(MutationABC): self.add_mutation_type( "group", "Group", - require_any_permission=[ - Permissions.groups_create, - Permissions.groups_update, - Permissions.groups_delete, - ], + require_any=( + [ + Permissions.groups_create, + Permissions.groups_update, + Permissions.groups_delete, + ], + [by_user_setup_mutation], + ), ) self.add_mutation_type( "shortUrl", diff --git a/api/src/api_graphql/mutations/group_mutation.py b/api/src/api_graphql/mutations/group_mutation.py index 6f0b1dd..16addbd 100644 --- a/api/src/api_graphql/mutations/group_mutation.py +++ b/api/src/api_graphql/mutations/group_mutation.py @@ -2,8 +2,10 @@ from typing import Optional 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.group_create_input import GroupCreateInput 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 @@ -20,27 +22,30 @@ class GroupMutation(MutationABC): def __init__(self): MutationABC.__init__(self, "Group") - self.mutation( - "create", - self.resolve_create, - GroupCreateInput, - require_any_permission=[Permissions.groups_create], + self.field( + MutationFieldBuilder("create") + .with_resolver(self.resolve_create) + .with_input(GroupCreateInput) + .with_require_any([Permissions.groups_create], [by_user_setup_mutation]) ) - self.mutation( - "update", - self.resolve_update, - GroupUpdateInput, - require_any_permission=[Permissions.groups_update], + + self.field( + MutationFieldBuilder("update") + .with_resolver(self.resolve_update) + .with_input(GroupUpdateInput) + .with_require_any([Permissions.groups_update], [by_user_setup_mutation]) ) - self.mutation( - "delete", - self.resolve_delete, - require_any_permission=[Permissions.groups_delete], + + self.field( + MutationFieldBuilder("delete") + .with_resolver(self.resolve_delete) + .with_require_any([Permissions.groups_delete], [by_user_setup_mutation]) ) - self.mutation( - "restore", - self.resolve_restore, - require_any_permission=[Permissions.groups_delete], + + self.field( + MutationFieldBuilder("restore") + .with_resolver(self.resolve_restore) + .with_require_any([Permissions.groups_delete], [by_user_setup_mutation]) ) @staticmethod @@ -75,6 +80,10 @@ class GroupMutation(MutationABC): async def resolve_create(cls, obj: GroupCreateInput, *_): logger.debug(f"create group: {obj.__dict__}") + already_exists = await groupDao.find_by({Group.name: obj.name}) + if len(already_exists) > 0: + raise ValueError(f"Group {obj.name} already exists") + group = Group( 0, obj.name, @@ -98,6 +107,12 @@ class GroupMutation(MutationABC): raise ValueError(f"Group with id {obj.id} not found") if obj.name is not None: + already_exists = await groupDao.find_by( + {Group.name: obj.name, Group.id: {"ne": obj.id}} + ) + if len(already_exists) > 0: + raise ValueError(f"Group {obj.name} already exists") + group = await groupDao.get_by_id(obj.id) group.name = obj.name await groupDao.update(group) diff --git a/api/src/api_graphql/mutations/short_url_mutation.py b/api/src/api_graphql/mutations/short_url_mutation.py index f39cefa..b471fba 100644 --- a/api/src/api_graphql/mutations/short_url_mutation.py +++ b/api/src/api_graphql/mutations/short_url_mutation.py @@ -58,6 +58,10 @@ class ShortUrlMutation(MutationABC): async def resolve_create(obj: ShortUrlCreateInput, *_): logger.debug(f"create short_url: {obj.__dict__}") + already_exists = await shortUrlDao.find_by({ShortUrl.short_url: obj.short_url}) + if len(already_exists) > 0: + raise ValueError(f"Short URL {obj.short_url} already exists") + short_url = ShortUrl( 0, obj.short_url, @@ -82,6 +86,11 @@ class ShortUrlMutation(MutationABC): short_url = await shortUrlDao.get_by_id(obj.id) if obj.short_url is not None: + already_exists = await shortUrlDao.find_by( + {ShortUrl.short_url: obj.short_url} + ) + if len(already_exists) > 0: + raise ValueError(f"Short URL {obj.short_url} already exists") short_url.short_url = obj.short_url if obj.target_url is not None: diff --git a/api/src/api_graphql/require_any_resolvers.py b/api/src/api_graphql/require_any_resolvers.py index e688fd9..8eea17f 100644 --- a/api/src/api_graphql/require_any_resolvers.py +++ b/api/src/api_graphql/require_any_resolvers.py @@ -36,11 +36,8 @@ async def by_user_setup_resolver(ctx: QueryContext) -> bool: 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) + return all(x.user_id == ctx.user.id for x in ctx.data.nodes) async def by_user_setup_mutation(ctx: QueryContext) -> bool: - 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) + return await FeatureFlags.has_feature(FeatureFlagsEnum.per_user_setup) diff --git a/api/src/api_graphql/service/query_context.py b/api/src/api_graphql/service/query_context.py index 40f85da..003d73b 100644 --- a/api/src/api_graphql/service/query_context.py +++ b/api/src/api_graphql/service/query_context.py @@ -15,6 +15,7 @@ class QueryContext: data: Any, user: Optional[User], user_permissions: Optional[list[Permissions]], + is_mutation: bool = False, *args, **kwargs ): @@ -31,11 +32,17 @@ class QueryContext: self._resolve_info = arg continue - self._filter = kwargs.get("filter", {}) + self._filter = kwargs.get("filters", {}) self._sort = kwargs.get("sort", {}) self._skip = get_value(kwargs, "skip", int) self._take = get_value(kwargs, "take", int) + self._input = kwargs.get("input", None) + self._args = args + self._kwargs = kwargs + + self._is_mutation = is_mutation + @property def data(self): return self._data @@ -64,5 +71,21 @@ class QueryContext: def take(self) -> Optional[int]: return self._take + @property + def input(self) -> Optional[Any]: + return self._input + + @property + def args(self) -> tuple: + return self._args + + @property + def kwargs(self) -> dict: + return self._kwargs + + @property + def is_mutation(self) -> bool: + return self._is_mutation + def has_permission(self, permission: Permissions) -> bool: return permission.value in self._user_permissions diff --git a/web/src/app/modules/admin/domains/domains.data.service.ts b/web/src/app/modules/admin/domains/domains.data.service.ts index 162f453..2af9aca 100644 --- a/web/src/app/modules/admin/domains/domains.data.service.ts +++ b/web/src/app/modules/admin/domains/domains.data.service.ts @@ -228,7 +228,7 @@ export class DomainsDataService return this.apollo .mutate<{ domain: { delete: boolean } }>({ mutation: gql` - mutation deleteDomain($id: ID!) { + mutation deleteDomain($id: Int!) { domain { delete(id: $id) } @@ -251,7 +251,7 @@ export class DomainsDataService return this.apollo .mutate<{ domain: { restore: boolean } }>({ mutation: gql` - mutation restoreDomain($id: ID!) { + mutation restoreDomain($id: Int!) { domain { restore(id: $id) } diff --git a/web/src/app/modules/admin/groups/form-page/group-form-page.component.html b/web/src/app/modules/admin/groups/form-page/group-form-page.component.html index 150c081..8f0373c 100644 --- a/web/src/app/modules/admin/groups/form-page/group-form-page.component.html +++ b/web/src/app/modules/admin/groups/form-page/group-form-page.component.html @@ -27,8 +27,9 @@ type="text" formControlName="name"/> -
+