feat(backend): refactor/fix group management for admins (#838)

* fix(frontend): 🐛 update dialog implementation to simplify state management

* test(backend):  refactor test fixtures + admin group tests

* chore(backend): 🔨 add launcher.json for python debugging (tests)

* fix typing

* feat(backend):  refactor/fix group management for admins

* feat(frontend):  add/fix admin group management

* add LDAP checker

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden
2021-11-25 14:17:02 -09:00
committed by GitHub
parent 0db8a58963
commit 791aa8c610
52 changed files with 881 additions and 331 deletions

View File

@@ -16,7 +16,7 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
ALGORITHM = "HS256"
def create_access_token(data: dict(), expires_delta: timedelta = None) -> str:
def create_access_token(data: dict, expires_delta: timedelta = None) -> str:
settings = get_app_settings()
to_encode = data.copy()
@@ -78,7 +78,7 @@ def user_from_ldap(db: Database, session, username: str, password: str) -> Priva
return user
def authenticate_user(session, email: str, password: str) -> PrivateUser | False:
def authenticate_user(session, email: str, password: str) -> PrivateUser | bool:
settings = get_app_settings()
db = get_database(session)

View File

@@ -99,8 +99,8 @@ class AppSettings(BaseSettings):
self.LDAP_BIND_TEMPLATE,
self.LDAP_ADMIN_FILTER,
}
return "" not in required and None not in required and self.LDAP_AUTH_ENABLED
not_none = None not in required
return self.LDAP_AUTH_ENABLED and not_none
class Config:
arbitrary_types_allowed = True

View File

@@ -1,16 +1,15 @@
from fastapi import APIRouter
from mealie.routes.routers import AdminAPIRouter
from mealie.services._base_http_service.router_factory import RouterFactory
from mealie.services.admin.admin_group_service import AdminGroupService
from mealie.services.admin.admin_user_service import AdminUserService
from . import admin_about, admin_email, admin_group, admin_log, admin_server_tasks
from . import admin_about, admin_email, admin_log, admin_server_tasks
router = AdminAPIRouter(prefix="/admin")
router.include_router(admin_about.router, tags=["Admin: About"])
router.include_router(admin_log.router, tags=["Admin: Log"])
router.include_router(admin_group.router, tags=["Admin: Group"])
router.include_router(RouterFactory(AdminUserService, prefix="/users", tags=["Admin: Users"]))
router.include_router(RouterFactory(AdminGroupService, prefix="/groups", tags=["Admin: Groups"]))
router.include_router(admin_email.router, tags=["Admin: Email"])
router.include_router(admin_server_tasks.router, tags=["Admin: Server Tasks"])

View File

@@ -47,5 +47,6 @@ async def check_app_config():
return CheckAppConfig(
email_ready=settings.SMTP_ENABLE,
ldap_ready=settings.LDAP_ENABLED,
base_url_set=url_set,
)

View File

@@ -5,11 +5,10 @@ from fastapi.responses import JSONResponse
from mealie.core.config import get_app_settings
from mealie.core.root_logger import get_logger
logger = get_logger(__name__)
logger = get_logger()
def log_wrapper(request: Request, e):
logger.error("Start 422 Error".center(60, "-"))
logger.error(f"{request.method} {request.url}")
logger.error(f"error is {e}")

View File

@@ -27,4 +27,5 @@ class AdminAboutInfo(AppInfo):
class CheckAppConfig(CamelModel):
email_ready: bool = False
ldap_ready: bool = False
base_url_set: bool = False

View File

@@ -0,0 +1,9 @@
from fastapi_camelcase import CamelModel
from .group_preferences import UpdateGroupPreferences
class GroupAdminUpdate(CamelModel):
id: int
name: str
preferences: UpdateGroupPreferences

18
mealie/schema/mapper.py Normal file
View File

@@ -0,0 +1,18 @@
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar("T", bound=BaseModel)
U = TypeVar("U", bound=BaseModel)
def mapper(source: U, dest: T, **kwargs) -> Generic[T]:
"""
Map a source model to a destination model. Only top-level fields are mapped.
"""
for field in source.__fields__:
if field in dest.__fields__:
setattr(dest, field, getattr(source, field))
return dest

View File

@@ -0,0 +1,59 @@
from __future__ import annotations
from functools import cached_property
from fastapi import HTTPException, status
from mealie.schema.group.group import GroupAdminUpdate
from mealie.schema.mapper import mapper
from mealie.schema.response import ErrorResponse
from mealie.schema.user.user import GroupBase, GroupInDB
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
from mealie.services._base_http_service.http_services import AdminHttpService
from mealie.services.events import create_group_event
from mealie.services.group_services.group_utils import create_new_group
class AdminGroupService(
CrudHttpMixins[GroupBase, GroupInDB, GroupAdminUpdate],
AdminHttpService[int, GroupInDB],
):
event_func = create_group_event
_schema = GroupInDB
@cached_property
def dal(self):
return self.db.groups
def populate_item(self, id: int) -> GroupInDB:
self.item = self.dal.get_one(id)
return self.item
def get_all(self) -> list[GroupInDB]:
return self.dal.get_all()
def create_one(self, data: GroupBase) -> GroupInDB:
return create_new_group(self.db, data)
def update_one(self, data: GroupAdminUpdate, item_id: int = None) -> GroupInDB:
target_id = item_id or data.id
if data.preferences:
preferences = self.db.group_preferences.get_one(value=target_id, key="group_id")
preferences = mapper(data.preferences, preferences)
self.item.preferences = self.db.group_preferences.update(preferences.id, preferences)
if data.name not in ["", self.item.name]:
self.item.name = data.name
self.item = self.dal.update(target_id, self.item)
return self.item
def delete_one(self, id: int = None) -> GroupInDB:
if len(self.item.users) > 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ErrorResponse(message="Cannot delete group with users").dict(),
)
return self._delete_one(id)

View File

@@ -5,14 +5,14 @@ from functools import cached_property
from mealie.schema.user.user import UserIn, UserOut
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
from mealie.services._base_http_service.http_services import AdminHttpService
from mealie.services.events import create_recipe_event
from mealie.services.events import create_user_event
class AdminUserService(
CrudHttpMixins[UserOut, UserIn, UserIn],
AdminHttpService[int, UserOut],
):
event_func = create_recipe_event
event_func = create_user_event
_schema = UserOut
@cached_property