feat(frontend): add group permissions (#721)

* style(frontend): 💄 add darktheme custom

* add dummy users in dev mode

* feat(frontend):  add group permissions editor UI

* feat(backend):  add group permissions setters

* test(backend):  tests for basic permission get/set (WIP)

Needs more testing

* remove old test

* chore(backend): copy template.env on setup

* feat(frontend):  enable send invitation via email

* feat(backend):  enable send invitation via email

* feat:  add app config checker for site-settings

* refactor(frontend): ♻️ consolidate bool checks

Co-authored-by: Hayden <hay-kot@pm.me>
This commit is contained in:
Hayden
2021-10-04 20:16:37 -08:00
committed by GitHub
parent b7b8aa9a08
commit 5d43fac7c9
43 changed files with 652 additions and 106 deletions

View File

@@ -55,7 +55,7 @@ class EmailService(BaseService):
def send_invitation(self, address: str, invitation_url: str) -> bool:
invitation = EmailTemplate(
subject="Invitation to join Mealie",
header_text="Invitation",
header_text="Your Invited!",
message_top="You have been invited to join Mealie.",
message_bottom="Please click the button below to accept the invitation.",
button_link=invitation_url,

View File

@@ -419,7 +419,7 @@
"
>
<div style="text-align: center">
{{ data.bottom_message}}
{{ data.message_bottom}}
</div>
</div>
</td>

View File

@@ -2,15 +2,17 @@ from __future__ import annotations
from uuid import uuid4
from fastapi import Depends
from fastapi import Depends, HTTPException, status
from mealie.core.dependencies.grouped import UserDeps
from mealie.core.root_logger import get_logger
from mealie.schema.group.group_permissions import SetPermissions
from mealie.schema.group.group_preferences import UpdateGroupPreferences
from mealie.schema.group.invite_token import ReadInviteToken, SaveInviteToken
from mealie.schema.group.invite_token import EmailInitationResponse, EmailInvitation, ReadInviteToken, SaveInviteToken
from mealie.schema.recipe.recipe_category import CategoryBase
from mealie.schema.user.user import GroupInDB
from mealie.schema.user.user import GroupInDB, PrivateUser, UserOut
from mealie.services._base_http_service.http_services import UserHttpService
from mealie.services.email import EmailService
from mealie.services.events import create_group_event
logger = get_logger(module=__name__)
@@ -31,10 +33,38 @@ class GroupSelfService(UserHttpService[int, str]):
"""Override parent method to remove `item_id` from arguments"""
return super().write_existing(item_id=0, deps=deps)
@classmethod
def manage_existing(cls, deps: UserDeps = Depends()):
"""Override parent method to remove `item_id` from arguments"""
if not deps.user.can_manage:
raise HTTPException(status.HTTP_403_FORBIDDEN)
return super().write_existing(item_id=0, deps=deps)
def populate_item(self, _: str = None) -> GroupInDB:
self.item = self.db.groups.get(self.group_id)
return self.item
# ====================================================================
# Manage Menbers
def get_members(self) -> list[UserOut]:
return self.db.users.multi_query(query_by={"group_id": self.item.id}, override_schema=UserOut)
def set_member_permissions(self, permissions: SetPermissions) -> PrivateUser:
target_user = self.db.users.get(permissions.user_id)
if not target_user:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
if target_user.group_id != self.group_id:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="User is not a member of this group")
target_user.can_invite = permissions.can_invite
target_user.can_manage = permissions.can_manage
target_user.can_organize = permissions.can_organize
return self.db.users.update(permissions.user_id, target_user)
# ====================================================================
# Meal Categories
@@ -53,11 +83,27 @@ class GroupSelfService(UserHttpService[int, str]):
# Group Invites
def create_invite_token(self, uses: int = 1) -> None:
if not self.user.can_invite:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="User is not allowed to create invite tokens")
token = SaveInviteToken(uses_left=uses, group_id=self.group_id, token=uuid4().hex)
return self.db.group_invite_tokens.create(token)
def get_invite_tokens(self) -> list[ReadInviteToken]:
return self.db.group_invite_tokens.multi_query({"group_id": self.group_id})
def email_invitation(self, invite: EmailInvitation) -> EmailInitationResponse:
email_service = EmailService()
url = f"{self.settings.BASE_URL}/register?token={invite.token}"
success = False
error = None
try:
success = email_service.send_invitation(address=invite.email, invitation_url=url)
except Exception as e:
error = str(e)
return EmailInitationResponse(success=success, error=error)
# ====================================================================
# Export / Import Recipes

View File

@@ -23,25 +23,21 @@ class RegistrationService(PublicHttpService[int, str]):
logger.info(f"Registering user {registration.username}")
token_entry = None
new_group = False
if registration.group:
new_group = True
group = self._register_new_group()
elif registration.group_token and registration.group_token != "":
token_entry = self.db.group_invite_tokens.get(registration.group_token)
print("Token Entry", token_entry)
if not token_entry:
raise HTTPException(status.HTTP_400_BAD_REQUEST, {"message": "Invalid group token"})
group = self.db.groups.get(token_entry.group_id)
else:
raise HTTPException(status.HTTP_400_BAD_REQUEST, {"message": "Missing group"})
user = self._create_new_user(group)
user = self._create_new_user(group, new_group)
if token_entry and user:
token_entry.uses_left = token_entry.uses_left - 1
@@ -54,7 +50,7 @@ class RegistrationService(PublicHttpService[int, str]):
return user
def _create_new_user(self, group: GroupInDB) -> PrivateUser:
def _create_new_user(self, group: GroupInDB, new_group=bool) -> PrivateUser:
new_user = UserIn(
email=self.registration.email,
username=self.registration.username,
@@ -62,6 +58,9 @@ class RegistrationService(PublicHttpService[int, str]):
full_name=self.registration.username,
advanced=self.registration.advanced,
group=group.name,
can_invite=new_group,
can_manage=new_group,
can_organize=new_group,
)
return self.db.users.create(new_user)