Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
@@ -1,21 +1,18 @@
|
|||||||
import { getCalendarsListAsync } from "@/features/Calendars/services/getCalendarsListAsync";
|
import { getCalendarsListAsync } from "@/features/Calendars/services/getCalendarsListAsync";
|
||||||
import {
|
import { getOpenPaasUser } from "@/features/User/userAPI";
|
||||||
getOpenPaasUser,
|
import { fetchOwnerData } from "@/features/Calendars/services/helpers";
|
||||||
getResourceDetails,
|
|
||||||
getUserDetails,
|
|
||||||
} from "@/features/User/userAPI";
|
|
||||||
import { getCalendars } from "@/features/Calendars/CalendarApi";
|
import { getCalendars } from "@/features/Calendars/CalendarApi";
|
||||||
import { formatReduxError } from "@/utils/errorUtils";
|
import { formatReduxError } from "@/utils/errorUtils";
|
||||||
import { normalizeCalendar } from "@/features/Calendars/utils/normalizeCalendar";
|
import { normalizeCalendar } from "@/features/Calendars/utils/normalizeCalendar";
|
||||||
|
|
||||||
jest.mock("@/features/User/userAPI");
|
jest.mock("@/features/User/userAPI");
|
||||||
|
jest.mock("@/features/Calendars/services/helpers");
|
||||||
jest.mock("@/features/Calendars/CalendarApi");
|
jest.mock("@/features/Calendars/CalendarApi");
|
||||||
jest.mock("@/utils/errorUtils");
|
jest.mock("@/utils/errorUtils");
|
||||||
jest.mock("@/features/Calendars/utils/normalizeCalendar");
|
jest.mock("@/features/Calendars/utils/normalizeCalendar");
|
||||||
|
|
||||||
const mockedGetOpenPaasUser = getOpenPaasUser as jest.Mock;
|
const mockedGetOpenPaasUser = getOpenPaasUser as jest.Mock;
|
||||||
const mockedGetUserDetails = getUserDetails as jest.Mock;
|
const mockedFetchOwnerData = fetchOwnerData as jest.Mock;
|
||||||
const mockedGetResourceDetails = getResourceDetails as jest.Mock;
|
|
||||||
const mockedGetCalendars = getCalendars as jest.Mock;
|
const mockedGetCalendars = getCalendars as jest.Mock;
|
||||||
const mockedFormatReduxError = formatReduxError as jest.Mock;
|
const mockedFormatReduxError = formatReduxError as jest.Mock;
|
||||||
const mockedNormalizeCalendar = normalizeCalendar as jest.Mock;
|
const mockedNormalizeCalendar = normalizeCalendar as jest.Mock;
|
||||||
@@ -82,7 +79,7 @@ describe("getCalendarsListAsync", () => {
|
|||||||
invite: [{ href: "", principal: "", access: 3, inviteStatus: 1 }],
|
invite: [{ href: "", principal: "", access: 3, inviteStatus: 1 }],
|
||||||
});
|
});
|
||||||
|
|
||||||
mockedGetUserDetails
|
mockedFetchOwnerData
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({
|
||||||
firstname: "John",
|
firstname: "John",
|
||||||
lastname: "Doe",
|
lastname: "Doe",
|
||||||
@@ -162,7 +159,38 @@ describe("getCalendarsListAsync", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fetch resource detail as fallback when user not found", async () => {
|
it("should handle error when fetching owner data fails", async () => {
|
||||||
|
getState.mockReturnValue({
|
||||||
|
calendars: {},
|
||||||
|
user: { userData: { openpaasId: "user-123" } },
|
||||||
|
});
|
||||||
|
mockedGetCalendars.mockResolvedValue({
|
||||||
|
_embedded: { "dav:calendar": [{ id: "cal-1" }] },
|
||||||
|
});
|
||||||
|
mockedNormalizeCalendar.mockReturnValue({
|
||||||
|
cal: { "dav:name": "Error Cal" },
|
||||||
|
id: "cal-1",
|
||||||
|
ownerId: "error-123",
|
||||||
|
});
|
||||||
|
|
||||||
|
// fetchOwnerData fails
|
||||||
|
mockedFetchOwnerData.mockRejectedValueOnce(new Error("Network Error"));
|
||||||
|
|
||||||
|
const thunk = getCalendarsListAsync();
|
||||||
|
const result = await thunk(dispatch, getState, undefined);
|
||||||
|
|
||||||
|
const payload = result.payload as any;
|
||||||
|
expect(mockedFetchOwnerData).toHaveBeenCalledWith("error-123");
|
||||||
|
expect(payload.importedCalendars["cal-1"].owner).toEqual({
|
||||||
|
firstname: "",
|
||||||
|
lastname: "Unknown User",
|
||||||
|
emails: [],
|
||||||
|
});
|
||||||
|
// Errors array should contain the error
|
||||||
|
expect(payload.errors).toContain("Network Error");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return owner data mapping properly (including resource: true)", async () => {
|
||||||
getState.mockReturnValue({
|
getState.mockReturnValue({
|
||||||
calendars: {},
|
calendars: {},
|
||||||
user: { userData: { openpaasId: "user-123" } },
|
user: { userData: { openpaasId: "user-123" } },
|
||||||
@@ -176,27 +204,19 @@ describe("getCalendarsListAsync", () => {
|
|||||||
ownerId: "resource-123",
|
ownerId: "resource-123",
|
||||||
});
|
});
|
||||||
|
|
||||||
// getUserDetails fails with 404 for the resource ID, succeeds for the creator
|
// fetchOwnerData succeeds and returns a resource config structure
|
||||||
mockedGetUserDetails.mockImplementation((id: string) => {
|
mockedFetchOwnerData.mockResolvedValueOnce({
|
||||||
if (id === "resource-123")
|
firstname: "Creator",
|
||||||
return Promise.reject({ response: { status: 404 } });
|
lastname: "User",
|
||||||
if (id === "creator-456")
|
emails: [],
|
||||||
return Promise.resolve({
|
resource: true,
|
||||||
firstname: "Creator",
|
|
||||||
lastname: "User",
|
|
||||||
emails: [],
|
|
||||||
});
|
|
||||||
return Promise.resolve({ firstname: "", lastname: "", emails: [] });
|
|
||||||
});
|
});
|
||||||
// Then getResourceDetails is called and succeeds
|
|
||||||
mockedGetResourceDetails.mockResolvedValueOnce({ creator: "creator-456" });
|
|
||||||
|
|
||||||
const thunk = getCalendarsListAsync();
|
const thunk = getCalendarsListAsync();
|
||||||
const result = await thunk(dispatch, getState, undefined);
|
const result = await thunk(dispatch, getState, undefined);
|
||||||
|
|
||||||
const payload = result.payload as any;
|
const payload = result.payload as any;
|
||||||
expect(mockedGetResourceDetails).toHaveBeenCalledWith("resource-123");
|
expect(mockedFetchOwnerData).toHaveBeenCalledWith("resource-123");
|
||||||
expect(mockedGetUserDetails).toHaveBeenCalledWith("creator-456");
|
|
||||||
expect(payload.importedCalendars["cal-1"].owner).toEqual({
|
expect(payload.importedCalendars["cal-1"].owner).toEqual({
|
||||||
firstname: "Creator",
|
firstname: "Creator",
|
||||||
lastname: "User",
|
lastname: "User",
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { fetchOwnerData } from "@/features/Calendars/services/helpers";
|
||||||
|
import { getResourceDetails, getUserDetails } from "@/features/User/userAPI";
|
||||||
|
|
||||||
|
jest.mock("@/features/User/userAPI");
|
||||||
|
|
||||||
|
const mockedGetUserDetails = getUserDetails as jest.Mock;
|
||||||
|
const mockedGetResourceDetails = getResourceDetails as jest.Mock;
|
||||||
|
|
||||||
|
describe("helpers", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchOwnerData", () => {
|
||||||
|
it("should return user details successfully", async () => {
|
||||||
|
const mockUser = {
|
||||||
|
firstname: "John",
|
||||||
|
lastname: "Doe",
|
||||||
|
emails: ["john@example.com"],
|
||||||
|
};
|
||||||
|
mockedGetUserDetails.mockResolvedValueOnce(mockUser);
|
||||||
|
|
||||||
|
const result = await fetchOwnerData("user-123");
|
||||||
|
|
||||||
|
expect(mockedGetUserDetails).toHaveBeenCalledWith("user-123");
|
||||||
|
expect(mockedGetResourceDetails).not.toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch resource details and its creator when user is not found", async () => {
|
||||||
|
const mockResource = { creator: "creator-456" };
|
||||||
|
const mockCreator = {
|
||||||
|
firstname: "Creator",
|
||||||
|
lastname: "User",
|
||||||
|
emails: ["creator@example.com"],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock getUserDetails to fail with 404 for the initial call
|
||||||
|
mockedGetUserDetails.mockRejectedValueOnce({
|
||||||
|
response: { status: 404 },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock getResourceDetails to succeed and return a creator ID
|
||||||
|
mockedGetResourceDetails.mockResolvedValueOnce(mockResource);
|
||||||
|
|
||||||
|
// Mock getUserDetails to succeed when called for the creator
|
||||||
|
mockedGetUserDetails.mockResolvedValueOnce(mockCreator);
|
||||||
|
|
||||||
|
const result = await fetchOwnerData("resource-123");
|
||||||
|
|
||||||
|
expect(mockedGetUserDetails).toHaveBeenNthCalledWith(1, "resource-123");
|
||||||
|
expect(mockedGetResourceDetails).toHaveBeenCalledWith("resource-123");
|
||||||
|
expect(mockedGetUserDetails).toHaveBeenNthCalledWith(2, "creator-456");
|
||||||
|
expect(result).toEqual({ ...mockCreator, resource: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error when getUserDetails fails with non-404 error", async () => {
|
||||||
|
const mockError = { response: { status: 500 } };
|
||||||
|
mockedGetUserDetails.mockRejectedValueOnce(mockError);
|
||||||
|
|
||||||
|
await expect(fetchOwnerData("user-123")).rejects.toEqual(mockError);
|
||||||
|
|
||||||
|
expect(mockedGetUserDetails).toHaveBeenCalledWith("user-123");
|
||||||
|
expect(mockedGetResourceDetails).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error when getResourceDetails fails", async () => {
|
||||||
|
const mockError = new Error("Resource not found");
|
||||||
|
|
||||||
|
// Mock getUserDetails to fail with 404 for the initial call
|
||||||
|
mockedGetUserDetails.mockRejectedValueOnce({
|
||||||
|
response: { status: 404 },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock getResourceDetails to fail
|
||||||
|
mockedGetResourceDetails.mockRejectedValueOnce(mockError);
|
||||||
|
|
||||||
|
await expect(fetchOwnerData("resource-123")).rejects.toEqual(mockError);
|
||||||
|
|
||||||
|
expect(mockedGetUserDetails).toHaveBeenCalledWith("resource-123");
|
||||||
|
expect(mockedGetResourceDetails).toHaveBeenCalledWith("resource-123");
|
||||||
|
expect(mockedGetUserDetails).toHaveBeenCalledTimes(1); // Only called once
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
type SyntheticEvent,
|
type SyntheticEvent,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useI18n } from "twake-i18n";
|
import { useI18n } from "twake-i18n";
|
||||||
|
import { ResourceIcon } from "./ResourceIcon";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -289,14 +290,21 @@ export function PeopleSearch({
|
|||||||
renderOption={(props, option) => {
|
renderOption={(props, option) => {
|
||||||
if (selectedUsers.find((u) => u.email === option.email)) return null;
|
if (selectedUsers.find((u) => u.email === option.email)) return null;
|
||||||
const { key, ...otherProps } = props;
|
const { key, ...otherProps } = props;
|
||||||
|
const isResource = option.objectType === "resource";
|
||||||
return (
|
return (
|
||||||
<ListItem key={key + option?.email} {...otherProps} disableGutters>
|
<ListItem key={key + option?.email} {...otherProps} disableGutters>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar {...stringAvatar(option.displayName || option.email)} />
|
{isResource ? (
|
||||||
|
<ResourceIcon displayName={option.displayName} />
|
||||||
|
) : (
|
||||||
|
<Avatar
|
||||||
|
{...stringAvatar(option.displayName || option.email)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={option.displayName}
|
primary={option.displayName}
|
||||||
secondary={option.email}
|
secondary={isResource ? "" : option.email}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
primary: { variant: "body2" },
|
primary: { variant: "body2" },
|
||||||
secondary: { variant: "caption" },
|
secondary: { variant: "caption" },
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ export default function CalendarResources({
|
|||||||
cal,
|
cal,
|
||||||
owner: user,
|
owner: user,
|
||||||
}))
|
}))
|
||||||
: { cal: undefined, owner: user };
|
: [{ cal: undefined, owner: user }];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function TempCalendarsInput({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PeopleSearch
|
<PeopleSearch
|
||||||
objectTypes={["user"]}
|
objectTypes={["user", "resource"]}
|
||||||
selectedUsers={tempUsers}
|
selectedUsers={tempUsers}
|
||||||
onChange={handleUserChange}
|
onChange={handleUserChange}
|
||||||
onToggleEventPreview={handleToggleEventPreview}
|
onToggleEventPreview={handleToggleEventPreview}
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ export const addCalendarResourceAsync = createAsyncThunk<
|
|||||||
?.replace(".json", "")
|
?.replace(".json", "")
|
||||||
?.split("/")[0];
|
?.split("/")[0];
|
||||||
|
|
||||||
|
if (!resourceId) {
|
||||||
|
return rejectWithValue({
|
||||||
|
message: "Unable to extract resource ID from calendar link",
|
||||||
|
} as RejectedError);
|
||||||
|
}
|
||||||
|
|
||||||
let owner: OpenPaasUserData = {
|
let owner: OpenPaasUserData = {
|
||||||
firstname: "",
|
firstname: "",
|
||||||
lastname: cal.cal["dav:name"] ?? "",
|
lastname: cal.cal["dav:name"] ?? "",
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { RootState } from "@/app/store";
|
import { RootState } from "@/app/store";
|
||||||
import { OpenPaasUserData } from "@/features/User/type/OpenPaasUserData";
|
import { OpenPaasUserData } from "@/features/User/type/OpenPaasUserData";
|
||||||
import {
|
import { getOpenPaasUser } from "@/features/User/userAPI";
|
||||||
getOpenPaasUser,
|
|
||||||
getResourceDetails,
|
|
||||||
getUserDetails,
|
|
||||||
} from "@/features/User/userAPI";
|
|
||||||
import { defaultColors } from "@/utils/defaultColors";
|
import { defaultColors } from "@/utils/defaultColors";
|
||||||
import { formatReduxError, toRejectedError } from "@/utils/errorUtils";
|
import { formatReduxError, toRejectedError } from "@/utils/errorUtils";
|
||||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
@@ -13,6 +9,7 @@ import { Calendar, CalendarInvite } from "../CalendarTypes";
|
|||||||
import { CalendarData } from "../types/CalendarData";
|
import { CalendarData } from "../types/CalendarData";
|
||||||
import { RejectedError } from "../types/RejectedError";
|
import { RejectedError } from "../types/RejectedError";
|
||||||
import { normalizeCalendar } from "../utils/normalizeCalendar";
|
import { normalizeCalendar } from "../utils/normalizeCalendar";
|
||||||
|
import { fetchOwnerData } from "./helpers";
|
||||||
|
|
||||||
export const getCalendarsListAsync = createAsyncThunk<
|
export const getCalendarsListAsync = createAsyncThunk<
|
||||||
{ importedCalendars: Record<string, Calendar>; errors: string },
|
{ importedCalendars: Record<string, Calendar>; errors: string },
|
||||||
@@ -43,42 +40,11 @@ export const getCalendarsListAsync = createAsyncThunk<
|
|||||||
const ownerDataMap = new Map<string, OpenPaasUserData>();
|
const ownerDataMap = new Map<string, OpenPaasUserData>();
|
||||||
const OWNER_BATCH_SIZE = 20;
|
const OWNER_BATCH_SIZE = 20;
|
||||||
|
|
||||||
const fetchResourceData = async (ownerId: string) => {
|
const mapOwnerData = async (ownerId: string) => {
|
||||||
try {
|
try {
|
||||||
const data = await getResourceDetails(ownerId);
|
const data = await fetchOwnerData(ownerId);
|
||||||
const ownerData = await getUserDetails(data.creator);
|
|
||||||
ownerDataMap.set(ownerId, {
|
|
||||||
...ownerData,
|
|
||||||
resource: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`Failed to fetch resource details for ${ownerId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
ownerDataMap.set(ownerId, {
|
|
||||||
firstname: "",
|
|
||||||
lastname: "Unknown User",
|
|
||||||
emails: [],
|
|
||||||
resource: true,
|
|
||||||
});
|
|
||||||
errors.push(formatReduxError(error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchOwnerData = async (ownerId: string) => {
|
|
||||||
try {
|
|
||||||
const data = await getUserDetails(ownerId);
|
|
||||||
ownerDataMap.set(ownerId, data);
|
ownerDataMap.set(ownerId, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const status = (error as { response?: { status?: number } }).response
|
|
||||||
?.status;
|
|
||||||
|
|
||||||
if (status === 404) {
|
|
||||||
await fetchResourceData(ownerId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`Failed to fetch user details for ${ownerId}:`, error);
|
console.error(`Failed to fetch user details for ${ownerId}:`, error);
|
||||||
ownerDataMap.set(ownerId, {
|
ownerDataMap.set(ownerId, {
|
||||||
firstname: "",
|
firstname: "",
|
||||||
@@ -91,7 +57,7 @@ export const getCalendarsListAsync = createAsyncThunk<
|
|||||||
|
|
||||||
for (let i = 0; i < uniqueOwnerIds.length; i += OWNER_BATCH_SIZE) {
|
for (let i = 0; i < uniqueOwnerIds.length; i += OWNER_BATCH_SIZE) {
|
||||||
const chunk = uniqueOwnerIds.slice(i, i + OWNER_BATCH_SIZE);
|
const chunk = uniqueOwnerIds.slice(i, i + OWNER_BATCH_SIZE);
|
||||||
await Promise.all(chunk.map((ownerId) => fetchOwnerData(ownerId)));
|
await Promise.all(chunk.map((ownerId) => mapOwnerData(ownerId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizedCalendars.forEach(
|
normalizedCalendars.forEach(
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { User } from "@/components/Attendees/PeopleSearch";
|
import { User } from "@/components/Attendees/PeopleSearch";
|
||||||
import { getCalendarVisibility } from "@/components/Calendar/utils/calendarUtils";
|
import { getCalendarVisibility } from "@/components/Calendar/utils/calendarUtils";
|
||||||
import { getUserDetails } from "@/features/User/userAPI";
|
|
||||||
import { formatReduxError } from "@/utils/errorUtils";
|
import { formatReduxError } from "@/utils/errorUtils";
|
||||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
import { getCalendars } from "../CalendarApi";
|
import { getCalendars } from "../CalendarApi";
|
||||||
import { Calendar } from "../CalendarTypes";
|
import { Calendar } from "../CalendarTypes";
|
||||||
import { RejectedError } from "../types/RejectedError";
|
import { RejectedError } from "../types/RejectedError";
|
||||||
|
import { fetchOwnerData } from "./helpers";
|
||||||
|
|
||||||
export const getTempCalendarsListAsync = createAsyncThunk<
|
export const getTempCalendarsListAsync = createAsyncThunk<
|
||||||
Record<string, Calendar>,
|
Record<string, Calendar>,
|
||||||
@@ -49,7 +49,7 @@ export const getTempCalendarsListAsync = createAsyncThunk<
|
|||||||
|
|
||||||
const id = source.replace("/calendars/", "").replace(".json", "");
|
const id = source.replace("/calendars/", "").replace(".json", "");
|
||||||
const visibility = getCalendarVisibility(cal["acl"] ?? []);
|
const visibility = getCalendarVisibility(cal["acl"] ?? []);
|
||||||
const ownerData = await getUserDetails(id.split("/")[0]);
|
const ownerData = await fetchOwnerData(id.split("/")[0]);
|
||||||
|
|
||||||
importedCalendars[id] = {
|
importedCalendars[id] = {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { OpenPaasUserData } from "@/features/User/type/OpenPaasUserData";
|
||||||
|
import { getResourceDetails, getUserDetails } from "@/features/User/userAPI";
|
||||||
|
|
||||||
|
const fetchOwnerOfResource = async (
|
||||||
|
ownerId: string
|
||||||
|
): Promise<OpenPaasUserData> => {
|
||||||
|
try {
|
||||||
|
const data = await getResourceDetails(ownerId);
|
||||||
|
const ownerData = await getUserDetails(data.creator);
|
||||||
|
return ownerData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch resource details for ${ownerId}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchOwnerData = async (
|
||||||
|
ownerId: string
|
||||||
|
): Promise<OpenPaasUserData> => {
|
||||||
|
try {
|
||||||
|
const owner = await getUserDetails(ownerId);
|
||||||
|
return owner;
|
||||||
|
} catch (error) {
|
||||||
|
const status = (error as { response?: { status?: number } }).response
|
||||||
|
?.status;
|
||||||
|
|
||||||
|
if (status === 404) {
|
||||||
|
const owner = await fetchOwnerOfResource(ownerId);
|
||||||
|
return {
|
||||||
|
...owner,
|
||||||
|
resource: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`Failed to fetch user details for ${ownerId}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user