mirror of
https://github.com/hay-kot/mealie.git
synced 2025-07-06 04:52:25 -07:00
feat: Consolidate Admin User APIs (#5050)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
parent
31f90c79c0
commit
47eb1ebbb1
7 changed files with 24 additions and 53 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useAdminApi } from "~/composables/api";
|
||||||
import type { UserIn, UserOut } from "~/lib/api/types/user";
|
import type { UserIn, UserOut } from "~/lib/api/types/user";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -8,7 +8,7 @@ to control whether the object is substantiated... but some of the others rely on
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const useAllUsers = function () {
|
export const useAllUsers = function () {
|
||||||
const api = useUserApi();
|
const api = useAdminApi();
|
||||||
const asyncKey = String(Date.now());
|
const asyncKey = String(Date.now());
|
||||||
const { data: users, refresh: refreshAllUsers } = useLazyAsyncData(asyncKey, async () => {
|
const { data: users, refresh: refreshAllUsers } = useLazyAsyncData(asyncKey, async () => {
|
||||||
const { data } = await api.users.getAll();
|
const { data } = await api.users.getAll();
|
||||||
|
@ -24,7 +24,7 @@ export const useAllUsers = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUser = function (refreshFunc: CallableFunction | null = null) {
|
export const useUser = function (refreshFunc: CallableFunction | null = null) {
|
||||||
const api = useUserApi();
|
const api = useAdminApi();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
function getUser(id: string) {
|
function getUser(id: string) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.core import security
|
from mealie.core import security
|
||||||
|
@ -42,6 +42,11 @@ class AdminUserManagementRoutes(BaseAdminController):
|
||||||
|
|
||||||
@router.post("", response_model=UserOut, status_code=201)
|
@router.post("", response_model=UserOut, status_code=201)
|
||||||
def create_one(self, data: UserIn):
|
def create_one(self, data: UserIn):
|
||||||
|
if self.repos.users.get_by_username(data.username):
|
||||||
|
raise HTTPException(status.HTTP_409_CONFLICT, {"message": self.t("exceptions.username-conflict-error")})
|
||||||
|
elif self.repos.users.get_one(data.email, "email"):
|
||||||
|
raise HTTPException(status.HTTP_409_CONFLICT, {"message": self.t("exceptions.email-conflict-error")})
|
||||||
|
|
||||||
data.password = security.hash_password(data.password)
|
data.password = security.hash_password(data.password)
|
||||||
return self.mixins.create_one(data)
|
return self.mixins.create_one(data)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ router = APIRouter()
|
||||||
|
|
||||||
router.include_router(registration.router, prefix=user_prefix, tags=["Users: Registration"])
|
router.include_router(registration.router, prefix=user_prefix, tags=["Users: Registration"])
|
||||||
router.include_router(crud.user_router)
|
router.include_router(crud.user_router)
|
||||||
router.include_router(crud.admin_router)
|
|
||||||
router.include_router(forgot_password.router, prefix=user_prefix, tags=["Users: Passwords"])
|
router.include_router(forgot_password.router, prefix=user_prefix, tags=["Users: Passwords"])
|
||||||
router.include_router(images.router, prefix=user_prefix, tags=["Users: Images"])
|
router.include_router(images.router, prefix=user_prefix, tags=["Users: Images"])
|
||||||
router.include_router(api_tokens.router)
|
router.include_router(api_tokens.router)
|
||||||
|
|
|
@ -1,52 +1,17 @@
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.core.security import hash_password
|
from mealie.core.security import hash_password
|
||||||
from mealie.core.security.providers.credentials_provider import CredentialsProvider
|
from mealie.core.security.providers.credentials_provider import CredentialsProvider
|
||||||
from mealie.db.models.users.users import AuthMethod
|
from mealie.db.models.users.users import AuthMethod
|
||||||
from mealie.routes._base import BaseAdminController, BaseUserController, controller
|
from mealie.routes._base import BaseUserController, controller
|
||||||
from mealie.routes._base.mixins import HttpRepo
|
from mealie.routes._base.routers import UserAPIRouter
|
||||||
from mealie.routes._base.routers import AdminAPIRouter, UserAPIRouter
|
|
||||||
from mealie.routes.users._helpers import assert_user_change_allowed
|
from mealie.routes.users._helpers import assert_user_change_allowed
|
||||||
from mealie.schema.response import ErrorResponse, SuccessResponse
|
from mealie.schema.response import ErrorResponse, SuccessResponse
|
||||||
from mealie.schema.response.pagination import PaginationQuery
|
from mealie.schema.user import ChangePassword, UserBase, UserOut
|
||||||
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserOut
|
from mealie.schema.user.user import UserRatings, UserRatingSummary
|
||||||
from mealie.schema.user.user import UserPagination, UserRatings, UserRatingSummary
|
|
||||||
|
|
||||||
user_router = UserAPIRouter(prefix="/users", tags=["Users: CRUD"])
|
user_router = UserAPIRouter(prefix="/users", tags=["Users: CRUD"])
|
||||||
admin_router = AdminAPIRouter(prefix="/users", tags=["Users: Admin CRUD"])
|
|
||||||
|
|
||||||
|
|
||||||
@controller(admin_router)
|
|
||||||
class AdminUserController(BaseAdminController):
|
|
||||||
@property
|
|
||||||
def mixins(self) -> HttpRepo:
|
|
||||||
return HttpRepo[UserIn, UserOut, UserBase](self.repos.users, self.logger)
|
|
||||||
|
|
||||||
@admin_router.get("", response_model=UserPagination)
|
|
||||||
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
|
||||||
"""Returns all users from all groups"""
|
|
||||||
|
|
||||||
response = self.repos.users.page_all(
|
|
||||||
pagination=q,
|
|
||||||
override=UserOut,
|
|
||||||
)
|
|
||||||
|
|
||||||
response.set_pagination_guides(admin_router.url_path_for("get_all"), q.model_dump())
|
|
||||||
return response
|
|
||||||
|
|
||||||
@admin_router.post("", response_model=UserOut, status_code=201)
|
|
||||||
def create_user(self, new_user: UserIn):
|
|
||||||
new_user.password = hash_password(new_user.password)
|
|
||||||
return self.mixins.create_one(new_user)
|
|
||||||
|
|
||||||
@admin_router.get("/{item_id}", response_model=UserOut)
|
|
||||||
def get_user(self, item_id: UUID4):
|
|
||||||
return self.mixins.get_one(item_id)
|
|
||||||
|
|
||||||
@admin_router.delete("/{item_id}")
|
|
||||||
def delete_user(self, item_id: UUID4):
|
|
||||||
self.mixins.delete_one(item_id)
|
|
||||||
|
|
||||||
|
|
||||||
@controller(user_router)
|
@controller(user_router)
|
||||||
|
|
8
tests/fixtures/fixture_users.py
vendored
8
tests/fixtures/fixture_users.py
vendored
|
@ -76,7 +76,7 @@ def h2_user(session: Session, admin_token, api_client: TestClient, unique_user:
|
||||||
"admin": False,
|
"admin": False,
|
||||||
"tokens": [],
|
"tokens": [],
|
||||||
}
|
}
|
||||||
response = api_client.post(api_routes.users, json=user_data, headers=admin_token)
|
response = api_client.post(api_routes.admin_users, json=user_data, headers=admin_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
# Log in as this user
|
# Log in as this user
|
||||||
|
@ -135,7 +135,7 @@ def g2_user(session: Session, admin_token, api_client: TestClient):
|
||||||
}
|
}
|
||||||
|
|
||||||
api_client.post(api_routes.admin_groups, json={"name": group}, headers=admin_token)
|
api_client.post(api_routes.admin_groups, json={"name": group}, headers=admin_token)
|
||||||
response = api_client.post(api_routes.users, json=create_data, headers=admin_token)
|
response = api_client.post(api_routes.admin_users, json=create_data, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ def user_tuple(session: Session, admin_token, api_client: TestClient) -> Generat
|
||||||
users_out = []
|
users_out = []
|
||||||
|
|
||||||
for usr in [create_data_1, create_data_2]:
|
for usr in [create_data_1, create_data_2]:
|
||||||
response = api_client.post(api_routes.users, json=usr, headers=admin_token)
|
response = api_client.post(api_routes.admin_users, json=usr, headers=admin_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
# Log in as this user
|
# Log in as this user
|
||||||
|
@ -312,7 +312,7 @@ def user_token(admin_token, api_client: TestClient):
|
||||||
"tokens": [],
|
"tokens": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
response = api_client.post(api_routes.users, json=create_data, headers=admin_token)
|
response = api_client.post(api_routes.admin_users, json=create_data, headers=admin_token)
|
||||||
|
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ def test_api_token_private(api_client: TestClient, admin_token):
|
||||||
response = api_client.post(api_routes.users_api_tokens, json={"name": "Test API Token"}, headers=admin_token)
|
response = api_client.post(api_routes.users_api_tokens, json={"name": "Test API Token"}, headers=admin_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
response = api_client.get(api_routes.users, headers=admin_token, params={"perPage": -1})
|
response = api_client.get(api_routes.admin_users, headers=admin_token, params={"perPage": -1})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
for user in response.json()["items"]:
|
for user in response.json()["items"]:
|
||||||
for user_token in user["tokens"] or []:
|
for user_token in user["tokens"] or []:
|
||||||
|
@ -39,7 +39,7 @@ def test_api_token_private(api_client: TestClient, admin_token):
|
||||||
|
|
||||||
|
|
||||||
def test_use_token(api_client: TestClient, long_live_token):
|
def test_use_token(api_client: TestClient, long_live_token):
|
||||||
response = api_client.get(api_routes.users, headers=long_live_token)
|
response = api_client.get(api_routes.admin_users, headers=long_live_token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
|
@ -88,9 +88,11 @@ def test_user_update(api_client: TestClient, unique_user: TestUser, admin_user:
|
||||||
|
|
||||||
|
|
||||||
def test_admin_updates(api_client: TestClient, admin_user: TestUser, unique_user: TestUser):
|
def test_admin_updates(api_client: TestClient, admin_user: TestUser, unique_user: TestUser):
|
||||||
response = api_client.get(api_routes.users_item_id(unique_user.user_id), headers=admin_user.token)
|
response = api_client.get(api_routes.admin_users_item_id(unique_user.user_id), headers=admin_user.token)
|
||||||
|
assert response.status_code == 200
|
||||||
user = response.json()
|
user = response.json()
|
||||||
response = api_client.get(api_routes.users_item_id(admin_user.user_id), headers=admin_user.token)
|
|
||||||
|
response = api_client.get(api_routes.admin_users_item_id(admin_user.user_id), headers=admin_user.token)
|
||||||
admin = response.json()
|
admin = response.json()
|
||||||
|
|
||||||
# admin updating themselves
|
# admin updating themselves
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue