Skip to content

Commit

Permalink
finished view and service layer, also made modal work correctly
Browse files Browse the repository at this point in the history
Signed-off-by: Trey <[email protected]>
  • Loading branch information
TreyWW committed Sep 15, 2024
1 parent f80abd2 commit 52f5ccd
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 170 deletions.
54 changes: 9 additions & 45 deletions backend/api/teams/create_user.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
from django.contrib import messages
from django.shortcuts import render
from django.template.defaultfilters import last
from django.urls import reverse
from django.utils.crypto import get_random_string

from backend.decorators import web_require_scopes
from backend.models import Organization, User, TeamMemberPermission
from backend.service.permissions.scopes import get_permissions_from_request
from backend.types.emails import SingleEmailInput
from backend.service.teams.create_user import create_user_service
from backend.types.requests import WebRequest
from settings.helpers import send_email


@web_require_scopes("team:invite", True, True)
def create_user_view(request: WebRequest):
def create_user_endpoint(request: WebRequest):
team_id = request.POST.get("team_id", "")

team: Organization | None = Organization.objects.filter(id=team_id).first()

if not team:
messages.error("This team does not exist")
messages.error(request, "This team does not exist")
return render(request, "base/toast.html")

if not team.is_owner(request.user):
Expand All @@ -30,47 +30,11 @@ def create_user_view(request: WebRequest):
email = request.POST.get("email", "")
permissions: list = get_permissions_from_request(request)

if not email:
messages.error(request, "Please enter a valid user email")
return render(request, "base/toast.html")
created_user = create_user_service(request, email, team, first_name, last_name, permissions)

if User.objects.filter(email=email).exists():
messages.error(request, "This user already exists, invite them instead!")
if created_user.failed:
messages.error(request, created_user.error)
return render(request, "base/toast.html")

temporary_password = get_random_string(length=8)

user: User = User.objects.create_user(email=email, first_name=first_name, last_name=last_name, username=email)
user.set_password(temporary_password)
user.awaiting_email_verification = False
user.save()

send_email(
SingleEmailInput(
destination=email,
subject="MyFinances | You have been invited to join an organization",
content=f"""
Hi {user.first_name or "User"},
You have been invited by {request.user.email} to join the organization {team.name}.
Your account email is: {email}
Your temporary password is: {temporary_password}
We suggest that you change your password as soon as you login, however no other user including the organization have
access to this password.
Upon login, you will be added to the \"{team.name}\" organization. However, if required, you may leave at any point.
Login to your new account using this link:
{request.build_absolute_uri(reverse("auth:login manual"))}
""",
)
)

team.members.add(user)

TeamMemberPermission.objects.create(user=user, team=team, scopes=permissions)

messages.success(request, "User was created successfully. They have been emailed instructions.")
else:
messages.success(request, f"The account for {first_name} was created successfully. They have been emailed instructions.")
return render(request, "base/toast.html")
40 changes: 22 additions & 18 deletions backend/api/teams/invites.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from backend.decorators import *
from backend.models import Notification, Organization, TeamInvitation, User
from backend.types.emails import SingleEmailInput
from backend.types.htmx import HtmxHttpRequest
from settings.helpers import send_email


def delete_notification(user: User, code: TeamInvitation):
Expand Down Expand Up @@ -44,7 +46,7 @@ def check_team_invitation_is_valid(request, invitation: TeamInvitation, code=Non
def send_user_team_invite(request: HtmxHttpRequest):
user_email = request.POST.get("email")
team_id = request.POST.get("team_id", "")
team = Organization.objects.filter(leader=request.user, id=team_id).first()
team: Organization | None = Organization.objects.filter(leader=request.user, id=team_id).first()

def return_error_notif(request: HtmxHttpRequest, message: str, autohide=None):
messages.error(request, message)
Expand All @@ -62,7 +64,7 @@ def return_error_notif(request: HtmxHttpRequest, message: str, autohide=None):
user: User | None = User.objects.filter(email=user_email).first()

if not user:
return return_error_notif(request, "User not found")
return return_error_notif(request, 'User not found. Either ask them to create an account or press "Create User"')

if user.teams_joined.filter(pk=team_id).exists():
return return_error_notif(request, "User already is in this team")
Expand All @@ -81,21 +83,6 @@ def return_error_notif(request: HtmxHttpRequest, message: str, autohide=None):

invitation = TeamInvitation.objects.create(team=team, user=user, invited_by=request.user)

# if EMAIL_SERVER_ENABLED and EMAIL_FROM_ADDRESS:
# SEND_SENDGRID_EMAIL(
# user.email,
# "You have been invited to join a team",
# f"""
# You have been invited to join the team {team.name}
#
# Invited by: {request.user}
#
# Click the link below to join:
# {request.build_absolute_uri(reverse("api:teams:join accept", kwargs={"code": invitation.code}))}
# """,
# from_email=EMAIL_FROM_ADDRESS,
# )

Notification.objects.create(
user=user,
message=f"New Organization Invite",
Expand All @@ -105,7 +92,24 @@ def return_error_notif(request: HtmxHttpRequest, message: str, autohide=None):
extra_value=invitation.code,
)

print(f"Invitation: {request.build_absolute_uri(reverse('api:teams:join accept', kwargs={'code': invitation.code}))}")
send_email(
SingleEmailInput(
destination=user.email,
subject="New Organization Invite",
content=f"""
Hi {user.first_name or "User"},
{request.user.first_name or f"User {request.user.email}"} has invited you to join the organization \"{team.name}\" (#{team.id})
Click the url below to accept the invite!
{request.build_absolute_uri(reverse("api:teams:join accept", kwargs={"code": invitation.code}))}
Didn't give permission to be added to this organization? You can safely ignore the email, no actions can be done on
behalf of you without your action.
""",
)
)

messages.success(request, "Invitation successfully sent")
response = HttpResponse(status=200)
Expand Down
6 changes: 6 additions & 0 deletions backend/api/teams/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.urls import path

from . import kick, switch_team, invites, leave, create, edit_permissions
from .create_user import create_user_endpoint

urlpatterns = [
path("edit_permissions/", edit_permissions.edit_user_permissions_endpoint, name="edit_permissions"),
Expand All @@ -19,6 +20,11 @@
switch_team.switch_team,
name="switch_team input",
),
path(
"create_user/",
create_user_endpoint,
name="create_user",
),
# INVITES #
path(
"invite/",
Expand Down
64 changes: 64 additions & 0 deletions backend/service/teams/create_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from django.urls import reverse
from django.utils.crypto import get_random_string

from backend.models import User, Organization, TeamMemberPermission
from backend.types.emails import SingleEmailInput
from backend.utils.dataclasses import BaseServiceResponse
from settings.helpers import send_email


class CreateUserServiceResponse(BaseServiceResponse[User]): ...


def create_user_service(
request, email: str, team: Organization, first_name: str, last_name: str, permissions: list[str]
) -> CreateUserServiceResponse:

if not first_name:
return CreateUserServiceResponse(error_message="Please enter a valid first name")

if not email:
return CreateUserServiceResponse(error_message="Please enter a valid user email")

if User.objects.filter(email=email).exists():
return CreateUserServiceResponse(error_message="This user already exists, invite them instead!")

temporary_password = get_random_string(length=8)

user: User = User.objects.create_user(email=email, first_name=first_name, last_name=last_name, username=email)
user.set_password(temporary_password)
user.awaiting_email_verification = False
user.require_change_password = True
user.save()

send_email(
SingleEmailInput(
destination=email,
subject="MyFinances | You have been invited to join an organization",
content=f"""
Hi {user.first_name or "User"},
You have been invited by {request.user.email} to join the organization {team.name}.
Your account email is: {email}
Your temporary password is: {temporary_password}
We suggest that you change your password as soon as you login, however no other user including the organization have
access to this password.
Upon login, you will be added to the \"{team.name}\" organization. However, if required, you may leave at any point.
Login to your new account using this link:
{request.build_absolute_uri(reverse("auth:login"))}
Didn't give permission to be added to this organization? You can safely ignore the email, no actions can be done on
behalf of you without your permission.
""",
)
)

team.members.add(user)

TeamMemberPermission.objects.create(user=user, team=team, scopes=permissions)

return CreateUserServiceResponse(True, response=user)
2 changes: 1 addition & 1 deletion frontend/templates/base/topbar/+icon_dropdown.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<li>
<span>Current Version: {{ version }}</span>
</li>
{% if git_version %}
{% if git_version and git_version != "prod" %}
<li>
<span class="text-center">{{ git_version | slice:7 }}</span>
</li>
Expand Down
2 changes: 1 addition & 1 deletion frontend/templates/components/permissions/selector.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
{% endfor %}
</ul>
</div>
</div>
</div>
123 changes: 64 additions & 59 deletions frontend/templates/modals/team_create_user.html
Original file line number Diff line number Diff line change
@@ -1,62 +1,67 @@
{% component_block "modal" id="modal_invite_user" start_open="true" title="Create User Account" %}
{% fill "content" %}
<form class="py-4"
id="modal_invite_user-form"
hx-post="{% url 'api:teams:invite' %}"
hx-swap="none">
{% csrf_token %}
<div class="form-control w-full">
<label class="label justify-start">
Users' Email
<span class="tooltip tooltip-right ml-2" data-tip="The email address of the user you are about to create">
{% component_block "modal" id="modal_team_create_user" start_open="true" title="Create User Account" %}
{% fill "content" %}
<form class="py-4"
id="modal_team_create_user-form"
hx-post="{% url 'api:teams:create_user' %}"
hx-swap="none">
{% csrf_token %}
<div class="form-control w-full">
<label class="label justify-start">
Users' Email
<span class="tooltip tooltip-right ml-2"
data-tip="The email address of the user you are about to create">
<i class="fa fa-info-circle"></i>
</span>
<span class="required_star">*</span>
</label>
<input id="modal_input-email"
name="email"
type="email"
class="input input-block input-bordered"
required>
</div>
<div class="flex flex-row gap-4 w-full">
<div>
<label class="label justify-start">
Users' First Name
<span class="required_star">*</span>
</label>
<input id="modal_input-email"
name="first_name"
type="text"
class="input input-block input-bordered"
required>
</div>
<div>
<label class="label justify-start">
Users' Last Name
<span class="required_star">*</span>
</label>
<input id="modal_input-email"
name="last_name"
type="text"
class="input input-block input-bordered"
required>
</div>
</div>
<input type="hidden" name="team_id" value="{{ team_id }}">
{% include "components/permissions/selector.html" %}
<div class="modal-action">
<button type="submit"
id="modal_invite_user-submit"
class="btn btn-primary"
x-on:invite_user_error.window="document.getElementById('modal_invite_user').close()">
Send Invite
</button>
<button type="button"
_="on click call #modal_invite_user.close()"
class="btn">Cancel
</button>
</div>
</form>
{% endfill %}
<span class="required_star">*</span>
</label>
<input id="modal_input-email"
name="email"
type="email"
class="input input-block input-bordered"
required>
</div>
<div class="flex flex-row gap-4 w-full">
<div>
<label class="label justify-start">
Users' First Name
<span class="required_star">*</span>
</label>
<input id="modal_input-first_name"
name="first_name"
type="text"
class="input input-block input-bordered"
required>
</div>
<div>
<label class="label justify-start">
Users' Last Name
<span class="required_star">*</span>
</label>
<input id="modal_input-last_name"
name="last_name"
type="text"
class="input input-block input-bordered"
required>
</div>
</div>
<input type="hidden" name="team_id" value="{{ team_id }}">
{% include "components/permissions/selector.html" %}
<div class="w-full align-content-start align-items-center gap-1 text-start border-2 rounded-lg border-info p-3 mt-3">
<mark class="rounded pb-1 bg-info pt-0.5 px-1">Note:</mark>
this will send the user instructions on how to login, and they will be granted the permissions above in
your organization <strong>right away</strong>. Double-check the permissions, you can modify them at any point.
</div>
<div class="modal-action">
<button type="submit"
id="modal_team_create_user-submit"
class="btn btn-primary"
x-on:team_create_user_error.window="document.getElementById('modal_team_create_user').close()">
Create account
</button>
<button type="button"
_="on click call #modal_team_create_user.close()"
class="btn">Cancel</button>
</div>
</form>
{% endfill %}
{% endcomponent_block %}
3 changes: 1 addition & 2 deletions frontend/templates/pages/invoices/dashboard/manage.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
<div class=" text-3xl w-full grid grid-cols-4">
<div hx-boost="true">
<a href="{% url "invoices:single:dashboard" %}"
class="btn btn-sm btn-outline btn-secondary me-3 float-left">Back to list
</a>
class="btn btn-sm btn-outline btn-secondary me-3 float-left">Back to list</a>
</div>
<h1 class="text-center col-span-2">Invoice #{{ invoice.id }}</h1>
<div>
Expand Down
Loading

0 comments on commit 52f5ccd

Please sign in to comment.