Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invite users #1

Merged
merged 16 commits into from
Nov 28, 2023
4 changes: 2 additions & 2 deletions .github/workflows/codecov.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CodeCov
name: Run Tests

on: [push, pull_request]

Expand Down Expand Up @@ -41,7 +41,7 @@ jobs:
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
REDIS_HOST: localhost
REDIS_PORT: ${{ job.services.redis.ports[6379] }}
run: cd OneSila && coverage run --source='.' manage.py test
run: cd OneSila && coverage run --source='.' manage.py test && coverage json
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v3
env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
coverage.json
*.cover
*.py,cover
.hypothesis/
Expand Down
74 changes: 66 additions & 8 deletions OneSila/core/factories/multi_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
from core.models.multi_tenant import MultiTenantUser
from django.core.exceptions import ValidationError

from core.signals import registered, invite_sent, invite_accepted, \
disabled, enabled


class RegisterUserFactory:
model = MultiTenantUser
Expand Down Expand Up @@ -34,19 +37,19 @@ def create_user(self):

self.user = user

def send_welcome(self):
pass
def send_signal(self):
registered.send(sender=self.user.__class__, instance=self.user)

@transaction.atomic
def run(self):
self.create_user()
self.send_welcome()
self.send_signal()


class InviteUserFactory:
model = MultiTenantUser
is_active = False
invitation_accepted = False
is_active = False

def __init__(self, *, multi_tenant_company, language, username, first_name="", last_name=""):
self.multi_tenant_company = multi_tenant_company
Expand Down Expand Up @@ -76,11 +79,66 @@ def create_user(self):
user.save()
self.user = user

def send_invite(self):
# FIXME: Send out (branded) email to the new user.
pass
def send_signal(self):
invite_sent.send(sender=self.user.__class__, instance=self.user)

@transaction.atomic
def run(self):
self.create_user()
self.send_invite()
self.send_signal()


class AcceptUserInviteFactory:
def __init__(self, user, password, language):
self.user = user

self.password = password
self.language = language

def update_user(self):
self.user.invitation_accepted = True
self.user.language = self.language
self.user.is_active = True

self.user.set_password(self.password)
self.user.save()

def send_signal(self):
invite_accepted.send(sender=self.user.__class__, instance=self.user)

@transaction.atomic
def run(self):
self.update_user()
self.send_signal()


class DisableUserFactory:
def __init__(self, user):
self.user = user

def disable_user(self):
self.user.set_inactive()

def send_signal(self):
disabled.send(sender=self.user.__class__, instance=self.user)

@transaction.atomic
def run(self):
self.disable_user()
self.send_signal()


class EnableUserFactory:
def __init__(self, user):
self.user = user

def enable_user(self):
self.user.set_active()

def send_signal(self):
enabled.send(sender=self.user.__class__, instance=self.user)

@transaction.atomic
def run(self):
self.enable_user()
self.send_signal()
12 changes: 12 additions & 0 deletions OneSila/core/models/multi_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ def save(self, *args, **kwargs):

super().save(*args, **kwargs)

def set_active(self, save=True):
self.is_active = True

if save:
self.save()

def set_inactive(self, save=True):
self.is_active = False

if save:
self.save()

class Meta:
verbose_name = _("Multi tenant user")
verbose_name_plural = _("Multi tenant users")
Expand Down
1 change: 1 addition & 0 deletions OneSila/core/schema/multi_tenant/mutations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .mutation_type import MultiTenantMutation
49 changes: 49 additions & 0 deletions OneSila/core/schema/multi_tenant/mutations/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from core.schema.multi_tenant.types.input import MultiTenantCompanyMyInput, \
MultiTenantCompanyPartialInput, MultiTenantUserPartialInput, \
MultiTenantUserInput, MultiTenantInviteUserInput, MultiTenantUserAcceptInviteInput, \
MultiTenantUserStatusInput
from core.schema.core.mutations import IsAuthenticated, default_extensions
from .mutation_classes import MyMultiTenantCompanyCreateMutation, \
MyMultiTentantCompanyUpdateMutation, UpdateMeMutation, \
RegisterUserMutation, InviteUserMutation, AcceptInvitationMutation, \
EnableUserMutation, DisableUserMutation


def register_my_multi_tenant_company():
extensions = [IsAuthenticated()]
return MyMultiTenantCompanyCreateMutation(MultiTenantCompanyMyInput, extensions=extensions)


def update_my_multi_tenant_company():
extensions = default_extensions
return MyMultiTentantCompanyUpdateMutation(MultiTenantCompanyPartialInput, extensions=extensions)


def update_me():
extensions = default_extensions
return UpdateMeMutation(MultiTenantUserPartialInput, extensions=extensions)


def register_user():
extensions = []
return RegisterUserMutation(MultiTenantUserInput, extensions=extensions)


def invite_user():
extensions = default_extensions
return InviteUserMutation(MultiTenantInviteUserInput, extensions=extensions)


def accept_user_invitation():
extensions = []
return AcceptInvitationMutation(MultiTenantUserAcceptInviteInput, extensions=extensions)


def disable_user():
extensions = default_extensions
return DisableUserMutation(MultiTenantUserStatusInput, extensions=extensions)


def enable_user():
extensions = default_extensions
return EnableUserMutation(MultiTenantUserStatusInput, extensions=extensions)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from strawberry import UNSET
from strawberry_django import auth as strawberry_auth

from strawberry.relay.utils import from_base64
from strawberry_django.resolvers import django_resolver
from strawberry_django.mutations import resolvers
from strawberry_django.auth.utils import get_current_user
Expand All @@ -8,28 +9,19 @@
from strawberry_django.auth.exceptions import IncorrectUsernamePasswordError

from django.db import transaction
from django.core.exceptions import ValidationError
from django.contrib import auth
from django.contrib.auth.password_validation import validate_password
from django.db.utils import IntegrityError
from django.conf import settings

from typing import cast, Type
from asgiref.sync import async_to_sync, sync_to_async
from asgiref.sync import async_to_sync

from channels import auth as channels_auth
from channels.db import database_sync_to_async

from core.schema.core.mutations import create, type, DjangoUpdateMutation, \
DjangoCreateMutation, GetMultiTenantCompanyMixin, default_extensions, \
update, Info, models, Iterable, Any, IsAuthenticated
from core.schema.core.mixins import GetQuerysetMultiTenantMixin
from core.factories.multi_tenant import InviteUserFactory, RegisterUserFactory

from .types.types import MultiTenantUserType, MultiTenantCompanyType
from .types.input import MultiTenantUserInput, MultiTenantUserPartialInput, \
MultiTenantCompanyPartialInput, MultiTenantCompanyInput, \
MultiTenantInviteUserInput, MultiTenantCompanyMyInput
from core.factories.multi_tenant import InviteUserFactory, RegisterUserFactory, \
AcceptUserInviteFactory, EnableUserFactory, DisableUserFactory


class CleanupDataMixin:
Expand Down Expand Up @@ -96,7 +88,16 @@ def create(self, data: dict[str, Any], *, info: Info):


class AcceptInvitationMutation(DjangoUpdateMutation):
pass
def update(self, info: Info, instance: models.Model, data: dict[str, Any]):
# Do not optimize anything while retrieving the object to update
with DjangoOptimizerExtension.disabled():
fac = AcceptUserInviteFactory(
user=instance,
password=data['password'],
language=data['language'])
fac.run()

return fac.user


class MyMultiTenantCompanyCreateMutation(GetMultiTenantCompanyMixin, DjangoCreateMutation):
Expand Down Expand Up @@ -152,47 +153,21 @@ def resolver(self, source: Any, info: Info, args: list[Any], kwargs: dict[str, A
return self.update(info, instance, resolvers.parse_input(info, vdata))


def register_my_multi_tenant_company():
extensions = [IsAuthenticated()]
return MyMultiTenantCompanyCreateMutation(MultiTenantCompanyMyInput, extensions=extensions)


def update_my_multi_tenant_company():
extensions = default_extensions
return MyMultiTentantCompanyUpdateMutation(MultiTenantCompanyPartialInput, extensions=extensions)


def update_me():
extensions = default_extensions
return UpdateMeMutation(MultiTenantUserPartialInput, extensions=extensions)


def register_user():
extensions = []
return RegisterUserMutation(MultiTenantUserInput, extensions=extensions)


def invite_user():
extensions = default_extensions
return InviteUserMutation(MultiTenantInviteUserInput, extensions=extensions)


@type(name="Mutation")
class MultiTenantMutation:
login: MultiTenantUserType = strawberry_auth.login()
logout = strawberry_auth.logout()
class DisableUserMutation(DjangoUpdateMutation):
def update(self, info: Info, instance: models.Model, data: dict[str, Any]):
# Do not optimize anything while retrieving the object to update
with DjangoOptimizerExtension.disabled():
fac = DisableUserFactory(user=instance)
fac.run()
return fac.user

register_user: MultiTenantUserType = register_user()
register_my_multi_tenant_company: MultiTenantCompanyType = register_my_multi_tenant_company()

update_me: MultiTenantUserType = update_me()
update_my_multi_tenant_company: MultiTenantCompanyType = update_my_multi_tenant_company()
class EnableUserMutation(DjangoUpdateMutation):
def update(self, info: Info, instance: models.Model, data: dict[str, Any]):
# Do not optimize anything while retrieving the object to update
with DjangoOptimizerExtension.disabled():
fac = EnableUserFactory(user=instance)
fac.run()

invite_user: MultiTenantUserType = invite_user()
# TODO: Invite user mutation.
# this mutation will:
# create "un-activated" user
# assign to multi-tenant-company
# send out email to invite and resturn invitation link
# + query/mutation flow for the user to actually subscribe.
# + mutation to re-invite the user
instance.refresh_from_db()
return instance
26 changes: 26 additions & 0 deletions OneSila/core/schema/multi_tenant/mutations/mutation_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from core.schema.core.mutations import type
from strawberry_django import auth as strawberry_auth
from .fields import register_user, register_my_multi_tenant_company, \
update_me, update_my_multi_tenant_company, invite_user, \
accept_user_invitation, disable_user, enable_user

from core.schema.multi_tenant.types.types import MultiTenantUserType, \
MultiTenantCompanyType


@type(name="Mutation")
class MultiTenantMutation:
login: MultiTenantUserType = strawberry_auth.login()
logout = strawberry_auth.logout()

register_user: MultiTenantUserType = register_user()
register_my_multi_tenant_company: MultiTenantCompanyType = register_my_multi_tenant_company()

update_me: MultiTenantUserType = update_me()
update_my_multi_tenant_company: MultiTenantCompanyType = update_my_multi_tenant_company()

invite_user: MultiTenantUserType = invite_user()
accept_user_invitation: MultiTenantUserType = accept_user_invitation()

disable_user: MultiTenantUserType = disable_user()
enable_user: MultiTenantUserType = enable_user()
11 changes: 3 additions & 8 deletions OneSila/core/schema/multi_tenant/queries.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
from strawberry_django import auth, field

from core.models.multi_tenant import MultiTenantCompany
from core.schema.core.helpers import get_multi_tenant_company
from core.schema.multi_tenant.types.types import MultiTenantUserType, MultiTenantCompanyType
from core.schema.core.queries import node, connection, ListConnectionWithTotalCount, \
type, field, default_extensions, Info
from core.schema.core.helpers import get_multi_tenant_company
from .types.types import MultiTenantUserType, MultiTenantCompanyType


from strawberry_django.fields.field import StrawberryDjangoField
from strawberry import relay


def my_multi_tenant_company_resolver(info: Info) -> MultiTenantCompany:
def my_multi_tenant_company_resolver(info: Info) -> MultiTenantCompanyType:
multi_tenant_company = get_multi_tenant_company(info)
return multi_tenant_company

Expand Down
16 changes: 14 additions & 2 deletions OneSila/core/schema/multi_tenant/types/filters.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
from core.schema.types.types import auto
from core.schema.types.filters import filter
from core.schema.core.types.types import auto
from core.schema.core.types.filters import filter

from core.models.multi_tenant import MultiTenantCompany, MultiTenantUser


@filter(MultiTenantUser)
class MultiTenantUserFilter:
first_name: auto
last_name: auto
username: auto
is_active: auto
invitation_accepted: auto
is_multi_tenant_company_owner: auto
Loading
Loading