diff --git a/backend/apps/ecommerce/tests.py b/backend/apps/ecommerce/tests.py index a161cad1b..8846542ca 100644 --- a/backend/apps/ecommerce/tests.py +++ b/backend/apps/ecommerce/tests.py @@ -112,7 +112,7 @@ def setUp(self) -> None: MembershipFactory( user=self.staff_user, organization=self.organization, - group=self.organization.primary_group, + group=self.organization.member_group, ) self.total_quantity = 5 self.max_buyable_quantity = 2 diff --git a/backend/apps/forms/signals.py b/backend/apps/forms/signals.py index d94907b67..ffe107665 100644 --- a/backend/apps/forms/signals.py +++ b/backend/apps/forms/signals.py @@ -9,6 +9,6 @@ def handle_new_form(sender, instance: Form, created: bool, **kwargs) -> None: if created: perms = ["forms.manage_form", "forms.change_form", "forms.delete_form"] - group = instance.organization.hr_group.group + group = instance.organization.admin_group.group for perm in perms: assign_perm(perm, group, instance) diff --git a/backend/apps/forms/tests.py b/backend/apps/forms/tests.py index b079384a2..8090061b7 100644 --- a/backend/apps/forms/tests.py +++ b/backend/apps/forms/tests.py @@ -31,7 +31,7 @@ def setUp(self) -> None: MembershipFactory( user=self.authorized_user, organization=self.organization, - group=self.organization.hr_group, + group=self.organization.admin_group, ) # Create the form diff --git a/backend/apps/organizations/migrations/0026_auto_20210426_1802.py b/backend/apps/organizations/migrations/0026_auto_20210426_1802.py index 14c6031c3..c01b9bd7c 100644 --- a/backend/apps/organizations/migrations/0026_auto_20210426_1802.py +++ b/backend/apps/organizations/migrations/0026_auto_20210426_1802.py @@ -18,7 +18,8 @@ def set_primary_groups(apps, schema_editor): created = True if organization.hr_group is None: hr_group = ResponsibleGroup.objects.create( - name="HR", description=f"HR-gruppen til {organization.name}. Tillatelser for å se og behandle søknader." + name="HR", + description=f"HR-gruppen til {organization.name}. Tillatelser for å se og behandle søknader.", ) organization.hr_group = hr_group created = True diff --git a/backend/apps/organizations/migrations/0034_alter_organization_options.py b/backend/apps/organizations/migrations/0034_alter_organization_options.py index 80be66ee5..997b0f88a 100644 --- a/backend/apps/organizations/migrations/0034_alter_organization_options.py +++ b/backend/apps/organizations/migrations/0034_alter_organization_options.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.16 on 2023-01-23 18:47 +# Generated by Django 3.2.16 on 2022-11-21 20:19 from django.db import migrations diff --git a/backend/apps/organizations/models.py b/backend/apps/organizations/models.py index d0f66d32a..3c4476091 100644 --- a/backend/apps/organizations/models.py +++ b/backend/apps/organizations/models.py @@ -4,7 +4,7 @@ from django.db import models from django.db.models import UniqueConstraint -from apps.permissions.constants import HR_TYPE, PRIMARY_TYPE +from apps.permissions.constants import ADMIN_GROUP_TYPE, MEMBER_GROUP_TYPE from apps.permissions.models import ResponsibleGroup @@ -28,7 +28,8 @@ class Organization(models.Model): # Permission groups # All members are added to the primary group # Members can be added to groups programatically - # The HR-group has the "forms.manage_form" permission, allowing them to view and manage responses to e.g. listings. + # The ADMIN-group has the "forms.manage_form" permission, allowing them to view and manage responses to e.g. + # listings. # The primary group is intended to act as a group for organizations who need any kind of # special permission, e.g. hyttestyret # Or if we wish to limit the creation of events or listings to certain organizations. @@ -42,16 +43,16 @@ class Organization(models.Model): ) @property - def hr_group(self) -> Optional["ResponsibleGroup"]: + def admin_group(self) -> Optional["ResponsibleGroup"]: try: - return self.permission_groups.get(group_type=HR_TYPE) + return self.permission_groups.get(group_type=ADMIN_GROUP_TYPE) except ResponsibleGroup.DoesNotExist: return None @property - def primary_group(self) -> Optional["ResponsibleGroup"]: + def member_group(self) -> Optional["ResponsibleGroup"]: try: - return self.permission_groups.get(group_type=PRIMARY_TYPE) + return self.permission_groups.get(group_type=MEMBER_GROUP_TYPE) except ResponsibleGroup.DoesNotExist: return None diff --git a/backend/apps/organizations/mutations.py b/backend/apps/organizations/mutations.py index 9b056f676..f8df8287e 100644 --- a/backend/apps/organizations/mutations.py +++ b/backend/apps/organizations/mutations.py @@ -2,12 +2,12 @@ from django.utils.text import slugify from decorators import permission_required -from apps.users.types import UserType from apps.permissions.models import ResponsibleGroup from apps.organizations import permissions as perms from apps.organizations.models import Membership, Organization from apps.organizations.types import MembershipType, OrganizationType +from apps.users.models import User def get_organization_from_data(*_, membership_data, **kwargs) -> Organization: @@ -47,7 +47,7 @@ class Arguments: id = graphene.ID(required=True) organization_data = OrganizationInput(required=False) - @permission_required("organizations.manage_organization") + @permission_required("organizations.change_organization") def mutate(self, info, id, organization_data=None): organization = Organization.objects.get(pk=id) user = info.context.user @@ -111,12 +111,77 @@ def mutate(self, _, membership_data): return AssignMembership(membership=membership, ok=True) -class RemoveMembership(graphene.Mutation): - removed_member = graphene.Field(UserType) +class MembershipInputWithUsername(graphene.InputObjectType): + username = graphene.String() + organization_id = graphene.ID() + group_id = graphene.ID() + + +class AssignMembershipWithUsername(graphene.Mutation): + membership = graphene.Field(MembershipType) ok = graphene.Boolean() class Arguments: - member_id = graphene.ID() + membership_data = MembershipInputWithUsername(required=True) + + @permission_required("organizations.manage_organization", fn=get_organization_from_data) + def mutate(self, _, membership_data): + organization = Organization.objects.prefetch_related("permission_groups").get( + pk=membership_data["organization_id"] + ) + + try: + group = organization.permission_groups.get(pk=membership_data.get("group_id")) + except ResponsibleGroup.DoesNotExist: + return AssignMembershipWithUsername(membership=None, ok=False) + + try: + user_id = User.objects.get(username=membership_data["username"]).id + except User.DoesNotExist: + return AssignMembershipWithUsername(membership=None, ok=False) + + membership = Membership( + organization_id=membership_data["organization_id"], + user_id=user_id, + group=group, + ) + membership.save() + return AssignMembershipWithUsername(membership=membership, ok=True) - def mutate(self, info, member_id): - raise NotImplementedError("Denne funksjonaliteten er ikke implementert.") + +class DeleteMembership(graphene.Mutation): + membership = graphene.Field(MembershipType) + ok = graphene.Boolean() + + class Arguments: + membership_id = graphene.ID() + + @permission_required("organizations.manage_organization") + def mutate(self, info, membership_id): + membership = Membership.objects.get(pk=membership_id) + membership.delete() + + ok = True + return DeleteMembership(ok=ok) + + +class ChangeMembershipInput(graphene.InputObjectType): + membership_id = graphene.ID() + group_id = graphene.ID() + + +class ChangeMembership(graphene.Mutation): + membership = graphene.Field(MembershipType) + ok = graphene.Boolean() + + class Arguments: + membership_data = ChangeMembershipInput(required=True) + + @permission_required("organizations.manage_organization") + def mutate(self, info, membership_data): + membership = Membership.objects.get(pk=membership_data["membership_id"]) + membership.group_id = membership_data["group_id"] + membership.save() + + ok = True + return ChangeMembership(membership=membership, ok=ok) diff --git a/backend/apps/organizations/schema.py b/backend/apps/organizations/schema.py index 590470737..353e18da7 100644 --- a/backend/apps/organizations/schema.py +++ b/backend/apps/organizations/schema.py @@ -3,9 +3,12 @@ from .mutations import ( AssignMembership, + AssignMembershipWithUsername, + DeleteMembership, CreateOrganization, DeleteOrganization, UpdateOrganization, + ChangeMembership, ) from .resolvers import MembershipResolvers, OrganizationResolvers from .types import MembershipType, OrganizationType @@ -17,6 +20,9 @@ class OrganizationMutations(graphene.ObjectType): delete_organization = DeleteOrganization.Field() assign_membership = AssignMembership.Field() + delete_membership = DeleteMembership.Field() + change_membership = ChangeMembership.Field() + assign_membership_with_username = AssignMembershipWithUsername.Field() class OrganizationQueries(graphene.ObjectType, OrganizationResolvers, MembershipResolvers): diff --git a/backend/apps/organizations/signals.py b/backend/apps/organizations/signals.py index d7dfb07a5..6f7b546ff 100644 --- a/backend/apps/organizations/signals.py +++ b/backend/apps/organizations/signals.py @@ -7,11 +7,11 @@ from apps.organizations.models import Membership, Organization from apps.permissions.constants import ( - HR_GROUP_NAME, - HR_TYPE, + ADMIN_GROUP_NAME, + ADMIN_GROUP_TYPE, ORGANIZATION, - PRIMARY_GROUP_NAME, - PRIMARY_TYPE, + MEMBER_GROUP_NAME, + MEMBER_GROUP_TYPE, ) from apps.permissions.models import ResponsibleGroup @@ -19,7 +19,7 @@ @receiver(post_save, sender=Membership) def handle_new_member(instance: Membership, **kwargs): optional_group: Optional[ResponsibleGroup] = instance.group - group: Group = instance.organization.primary_group.group + group: Group = instance.organization.member_group.group org_group: Group = Group.objects.get(name=ORGANIZATION) user = instance.user user.groups.add(org_group) @@ -31,7 +31,7 @@ def handle_new_member(instance: Membership, **kwargs): @receiver(pre_delete, sender=Membership) def handle_removed_member(instance: Membership, **kwargs): - group: Group = instance.organization.primary_group.group + group: Group = instance.organization.member_group.group org_group: Group = Group.objects.get(name=ORGANIZATION) user = instance.user if group: @@ -44,19 +44,21 @@ def handle_removed_member(instance: Membership, **kwargs): @receiver(post_save, sender=Organization) def create_default_groups(instance: Organization, created, **kwargs): """ - Creates and assigns a primary group and HR group to members of the organization. + Creates and assigns a primary group and ADMIN group to members of the organization. """ if created: ResponsibleGroup.objects.create( - name=PRIMARY_GROUP_NAME, + name=f"{instance.name}:{MEMBER_GROUP_NAME}", description=f"Medlemmer av {instance.name}.", organization=instance, - group_type=PRIMARY_TYPE, + group_type=MEMBER_GROUP_TYPE, ) - hr_group = ResponsibleGroup.objects.create( - name=HR_GROUP_NAME, - description=f"HR-gruppen til {instance.name}. Tillatelser for å se og behandle søknader.", + admin_group = ResponsibleGroup.objects.create( + name=f"{instance.name}:{ADMIN_GROUP_NAME}", + description=f"ADMIN-gruppen til {instance.name}. Tillatelser for å se og behandle søknader og medlemmer.", organization=instance, - group_type=HR_TYPE, + group_type=ADMIN_GROUP_TYPE, ) - assign_perm("forms.add_form", hr_group.group) + print(admin_group.group, instance) + assign_perm("forms.add_form", admin_group.group) + # assign_perm("organizations.manage_organization", admin_group.group, instance) diff --git a/backend/apps/organizations/types.py b/backend/apps/organizations/types.py index 0afeb43cd..2c9f10b4e 100644 --- a/backend/apps/organizations/types.py +++ b/backend/apps/organizations/types.py @@ -14,8 +14,8 @@ class OrganizationType(DjangoObjectType): absolute_slug = graphene.String() listings = graphene.List(NonNull(ListingType)) - primary_group = graphene.Field(source="primary_group", type=ResponsibleGroupType) - hr_group = graphene.Field(source="hr_group", type=ResponsibleGroupType) + member_group = graphene.Field(source="member_group", type=ResponsibleGroupType) + admin_group = graphene.Field(source="admin_group", type=ResponsibleGroupType) class Meta: model = Organization @@ -30,8 +30,8 @@ class Meta: "users", "events", "logo_url", - "primary_group", - "hr_group", + "member_group", + "admin_group", ] @staticmethod diff --git a/backend/apps/permissions/constants.py b/backend/apps/permissions/constants.py index 230f8a70c..77d632f61 100644 --- a/backend/apps/permissions/constants.py +++ b/backend/apps/permissions/constants.py @@ -3,14 +3,14 @@ DefaultPermissionsType = Final[list[tuple[str, str]]] # Default ResponsibleGroup types -PRIMARY_TYPE: Literal["PRIMARY"] = "PRIMARY" -HR_TYPE: Literal["HR"] = "HR" +MEMBER_GROUP_TYPE: Literal["MEMBER"] = "MEMBER" +ADMIN_GROUP_TYPE: Literal["ADMIN"] = "ADMIN" ORGANIZATION: Final = "Organization member" INDOK: Final = "Indøk" REGISTERED_USER: Final = "Registered user" -PRIMARY_GROUP_NAME: Final = "Medlem" -HR_GROUP_NAME: Final = "HR" +MEMBER_GROUP_NAME: Final = "Medlem" +ADMIN_GROUP_NAME: Final = "Administrator" DEFAULT_ORGANIZATION_PERMISSIONS: DefaultPermissionsType = [ ("events", "add_event"), diff --git a/backend/apps/permissions/migrations/0003_auto_20210824_1213.py b/backend/apps/permissions/migrations/0003_auto_20210824_1213.py index 9834f6dbd..6195edc49 100644 --- a/backend/apps/permissions/migrations/0003_auto_20210824_1213.py +++ b/backend/apps/permissions/migrations/0003_auto_20210824_1213.py @@ -4,7 +4,7 @@ from django.db import migrations from django.db.models.query_utils import Q -from apps.permissions.constants import PRIMARY_GROUP_NAME, HR_GROUP_NAME +from apps.permissions.constants import MEMBER_GROUP_NAME, ADMIN_GROUP_NAME if TYPE_CHECKING: from apps.organizations import models @@ -19,14 +19,14 @@ def improve_group_legibility(apps, _): responsible_group: "ResponsibleGroup" = group.responsiblegroup try: organization: "models.Organization" = Organization.objects.get( - Q(primary_group=responsible_group) | Q(hr_group=responsible_group) + Q(member_group=responsible_group) | Q(admin_group=responsible_group) ) responsible_group_name = responsible_group.name - if organization.primary_group == responsible_group: - responsible_group_name = PRIMARY_GROUP_NAME - elif organization.hr_group == responsible_group: - responsible_group_name = HR_GROUP_NAME + if organization.member_group == responsible_group: + responsible_group_name = MEMBER_GROUP_NAME + elif organization.admin_group == responsible_group: + responsible_group_name = ADMIN_GROUP_NAME if responsible_group.name != responsible_group_name: responsible_group.name = responsible_group_name responsible_group.save() @@ -50,7 +50,7 @@ def reverse_legible_group_names(apps, _): responsible_group = group.responsiblegroup try: organization = responsible_group.organization - if organization.primary_group == responsible_group: + if organization.member_group == responsible_group: responsible_group.name = organization.name responsible_group.save() except Organization.DoesNotExist: diff --git a/backend/apps/permissions/migrations/0005_auto_20210824_1446.py b/backend/apps/permissions/migrations/0005_auto_20210824_1446.py index 9cb248bc7..afb375654 100644 --- a/backend/apps/permissions/migrations/0005_auto_20210824_1446.py +++ b/backend/apps/permissions/migrations/0005_auto_20210824_1446.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Type from django.db import migrations -from apps.permissions.constants import HR_TYPE, PRIMARY_TYPE +from apps.permissions.constants import ADMIN_GROUP_TYPE, MEMBER_GROUP_TYPE if TYPE_CHECKING: from apps.organizations import models as org_models @@ -15,22 +15,22 @@ def move_permission_groups_to_fk(apps, _): Organization: Type["org_models.Organization"] = apps.get_model("organizations", "Organization") for organization in Organization.objects.all(): - primary_group = organization.primary_group - hr_group = organization.hr_group + member_group = organization.member_group + admin_group = organization.admin_group - if primary_group and hr_group: - primary_group.group_type = PRIMARY_TYPE - hr_group.group_type = HR_TYPE + if member_group and admin_group: + member_group.group_type = MEMBER_GROUP_TYPE + admin_group.group_type = ADMIN_GROUP_TYPE # Name as of this migration - primary_group.temp_organization = organization - hr_group.temp_organization = organization + member_group.temp_organization = organization + admin_group.temp_organization = organization - primary_group.save() - hr_group.save() + member_group.save() + admin_group.save() - organization.primary_group = None - organization.hr_group = None + organization.member_group = None + organization.admin_group = None organization.save() # Delete orphan responsible groups @@ -43,10 +43,12 @@ def move_permission_groups_to_one_to_one_field(apps, _): organization: "org_models.Organization" for organization in Organization.objects.all(): - organization.primary_group = ResponsibleGroup.objects.get( - temp_organization=organization, group_type=PRIMARY_TYPE + organization.member_group = ResponsibleGroup.objects.get( + temp_organization=organization, group_type=MEMBER_GROUP_TYPE + ) + organization.admin_group = ResponsibleGroup.objects.get( + temp_organization=organization, group_type=ADMIN_GROUP_TYPE ) - organization.hr_group = ResponsibleGroup.objects.get(temp_organization=organization, group_type=HR_TYPE) organization.save() diff --git a/backend/apps/permissions/migrations/0034_auto_20221114_1854.py b/backend/apps/permissions/migrations/0034_auto_20221114_1854.py new file mode 100644 index 000000000..3bd94ac85 --- /dev/null +++ b/backend/apps/permissions/migrations/0034_auto_20221114_1854.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.16 on 2022-11-14 17:54 + +from django.db import migrations +from apps.permissions.constants import ADMIN_GROUP_TYPE, MEMBER_GROUP_TYPE + + +def migrate_org_groups(apps, schema_editor): + ResponsibleGroup = apps.get_model("permissions", "ResponsibleGroup") + for responsible_group in ResponsibleGroup.objects.all(): + if responsible_group.name == "HR": + # ResponsibleGroup names format is "Organization:Type" (e.g. "Organization:ADMIN") + # Type is either "ADMIN" or "MEMBER" + # ResponsibleGroup has a group field which is a django.contrib.auth.models.Group with outdated names + # Change the Type in group name to "ADMIN" or "MEMBER" depending on the ResponsibleGroup name (e.g. "Organization:HR:uuid" -> "Organization:ADMIN:uuid") + # Change ResponsibleGroup.group_type from "HR" or "PRIMARY" to "ADMIN" or "MEMBER" + responsible_group.name = f"{responsible_group.organization.name}:{ADMIN_GROUP_TYPE}" + responsible_group.group.name = ( + f"{responsible_group.organization.name}:{ADMIN_GROUP_TYPE}:git{responsible_group.group.uuid}" + ) + responsible_group.group_type = ADMIN_GROUP_TYPE + responsible_group.save() + if responsible_group.name == "Medlem": + responsible_group.name = f"{responsible_group.organization.name}:{MEMBER_GROUP_TYPE}" + responsible_group.group.name = ( + f"{responsible_group.organization.name}:{MEMBER_GROUP_TYPE}:{responsible_group.group.uuid}" + ) + responsible_group.group_type = MEMBER_GROUP_TYPE + responsible_group.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("organizations", "0033_merge_0031_auto_20210909_1813_0032_auto_20210824_1457"), + ] + + operations = [migrations.RunPython(migrate_org_groups)] diff --git a/backend/apps/permissions/migrations/0035_merge_20221117_1825.py b/backend/apps/permissions/migrations/0035_merge_20221117_1825.py new file mode 100644 index 000000000..e110603b3 --- /dev/null +++ b/backend/apps/permissions/migrations/0035_merge_20221117_1825.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2.16 on 2022-11-17 17:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("permissions", "0011_merge_0004_auto_20210824_2126_0010_auto_20210824_1546"), + ("permissions", "0034_auto_20221114_1854"), + ] + + operations = [] diff --git a/backend/apps/permissions/signals.py b/backend/apps/permissions/signals.py index fa2ae43fd..cc63f27fc 100644 --- a/backend/apps/permissions/signals.py +++ b/backend/apps/permissions/signals.py @@ -24,8 +24,7 @@ def create_named_group(sender, instance: ResponsibleGroup, **kwargs): try: instance.group except ObjectDoesNotExist: - prefix: str = instance.organization.name - group = Group.objects.create(name=f"{prefix}:{instance.name}:{uuid4().hex}") + group = Group.objects.create(name=f"{instance.name}:{uuid4().hex}") instance.group = group diff --git a/backend/schema.json b/backend/schema.json index aba27c835..9d733d407 100644 --- a/backend/schema.json +++ b/backend/schema.json @@ -1581,7 +1581,7 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "primaryGroup", + "name": "memberGroup", "type": { "kind": "OBJECT", "name": "ResponsibleGroupType", @@ -1593,7 +1593,7 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "hrGroup", + "name": "adminGroup", "type": { "kind": "OBJECT", "name": "ResponsibleGroupType", @@ -5880,6 +5880,83 @@ "ofType": null } }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "membershipId", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "deleteMembership", + "type": { + "kind": "OBJECT", + "name": "DeleteMembership", + "ofType": null + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "membershipData", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "ChangeMembershipInput", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "changeMembership", + "type": { + "kind": "OBJECT", + "name": "ChangeMembership", + "ofType": null + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "membershipData", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "MembershipInputWithUsername", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "assignMembershipWithUsername", + "type": { + "kind": "OBJECT", + "name": "AssignMembershipWithUsername", + "ofType": null + } + }, { "args": [ { @@ -8301,6 +8378,183 @@ "name": "MembershipInput", "possibleTypes": null }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "membership", + "type": { + "kind": "OBJECT", + "name": "MembershipType", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "ok", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "DeleteMembership", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "membership", + "type": { + "kind": "OBJECT", + "name": "MembershipType", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "ok", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "ChangeMembership", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": null, + "inputFields": [ + { + "defaultValue": null, + "description": null, + "name": "membershipId", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "groupId", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + ], + "interfaces": null, + "kind": "INPUT_OBJECT", + "name": "ChangeMembershipInput", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "membership", + "type": { + "kind": "OBJECT", + "name": "MembershipType", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "ok", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "AssignMembershipWithUsername", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": null, + "inputFields": [ + { + "defaultValue": null, + "description": null, + "name": "username", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "organizationId", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "groupId", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + ], + "interfaces": null, + "kind": "INPUT_OBJECT", + "name": "MembershipInputWithUsername", + "possibleTypes": null + }, { "description": "Add a new booking to the database", "enumValues": null, diff --git a/frontend/src/components/pages/organization/OrgMembers.tsx b/frontend/src/components/pages/organization/OrgMembers.tsx index 465a076c8..569df3634 100644 --- a/frontend/src/components/pages/organization/OrgMembers.tsx +++ b/frontend/src/components/pages/organization/OrgMembers.tsx @@ -1,4 +1,4 @@ -import { useQuery } from "@apollo/client"; +import { useMutation, useQuery } from "@apollo/client"; import { Delete, GroupAdd, AdminPanelSettings } from "@mui/icons-material"; import { Button, @@ -18,7 +18,14 @@ import { orderBy } from "lodash"; import { useState } from "react"; import { PermissionRequired } from "@/components/Auth"; -import { AdminOrganizationFragment, MembershipsDocument } from "@/generated/graphql"; +import { + AdminOrganizationFragment, + MembershipsDocument, + MembershipType, + AssignMembershipWithUsernameDocument, + DeleteMembershipDocument, + ChangeMembershipDocument, +} from "@/generated/graphql"; type Props = { organization: AdminOrganizationFragment; @@ -26,6 +33,9 @@ type Props = { export const OrgMembers: React.FC = ({ organization }) => { const { data, loading, error } = useQuery(MembershipsDocument, { variables: { organizationId: organization.id } }); + const [AssignMembershipWithUsername] = useMutation(AssignMembershipWithUsernameDocument); + const [DeleteMembership] = useMutation(DeleteMembershipDocument); + const [ChangeMembership] = useMutation(ChangeMembershipDocument); const [userInput, setUserInput] = useState(""); @@ -35,9 +45,63 @@ export const OrgMembers: React.FC = ({ organization }) => { //Sorterer medlemmer alfabetisk const memberships = orderBy(data?.memberships, "user.firstName", "asc"); - const addUser = () => { + const handleAddMembership = () => { //Legg til funksjonalitet for å legge til bruker ved brukernavn - setUserInput(""); //Funker men oppdaterer ikke siden? + + AssignMembershipWithUsername({ + variables: { + membershipData: { + organizationId: organization.id, + username: userInput.toLowerCase(), //Just to make sure + groupId: organization?.memberGroup?.uuid, + }, + }, + }); + //Problem: Clientside cache doesnt get updated + setUserInput(""); + }; + + const handleGroupChange = (membership: MembershipType | any) => { + if (!membership) return; + + const currentRole = membership?.group?.uuid == organization.adminGroup?.uuid ? "ADMIN" : "MEMBER"; + if (currentRole == "ADMIN") { + //Legg til funksjonalitet for å demotere bruker + + ChangeMembership({ + variables: { + membershipData: { + membershipId: membership?.id, + groupId: organization?.memberGroup?.uuid, + }, + }, + }); + } + + if (currentRole == "MEMBER") { + //Legg til funksjonalitet for å promote bruker + + ChangeMembership({ + variables: { + membershipData: { + membershipId: membership?.id, + groupId: organization?.adminGroup?.uuid, + }, + }, + }); + } + }; + + const handleRemoveMembership = (membership: MembershipType | any) => { + if (!membership) return; + + DeleteMembership({ + variables: { + membershipId: membership.id, + }, + }); + + //Problem: Clientside cache doesnt get updated }; return ( @@ -54,7 +118,7 @@ export const OrgMembers: React.FC = ({ organization }) => { /> - @@ -78,14 +142,25 @@ export const OrgMembers: React.FC = ({ organization }) => { {membership.user.firstName} {membership.user.lastName} - {membership?.group?.uuid == organization.hrGroup?.uuid ? "Administrator" : "Medlem"} + {membership?.group?.uuid == organization.adminGroup?.uuid ? "Administrator" : "Medlem"} - - diff --git a/frontend/src/generated/graphql.tsx b/frontend/src/generated/graphql.tsx index e773caf0f..b23effa36 100644 --- a/frontend/src/generated/graphql.tsx +++ b/frontend/src/generated/graphql.tsx @@ -112,6 +112,12 @@ export type AssignMembership = { ok?: Maybe; }; +export type AssignMembershipWithUsername = { + __typename?: "AssignMembershipWithUsername"; + membership?: Maybe; + ok?: Maybe; +}; + export type AttemptCapturePayment = { __typename?: "AttemptCapturePayment"; order?: Maybe; @@ -212,6 +218,17 @@ export type CategoryType = { name: Scalars["String"]; }; +export type ChangeMembership = { + __typename?: "ChangeMembership"; + membership?: Maybe; + ok?: Maybe; +}; + +export type ChangeMembershipInput = { + groupId?: InputMaybe; + membershipId?: InputMaybe; +}; + export type CreateArchiveDocument = { __typename?: "CreateArchiveDocument"; arhiveDocument?: Maybe; @@ -407,6 +424,12 @@ export type DeleteListing = { ok?: Maybe; }; +export type DeleteMembership = { + __typename?: "DeleteMembership"; + membership?: Maybe; + ok?: Maybe; +}; + export type DeleteOrganization = { __typename?: "DeleteOrganization"; ok?: Maybe; @@ -552,6 +575,12 @@ export type MembershipInput = { userId?: InputMaybe; }; +export type MembershipInputWithUsername = { + groupId?: InputMaybe; + organizationId?: InputMaybe; + username?: InputMaybe; +}; + export type MembershipType = { __typename?: "MembershipType"; group?: Maybe; @@ -570,8 +599,10 @@ export type Mutations = { */ adminEventSignOff?: Maybe; assignMembership?: Maybe; + assignMembershipWithUsername?: Maybe; attemptCapturePayment?: Maybe; authUser: AuthUser; + changeMembership?: Maybe; createArchivedocument?: Maybe; createBlog?: Maybe; createBlogPost?: Maybe; @@ -602,6 +633,7 @@ export type Mutations = { deleteForm?: Maybe; /** Deletes the listing with the given ID */ deleteListing?: Maybe; + deleteMembership?: Maybe; deleteOrganization?: Maybe; deleteQuestion?: Maybe; /** @@ -652,6 +684,10 @@ export type MutationsAssignMembershipArgs = { membershipData: MembershipInput; }; +export type MutationsAssignMembershipWithUsernameArgs = { + membershipData: MembershipInputWithUsername; +}; + export type MutationsAttemptCapturePaymentArgs = { orderId: Scalars["ID"]; }; @@ -660,6 +696,10 @@ export type MutationsAuthUserArgs = { code: Scalars["String"]; }; +export type MutationsChangeMembershipArgs = { + membershipData: ChangeMembershipInput; +}; + export type MutationsCreateArchivedocumentArgs = { date?: InputMaybe; fileLocation?: InputMaybe; @@ -760,6 +800,10 @@ export type MutationsDeleteListingArgs = { id?: InputMaybe; }; +export type MutationsDeleteMembershipArgs = { + membershipId?: InputMaybe; +}; + export type MutationsDeleteOrganizationArgs = { id: Scalars["ID"]; }; @@ -901,17 +945,17 @@ export type OrganizationInput = { export type OrganizationType = { __typename?: "OrganizationType"; absoluteSlug?: Maybe; + adminGroup?: Maybe; children: Array; color?: Maybe; description: Scalars["String"]; events: Array; - hrGroup?: Maybe; id: Scalars["ID"]; listings?: Maybe>; logoUrl?: Maybe; + memberGroup?: Maybe; name: Scalars["String"]; parent?: Maybe; - primaryGroup?: Maybe; slug: Scalars["String"]; users: Array; }; @@ -2976,8 +3020,8 @@ export type AdminOrganizationFragment = { __typename?: "OrganizationType"; id: string; name: string; - hrGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; - primaryGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; + adminGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; + memberGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; events: Array<{ __typename?: "EventType"; id: string; @@ -3007,10 +3051,72 @@ export type OrgAdminListingFragment = { __typename?: "ListingType"; id: string; export type MembershipFragment = { __typename?: "MembershipType"; id: string; - user: { __typename?: "UserType"; firstName: string; lastName: string }; + user: { __typename?: "UserType"; id: string; firstName: string; lastName: string }; group?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; }; +export type AssignMembershipMutationVariables = Exact<{ + membershipData: MembershipInput; +}>; + +export type AssignMembershipMutation = { + __typename?: "Mutations"; + assignMembership?: { + __typename?: "AssignMembership"; + ok?: boolean | null; + membership?: { + __typename?: "MembershipType"; + id: string; + organization: { __typename?: "OrganizationType"; id: string }; + group?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; + } | null; + } | null; +}; + +export type AssignMembershipWithUsernameMutationVariables = Exact<{ + membershipData: MembershipInputWithUsername; +}>; + +export type AssignMembershipWithUsernameMutation = { + __typename?: "Mutations"; + assignMembershipWithUsername?: { + __typename?: "AssignMembershipWithUsername"; + ok?: boolean | null; + membership?: { + __typename?: "MembershipType"; + id: string; + organization: { __typename?: "OrganizationType"; id: string }; + group?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; + } | null; + } | null; +}; + +export type DeleteMembershipMutationVariables = Exact<{ + membershipId: Scalars["ID"]; +}>; + +export type DeleteMembershipMutation = { + __typename?: "Mutations"; + deleteMembership?: { __typename?: "DeleteMembership"; ok?: boolean | null } | null; +}; + +export type ChangeMembershipMutationVariables = Exact<{ + membershipData: ChangeMembershipInput; +}>; + +export type ChangeMembershipMutation = { + __typename?: "Mutations"; + changeMembership?: { + __typename?: "ChangeMembership"; + ok?: boolean | null; + membership?: { + __typename?: "MembershipType"; + id: string; + group?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; + } | null; + } | null; +}; + export type AdminOrganizationQueryVariables = Exact<{ orgId: Scalars["ID"]; }>; @@ -3021,8 +3127,8 @@ export type AdminOrganizationQuery = { __typename?: "OrganizationType"; id: string; name: string; - hrGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; - primaryGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; + adminGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; + memberGroup?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; events: Array<{ __typename?: "EventType"; id: string; @@ -3046,7 +3152,7 @@ export type MembershipsQuery = { memberships?: Array<{ __typename?: "MembershipType"; id: string; - user: { __typename?: "UserType"; firstName: string; lastName: string }; + user: { __typename?: "UserType"; id: string; firstName: string; lastName: string }; group?: { __typename?: "ResponsibleGroupType"; uuid: string } | null; }> | null; }; @@ -4942,7 +5048,7 @@ export const AdminOrganizationFragmentDoc = { { kind: "Field", name: { kind: "Name", value: "name" } }, { kind: "Field", - name: { kind: "Name", value: "hrGroup" }, + name: { kind: "Name", value: "adminGroup" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "uuid" } }], @@ -4950,7 +5056,7 @@ export const AdminOrganizationFragmentDoc = { }, { kind: "Field", - name: { kind: "Name", value: "primaryGroup" }, + name: { kind: "Name", value: "memberGroup" }, selectionSet: { kind: "SelectionSet", selections: [{ kind: "Field", name: { kind: "Name", value: "uuid" } }], @@ -5031,6 +5137,7 @@ export const MembershipFragmentDoc = { selectionSet: { kind: "SelectionSet", selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "firstName" } }, { kind: "Field", name: { kind: "Name", value: "lastName" } }, ], @@ -9328,6 +9435,236 @@ export const UserOrganizationsDocument = { }, ], } as unknown as DocumentNode; +export const AssignMembershipDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "mutation", + name: { kind: "Name", value: "assignMembership" }, + variableDefinitions: [ + { + kind: "VariableDefinition", + variable: { kind: "Variable", name: { kind: "Name", value: "membershipData" } }, + type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "MembershipInput" } } }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "assignMembership" }, + arguments: [ + { + kind: "Argument", + name: { kind: "Name", value: "membershipData" }, + value: { kind: "Variable", name: { kind: "Name", value: "membershipData" } }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "membership" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + { + kind: "Field", + name: { kind: "Name", value: "organization" }, + selectionSet: { + kind: "SelectionSet", + selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }], + }, + }, + { + kind: "Field", + name: { kind: "Name", value: "group" }, + selectionSet: { + kind: "SelectionSet", + selections: [{ kind: "Field", name: { kind: "Name", value: "uuid" } }], + }, + }, + ], + }, + }, + { kind: "Field", name: { kind: "Name", value: "ok" } }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode; +export const AssignMembershipWithUsernameDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "mutation", + name: { kind: "Name", value: "assignMembershipWithUsername" }, + variableDefinitions: [ + { + kind: "VariableDefinition", + variable: { kind: "Variable", name: { kind: "Name", value: "membershipData" } }, + type: { + kind: "NonNullType", + type: { kind: "NamedType", name: { kind: "Name", value: "MembershipInputWithUsername" } }, + }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "assignMembershipWithUsername" }, + arguments: [ + { + kind: "Argument", + name: { kind: "Name", value: "membershipData" }, + value: { kind: "Variable", name: { kind: "Name", value: "membershipData" } }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "membership" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + { + kind: "Field", + name: { kind: "Name", value: "organization" }, + selectionSet: { + kind: "SelectionSet", + selections: [{ kind: "Field", name: { kind: "Name", value: "id" } }], + }, + }, + { + kind: "Field", + name: { kind: "Name", value: "group" }, + selectionSet: { + kind: "SelectionSet", + selections: [{ kind: "Field", name: { kind: "Name", value: "uuid" } }], + }, + }, + ], + }, + }, + { kind: "Field", name: { kind: "Name", value: "ok" } }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode; +export const DeleteMembershipDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "mutation", + name: { kind: "Name", value: "deleteMembership" }, + variableDefinitions: [ + { + kind: "VariableDefinition", + variable: { kind: "Variable", name: { kind: "Name", value: "membershipId" } }, + type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "ID" } } }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "deleteMembership" }, + arguments: [ + { + kind: "Argument", + name: { kind: "Name", value: "membershipId" }, + value: { kind: "Variable", name: { kind: "Name", value: "membershipId" } }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [{ kind: "Field", name: { kind: "Name", value: "ok" } }], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode; +export const ChangeMembershipDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "mutation", + name: { kind: "Name", value: "changeMembership" }, + variableDefinitions: [ + { + kind: "VariableDefinition", + variable: { kind: "Variable", name: { kind: "Name", value: "membershipData" } }, + type: { + kind: "NonNullType", + type: { kind: "NamedType", name: { kind: "Name", value: "ChangeMembershipInput" } }, + }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "changeMembership" }, + arguments: [ + { + kind: "Argument", + name: { kind: "Name", value: "membershipData" }, + value: { kind: "Variable", name: { kind: "Name", value: "membershipData" } }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "membership" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + { + kind: "Field", + name: { kind: "Name", value: "group" }, + selectionSet: { + kind: "SelectionSet", + selections: [{ kind: "Field", name: { kind: "Name", value: "uuid" } }], + }, + }, + ], + }, + }, + { kind: "Field", name: { kind: "Name", value: "ok" } }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode; export const AdminOrganizationDocument = { kind: "Document", definitions: [ @@ -9475,33 +9812,13 @@ export const MembershipsDocument = { ], selectionSet: { kind: "SelectionSet", - selections: [ - { kind: "Field", name: { kind: "Name", value: "id" } }, - { - kind: "Field", - name: { kind: "Name", value: "user" }, - selectionSet: { - kind: "SelectionSet", - selections: [ - { kind: "Field", name: { kind: "Name", value: "firstName" } }, - { kind: "Field", name: { kind: "Name", value: "lastName" } }, - ], - }, - }, - { - kind: "Field", - name: { kind: "Name", value: "group" }, - selectionSet: { - kind: "SelectionSet", - selections: [{ kind: "Field", name: { kind: "Name", value: "uuid" } }], - }, - }, - ], + selections: [{ kind: "FragmentSpread", name: { kind: "Name", value: "Membership" } }], }, }, ], }, }, + ...MembershipFragmentDoc.definitions, ], } as unknown as DocumentNode; export const HasPermissionDocument = { diff --git a/frontend/src/graphql/orgs/fragments.graphql b/frontend/src/graphql/orgs/fragments.graphql index aa1d15ff6..1b7d47ee2 100644 --- a/frontend/src/graphql/orgs/fragments.graphql +++ b/frontend/src/graphql/orgs/fragments.graphql @@ -1,10 +1,10 @@ fragment AdminOrganization on OrganizationType { id name - hrGroup { + adminGroup { uuid } - primaryGroup { + memberGroup { uuid } events { @@ -36,6 +36,7 @@ fragment OrgAdminListing on ListingType { fragment Membership on MembershipType { id user { + id firstName lastName } diff --git a/frontend/src/graphql/orgs/mutations.graphql b/frontend/src/graphql/orgs/mutations.graphql new file mode 100644 index 000000000..4f508f5a1 --- /dev/null +++ b/frontend/src/graphql/orgs/mutations.graphql @@ -0,0 +1,47 @@ +mutation assignMembership($membershipData: MembershipInput!) { + assignMembership(membershipData: $membershipData) { + membership { + id + organization { + id + } + group { + uuid + } + } + ok + } +} + +mutation assignMembershipWithUsername($membershipData: MembershipInputWithUsername!) { + assignMembershipWithUsername(membershipData: $membershipData) { + membership { + id + organization { + id + } + group { + uuid + } + } + ok + } +} + +mutation deleteMembership($membershipId: ID!) { + deleteMembership(membershipId: $membershipId) { + ok + } +} + +mutation changeMembership($membershipData: ChangeMembershipInput!) { + changeMembership(membershipData: $membershipData) { + membership { + id + group { + uuid + } + } + ok + } +} diff --git a/frontend/src/graphql/orgs/queries.graphql b/frontend/src/graphql/orgs/queries.graphql index 269a060ba..2db7169a8 100644 --- a/frontend/src/graphql/orgs/queries.graphql +++ b/frontend/src/graphql/orgs/queries.graphql @@ -6,13 +6,6 @@ query adminOrganization($orgId: ID!) { query memberships($organizationId: ID!) { memberships(organizationId: $organizationId) { - id - user { - firstName - lastName - } - group { - uuid - } + ...Membership } }