mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-25 08:09:41 +02:00
feat: User Tooltip (#4319)
This commit is contained in:
parent
a2bdb02a7f
commit
e06572b7ca
23 changed files with 164 additions and 80 deletions
|
@ -37,6 +37,7 @@ These endpoints have moved, but are otherwise unchanged:
|
|||
- `/explore/recipes/{group_slug}` -> `/explore/groups/{group_slug}/recipes`
|
||||
|
||||
`/groups/members` previously returned a `UserOut` object, but now returns a `UserSummary`. Should you need the full user information (username, email, etc.), rather than just the summary, see `/households/members` instead for the household members.
|
||||
`/groups/members` previously returned a list of users, but now returns paginated users (similar to all other list endpoints).
|
||||
|
||||
These endpoints have been completely removed:
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</template>
|
||||
<template #item.userId="{ item }">
|
||||
<v-list-item class="justify-start">
|
||||
<UserAvatar :user-id="item.userId" size="40" />
|
||||
<UserAvatar :user-id="item.userId" :tooltip="false" size="40" />
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ getMember(item.userId) }}
|
||||
|
@ -153,7 +153,7 @@ export default defineComponent({
|
|||
async function refreshMembers() {
|
||||
const { data } = await api.groups.fetchMembers();
|
||||
if (data) {
|
||||
members.value = data;
|
||||
members.value = data.items;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<v-divider class="mx-2"></v-divider>
|
||||
<div v-if="user.id" class="d-flex flex-column">
|
||||
<div class="d-flex mt-3" style="gap: 10px">
|
||||
<UserAvatar size="40" :user-id="user.id" />
|
||||
<UserAvatar :tooltip="false" size="40" :user-id="user.id" />
|
||||
|
||||
<v-textarea
|
||||
v-model="comment"
|
||||
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-for="comment in recipe.comments" :key="comment.id" class="d-flex my-2" style="gap: 10px">
|
||||
<UserAvatar size="40" :user-id="comment.userId" />
|
||||
<UserAvatar :tooltip="false" size="40" :user-id="comment.userId" />
|
||||
<v-card outlined class="flex-grow-1">
|
||||
<v-card-text class="pa-3 pb-0">
|
||||
<p class="">{{ comment.user.username }} • {{ $d(Date.parse(comment.createdAt), "medium") }}</p>
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
<template>
|
||||
<v-list-item-avatar v-if="list && userId">
|
||||
<v-tooltip
|
||||
v-if="userId"
|
||||
:disabled="!user || !tooltip"
|
||||
right
|
||||
>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-list-item-avatar v-if="list" v-bind="attrs" v-on="on">
|
||||
<v-img :src="imageURL" :alt="userId" @load="error = false" @error="error = true"> </v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-avatar v-else-if="userId" :size="size">
|
||||
<v-avatar v-else :size="size" v-bind="attrs" v-on="on">
|
||||
<v-img :src="imageURL" :alt="userId" @load="error = false" @error="error = true"> </v-img>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<span v-if="user">
|
||||
{{ user.fullName }}
|
||||
</span>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, useContext, computed } from "@nuxtjs/composition-api";
|
||||
import { useUserStore } from "~/composables/store/use-user-store";
|
||||
import { UserOut } from "~/lib/api/types/user";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -25,6 +37,10 @@ export default defineComponent({
|
|||
type: String,
|
||||
default: "42",
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const state = reactive({
|
||||
|
@ -32,15 +48,20 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
const { $auth } = useContext();
|
||||
const { store: users } = useUserStore();
|
||||
const user = computed(() => {
|
||||
return users.value.find((user) => user.id === props.userId);
|
||||
})
|
||||
|
||||
const imageURL = computed(() => {
|
||||
// TODO Setup correct user type for $auth.user
|
||||
const user = $auth.user as unknown as UserOut | null;
|
||||
const key = user?.cacheKey ?? "";
|
||||
const authUser = $auth.user as unknown as UserOut | null;
|
||||
const key = authUser?.cacheKey ?? "";
|
||||
return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
|
||||
});
|
||||
|
||||
return {
|
||||
user,
|
||||
imageURL,
|
||||
...toRefs(state),
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<!-- User Profile -->
|
||||
<template v-if="loggedIn">
|
||||
<v-list-item two-line :to="userProfileLink" exact>
|
||||
<UserAvatar list :user-id="$auth.user.id" />
|
||||
<UserAvatar list :user-id="$auth.user.id" :tooltip="false" />
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="pr-2"> {{ $auth.user.fullName }}</v-list-item-title>
|
||||
|
|
|
@ -57,35 +57,30 @@ function getRequests(axiosInstance: NuxtAxiosInstance): ApiRequestInstance {
|
|||
};
|
||||
}
|
||||
|
||||
export const useAdminApi = function (): AdminAPI {
|
||||
export const useRequests = function (): ApiRequestInstance {
|
||||
const { $axios, i18n } = useContext();
|
||||
|
||||
$axios.setHeader("Accept-Language", i18n.locale);
|
||||
|
||||
const requests = getRequests($axios);
|
||||
return getRequests($axios);
|
||||
};
|
||||
|
||||
export const useAdminApi = function (): AdminAPI {
|
||||
const requests = useRequests();
|
||||
return new AdminAPI(requests);
|
||||
};
|
||||
|
||||
export const useUserApi = function (): UserApi {
|
||||
const { $axios, i18n } = useContext();
|
||||
$axios.setHeader("Accept-Language", i18n.locale);
|
||||
|
||||
const requests = getRequests($axios);
|
||||
const requests = useRequests();
|
||||
return new UserApi(requests);
|
||||
};
|
||||
|
||||
export const usePublicApi = function (): PublicApi {
|
||||
const { $axios, i18n } = useContext();
|
||||
$axios.setHeader("Accept-Language", i18n.locale);
|
||||
|
||||
const requests = getRequests($axios);
|
||||
const requests = useRequests();
|
||||
return new PublicApi(requests);
|
||||
};
|
||||
|
||||
export const usePublicExploreApi = function (groupSlug: string): PublicExploreApi {
|
||||
const { $axios, i18n } = useContext();
|
||||
$axios.setHeader("Accept-Language", i18n.locale);
|
||||
|
||||
const requests = getRequests($axios);
|
||||
const requests = useRequests();
|
||||
return new PublicExploreApi(requests, groupSlug);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { QueryValue } from "~/lib/api/base/route";
|
|||
|
||||
interface ReadOnlyStoreActions<T extends BoundT> {
|
||||
getAll(page?: number, perPage?: number, params?: any): Ref<T[] | null>;
|
||||
refresh(): Promise<void>;
|
||||
refresh(page?: number, perPage?: number, params?: any): Promise<void>;
|
||||
}
|
||||
|
||||
interface StoreActions<T extends BoundT> extends ReadOnlyStoreActions<T> {
|
||||
|
@ -50,9 +50,9 @@ export function useReadOnlyActions<T extends BoundT>(
|
|||
return allItems;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
async function refresh(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
|
||||
loading.value = true;
|
||||
const { data } = await api.getAll();
|
||||
const { data } = await api.getAll(page, perPage, params);
|
||||
|
||||
if (data && data.items && allRef) {
|
||||
allRef.value = data.items;
|
||||
|
@ -101,9 +101,9 @@ export function useStoreActions<T extends BoundT>(
|
|||
return allItems;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
async function refresh(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
|
||||
loading.value = true;
|
||||
const { data } = await api.getAll();
|
||||
const { data } = await api.getAll(page, perPage, params);
|
||||
|
||||
if (data && data.items && allRef) {
|
||||
allRef.value = data.items;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { ref, reactive, Ref } from "@nuxtjs/composition-api";
|
|||
import { useReadOnlyActions, useStoreActions } from "./use-actions-factory";
|
||||
import { BoundT } from "./types";
|
||||
import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
|
||||
import { QueryValue } from "~/lib/api/base/route";
|
||||
|
||||
export const useData = function<T extends BoundT>(defaultObject: T) {
|
||||
const data = reactive({ ...defaultObject });
|
||||
|
@ -16,16 +17,21 @@ export const useReadOnlyStore = function<T extends BoundT>(
|
|||
store: Ref<T[]>,
|
||||
loading: Ref<boolean>,
|
||||
api: BaseCRUDAPIReadOnly<T>,
|
||||
params = {} as Record<string, QueryValue>,
|
||||
) {
|
||||
const storeActions = useReadOnlyActions(api, store, loading);
|
||||
const actions = {
|
||||
...useReadOnlyActions(api, store, loading),
|
||||
...storeActions,
|
||||
async refresh() {
|
||||
return await storeActions.refresh(1, -1, params);
|
||||
},
|
||||
flushStore() {
|
||||
store.value = [];
|
||||
},
|
||||
};
|
||||
|
||||
if (!loading.value && (!store.value || store.value.length === 0)) {
|
||||
const result = actions.getAll();
|
||||
const result = actions.getAll(1, -1, params);
|
||||
store.value = result.value || [];
|
||||
}
|
||||
|
||||
|
@ -36,16 +42,21 @@ export const useStore = function<T extends BoundT>(
|
|||
store: Ref<T[]>,
|
||||
loading: Ref<boolean>,
|
||||
api: BaseCRUDAPI<unknown, T, unknown>,
|
||||
params = {} as Record<string, QueryValue>,
|
||||
) {
|
||||
const storeActions = useStoreActions(api, store, loading);
|
||||
const actions = {
|
||||
...useStoreActions(api, store, loading),
|
||||
...storeActions,
|
||||
async refresh() {
|
||||
return await storeActions.refresh(1, -1, params);
|
||||
},
|
||||
flushStore() {
|
||||
store = ref([]);
|
||||
},
|
||||
};
|
||||
|
||||
if (!loading.value && (!store.value || store.value.length === 0)) {
|
||||
const result = actions.getAll();
|
||||
const result = actions.getAll(1, -1, params);
|
||||
store.value = result.value || [];
|
||||
}
|
||||
|
||||
|
|
20
frontend/composables/store/use-user-store.ts
Normal file
20
frontend/composables/store/use-user-store.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { ref, Ref } from "@nuxtjs/composition-api";
|
||||
import { useReadOnlyStore } from "../partials/use-store-factory";
|
||||
import { useRequests } from "../api/api-client";
|
||||
import { UserSummary } from "~/lib/api/types/user";
|
||||
import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
|
||||
|
||||
const store: Ref<UserSummary[]> = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
class GroupUserAPIReadOnly extends BaseCRUDAPIReadOnly<UserSummary> {
|
||||
baseRoute = "/api/groups/members";
|
||||
itemRoute = (idOrUsername: string | number) => `/groups/members/${idOrUsername}`;
|
||||
}
|
||||
|
||||
export const useUserStore = function () {
|
||||
const requests = useRequests();
|
||||
const api = new GroupUserAPIReadOnly(requests);
|
||||
|
||||
return useReadOnlyStore<UserSummary>(store, loading, api, {orderBy: "full_name"});
|
||||
}
|
|
@ -77,6 +77,7 @@ export interface ReadWebhook {
|
|||
}
|
||||
export interface UserSummary {
|
||||
id: string;
|
||||
username: string;
|
||||
fullName: string;
|
||||
}
|
||||
export interface ReadGroupPreferences {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { BaseCRUDAPI } from "../base/base-clients";
|
||||
import { PaginationData } from "../types/non-generated";
|
||||
import { QueryValue } from "../base/route";
|
||||
import { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user";
|
||||
import {
|
||||
GroupAdminUpdate,
|
||||
|
@ -14,11 +16,7 @@ const routes = {
|
|||
groupsSelf: `${prefix}/groups/self`,
|
||||
preferences: `${prefix}/groups/preferences`,
|
||||
storage: `${prefix}/groups/storage`,
|
||||
membersHouseholdId: (householdId: string | number | null) => {
|
||||
return householdId ?
|
||||
`${prefix}/households/members?householdId=${householdId}` :
|
||||
`${prefix}/groups/members`;
|
||||
},
|
||||
members: `${prefix}/groups/members`,
|
||||
groupsId: (id: string | number) => `${prefix}/admin/groups/${id}`,
|
||||
};
|
||||
|
||||
|
@ -40,8 +38,8 @@ export class GroupAPI extends BaseCRUDAPI<GroupBase, GroupInDB, GroupAdminUpdate
|
|||
return await this.requests.put<ReadGroupPreferences, UpdateGroupPreferences>(routes.preferences, payload);
|
||||
}
|
||||
|
||||
async fetchMembers(householdId: string | number | null = null) {
|
||||
return await this.requests.get<UserSummary[]>(routes.membersHouseholdId(householdId));
|
||||
async fetchMembers(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
|
||||
return await this.requests.get<PaginationData<UserSummary>>(routes.members, { page, perPage, ...params });
|
||||
}
|
||||
|
||||
async storage() {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { BaseCRUDAPIReadOnly } from "../base/base-clients";
|
||||
import { PaginationData } from "../types/non-generated";
|
||||
import { QueryValue } from "../base/route";
|
||||
import { UserOut } from "~/lib/api/types/user";
|
||||
import {
|
||||
HouseholdInDB,
|
||||
|
@ -48,8 +50,8 @@ export class HouseholdAPI extends BaseCRUDAPIReadOnly<HouseholdSummary> {
|
|||
return await this.requests.post<ReadInviteToken>(routes.invitation, payload);
|
||||
}
|
||||
|
||||
async fetchMembers() {
|
||||
return await this.requests.get<UserOut[]>(routes.members);
|
||||
async fetchMembers(page = 1, perPage = -1, params = {} as Record<string, QueryValue>) {
|
||||
return await this.requests.get<PaginationData<UserOut>>(routes.members, { page, perPage, ...params });
|
||||
}
|
||||
|
||||
async setMemberPermissions(payload: SetPermissions) {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
disable-pagination
|
||||
>
|
||||
<template #item.avatar="{ item }">
|
||||
<UserAvatar :user-id="item.id" />
|
||||
<UserAvatar :tooltip="false" :user-id="item.id" />
|
||||
</template>
|
||||
<template #item.admin="{ item }">
|
||||
{{ item.admin ? $t('user.admin') : $t('user.user') }}
|
||||
|
@ -111,7 +111,7 @@ export default defineComponent({
|
|||
async function refreshMembers() {
|
||||
const { data } = await api.households.fetchMembers();
|
||||
if (data) {
|
||||
members.value = data;
|
||||
members.value = data.items;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1025,7 +1025,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// update current user
|
||||
allUsers.value = data.sort((a, b) => ((a.fullName || "") < (b.fullName || "") ? -1 : 1));
|
||||
allUsers.value = data.items.sort((a, b) => ((a.fullName || "") < (b.fullName || "") ? -1 : 1));
|
||||
currentUserId.value = shoppingList.value?.userId;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<BasePageTitle divider>
|
||||
<template #header>
|
||||
<div class="d-flex flex-column align-center justify-center">
|
||||
<UserAvatar size="96" :user-id="$auth.user.id" />
|
||||
<UserAvatar :tooltip="false" size="96" :user-id="$auth.user.id" />
|
||||
<AppButtonUpload
|
||||
class="my-1"
|
||||
file-name="profile"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<v-container v-if="user">
|
||||
<section class="d-flex flex-column align-center mt-4">
|
||||
<UserAvatar size="96" :user-id="user.id" />
|
||||
<UserAvatar :tooltip="false" size="96" :user-id="user.id" />
|
||||
|
||||
<h2 class="headline">{{ $t('profile.welcome-user', [user.fullName]) }}</h2>
|
||||
<p class="subtitle-1 mb-0 text-center">
|
||||
|
|
2
frontend/types/components.d.ts
vendored
2
frontend/types/components.d.ts
vendored
|
@ -34,6 +34,7 @@ import ReportTable from "@/components/global/ReportTable.vue";
|
|||
import SafeMarkdown from "@/components/global/SafeMarkdown.vue";
|
||||
import StatsCards from "@/components/global/StatsCards.vue";
|
||||
import ToggleState from "@/components/global/ToggleState.vue";
|
||||
import WakelockSwitch from "@/components/global/WakelockSwitch.vue";
|
||||
import DefaultLayout from "@/components/layout/DefaultLayout.vue";
|
||||
|
||||
declare module "vue" {
|
||||
|
@ -74,6 +75,7 @@ declare module "vue" {
|
|||
SafeMarkdown: typeof SafeMarkdown;
|
||||
StatsCards: typeof StatsCards;
|
||||
ToggleState: typeof ToggleState;
|
||||
WakelockSwitch: typeof WakelockSwitch;
|
||||
// Layout Components
|
||||
DefaultLayout: typeof DefaultLayout;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from functools import cached_property
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Query
|
||||
from fastapi import Depends, HTTPException
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
|
@ -8,7 +9,7 @@ from mealie.routes._base.controller import controller
|
|||
from mealie.routes._base.routers import UserAPIRouter
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences, UpdateGroupPreferences
|
||||
from mealie.schema.group.group_statistics import GroupStorage
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
|
||||
from mealie.schema.user.user import GroupSummary, UserSummary
|
||||
from mealie.services.group_services.group_service import GroupService
|
||||
|
||||
|
@ -26,13 +27,29 @@ class GroupSelfServiceController(BaseUserController):
|
|||
"""Returns the Group Data for the Current User"""
|
||||
return self.group.cast(GroupSummary)
|
||||
|
||||
@router.get("/members", response_model=list[UserSummary])
|
||||
def get_group_members(self, household_id: UUID4 | None = Query(None, alias="householdId")):
|
||||
"""Returns all users belonging to the current group, optionally filtered by household_id"""
|
||||
@router.get("/members", response_model=PaginationBase[UserSummary])
|
||||
def get_group_members(self, q: PaginationQuery = Depends()):
|
||||
"""Returns all users belonging to the current group"""
|
||||
|
||||
query_filter = f"household_id={household_id}" if household_id else None
|
||||
private_users = self.repos.users.page_all(PaginationQuery(page=1, per_page=-1, query_filter=query_filter)).items
|
||||
return [user.cast(UserSummary) for user in private_users]
|
||||
response = self.repos.users.page_all(q, override=UserSummary)
|
||||
response.set_pagination_guides(router.url_path_for("get_group_members"), q.model_dump())
|
||||
return response
|
||||
|
||||
@router.get("/members/{username_or_id}", response_model=UserSummary)
|
||||
def get_group_member(self, username_or_id: str | UUID4):
|
||||
"""Returns a single user belonging to the current group"""
|
||||
|
||||
try:
|
||||
UUID(username_or_id)
|
||||
key = "id"
|
||||
except ValueError:
|
||||
key = "username"
|
||||
|
||||
private_user = self.repos.users.get_one(username_or_id, key)
|
||||
if not private_user:
|
||||
raise HTTPException(status_code=404, detail="User Not Found")
|
||||
|
||||
return private_user.cast(UserSummary)
|
||||
|
||||
@router.get("/preferences", response_model=ReadGroupPreferences)
|
||||
def get_group_preferences(self):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from functools import cached_property
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from fastapi import Depends, HTTPException, status
|
||||
|
||||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
|
@ -9,7 +9,7 @@ from mealie.schema.household.household import HouseholdInDB
|
|||
from mealie.schema.household.household_permissions import SetPermissions
|
||||
from mealie.schema.household.household_preferences import ReadHouseholdPreferences, UpdateHouseholdPreferences
|
||||
from mealie.schema.household.household_statistics import HouseholdStatistics
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
|
||||
from mealie.schema.user.user import UserOut
|
||||
from mealie.services.household_services.household_service import HouseholdService
|
||||
|
||||
|
@ -27,13 +27,20 @@ class HouseholdSelfServiceController(BaseUserController):
|
|||
"""Returns the Household Data for the Current User"""
|
||||
return self.household
|
||||
|
||||
@router.get("/members", response_model=list[UserOut])
|
||||
def get_household_members(self):
|
||||
@router.get("/members", response_model=PaginationBase[UserOut])
|
||||
def get_household_members(self, q: PaginationQuery = Depends()):
|
||||
"""Returns all users belonging to the current household"""
|
||||
private_users = self.repos.users.page_all(
|
||||
PaginationQuery(page=1, per_page=-1, query_filter=f"household_id={self.household_id}")
|
||||
).items
|
||||
return [user.cast(UserOut) for user in private_users]
|
||||
|
||||
qf_part = f"household_id={self.household_id}"
|
||||
if q.query_filter:
|
||||
q.query_filter = f"({q.query_filter}) AND {qf_part}"
|
||||
else:
|
||||
q.query_filter = qf_part
|
||||
|
||||
response = self.repos.users.page_all(q, override=UserOut)
|
||||
|
||||
response.set_pagination_guides(router.url_path_for("get_household_members"), q.model_dump())
|
||||
return response
|
||||
|
||||
@router.get("/preferences", response_model=ReadHouseholdPreferences)
|
||||
def get_household_preferences(self):
|
||||
|
|
|
@ -180,6 +180,7 @@ class UserOut(UserBase):
|
|||
|
||||
class UserSummary(MealieModel):
|
||||
id: UUID4
|
||||
username: str
|
||||
full_name: str
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import random
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
|
@ -8,10 +9,10 @@ from tests.utils.fixture_schemas import TestUser
|
|||
|
||||
|
||||
def test_get_group_members(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
|
||||
response = api_client.get(api_routes.groups_members, headers=unique_user.token)
|
||||
response = api_client.get(api_routes.groups_members, params={"perPage": -1}, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
members = response.json()
|
||||
members = response.json()["items"]
|
||||
assert len(members) >= 2
|
||||
|
||||
all_ids = [x["id"] for x in members]
|
||||
|
@ -20,19 +21,21 @@ def test_get_group_members(api_client: TestClient, unique_user: TestUser, h2_use
|
|||
assert str(h2_user.user_id) in all_ids
|
||||
|
||||
|
||||
def test_get_group_members_filtered(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
|
||||
response = api_client.get(
|
||||
api_routes.groups_members, params={"householdId": h2_user.household_id}, headers=unique_user.token
|
||||
)
|
||||
@pytest.mark.parametrize("query", ["id", "username"])
|
||||
def test_get_group_member(api_client: TestClient, unique_user: TestUser, h2_user: TestUser, query: str):
|
||||
if query == "id":
|
||||
param = str(h2_user.user_id)
|
||||
else:
|
||||
param = h2_user.username
|
||||
|
||||
response = api_client.get(api_routes.groups_members_username_or_id(param), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == str(h2_user.user_id)
|
||||
|
||||
members = response.json()
|
||||
assert len(members) >= 1
|
||||
|
||||
all_ids = [x["id"] for x in members]
|
||||
|
||||
assert str(unique_user.user_id) not in all_ids
|
||||
assert str(h2_user.user_id) in all_ids
|
||||
def test_get_group_member_not_found(api_client: TestClient, unique_user: TestUser):
|
||||
response = api_client.get(api_routes.groups_members_username_or_id(random_string()), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_get_households(api_client: TestClient, unique_user: TestUser):
|
||||
|
|
|
@ -7,10 +7,10 @@ from tests.utils.fixture_schemas import TestUser
|
|||
def test_get_household_members(api_client: TestClient, user_tuple: list[TestUser], h2_user: TestUser):
|
||||
usr_1, usr_2 = user_tuple
|
||||
|
||||
response = api_client.get(api_routes.households_members, headers=usr_1.token)
|
||||
response = api_client.get(api_routes.households_members, params={"perPage": -1}, headers=usr_1.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
members = response.json()
|
||||
members = response.json()["items"]
|
||||
assert len(members) >= 2
|
||||
|
||||
all_ids = [x["id"] for x in members]
|
||||
|
|
|
@ -320,6 +320,11 @@ def groups_labels_item_id(item_id):
|
|||
return f"{prefix}/groups/labels/{item_id}"
|
||||
|
||||
|
||||
def groups_members_username_or_id(username_or_id):
|
||||
"""`/api/groups/members/{username_or_id}`"""
|
||||
return f"{prefix}/groups/members/{username_or_id}"
|
||||
|
||||
|
||||
def groups_reports_item_id(item_id):
|
||||
"""`/api/groups/reports/{item_id}`"""
|
||||
return f"{prefix}/groups/reports/{item_id}"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue