Files
dashboards/services/meshkee-content.ts
T

286 lines
6.5 KiB
TypeScript

import { apiRequest, getAccessToken } from "@/lib/meshkee-api";
export interface MeshkeeProduct {
id: string;
title: string;
nameFa?: string;
summary?: string;
slug: string;
status?: string;
categoryId?: string | null;
categoryName?: string;
categoryParentName?: string;
thumbnailUrl?: string | null;
image?: string;
thumbnail?: string;
}
export interface MeshkeeBlog {
id: string;
title: string;
slug: string;
abstract?: string | null;
titleImageUrl?: string | null;
createdAt?: string;
}
export interface MeshkeePortfolio {
id: string;
title?: string;
titleFa?: string | null;
titleEn?: string | null;
name?: string;
nameFa?: string | null;
nameEn?: string | null;
slug: string;
abstract?: string | null;
titleImageUrl?: string | null;
coverUrl?: string | null;
image?: string | null;
}
export interface MeshkeeMediaItem {
id: string;
mediaType: string;
publicUrl: string;
altText?: string | null;
fileName?: string | null;
width?: number | null;
height?: number | null;
}
export interface MeshkeeBusinessCategory {
id: string;
name: string;
nameFa?: string | null;
parentId: string | null;
slug?: string;
isActive?: boolean;
}
export type ListResponse<T> = {
items: T[];
total: number;
page: number;
pageSize: number;
};
type ListOptions = {
page?: number;
pageSize?: number;
};
async function listResource<T>(
path: string,
options: ListOptions = {},
): Promise<ListResponse<T>> {
const page = options.page ?? 1;
const pageSize = options.pageSize ?? 12;
const params = new URLSearchParams({
page: String(page),
pageSize: String(pageSize),
});
const data = await apiRequest<ListResponse<T>>(`${path}?${params}`, {
auth: true,
});
return {
items: (data.items ?? []).map((item) => ({
...item,
id: String((item as { id: string | number }).id),
})),
total: data.total ?? 0,
page: data.page ?? page,
pageSize: data.pageSize ?? pageSize,
};
}
export function listMeshkeeProducts(
businessId: string,
options: ListOptions = {},
) {
return listResource<MeshkeeProduct>(
`/businesses/${businessId}/products`,
options,
);
}
export function listMeshkeeBlogs(
businessId: string,
options: ListOptions = {},
) {
return listResource<MeshkeeBlog>(`/businesses/${businessId}/blogs`, options);
}
export function listMeshkeePortfolios(
businessId: string,
options: ListOptions = {},
) {
return listResource<MeshkeePortfolio>(
`/businesses/${businessId}/portfolios`,
options,
);
}
export async function listMeshkeeGallery(
businessId: string,
options: ListOptions = {},
) {
const page = options.page ?? 1;
const pageSize = options.pageSize ?? 16;
const params = new URLSearchParams({
page: String(page),
pageSize: String(pageSize),
});
const data = await apiRequest<ListResponse<MeshkeeMediaItem>>(
`/businesses/${businessId}/gallery?${params}`,
{ auth: true },
);
return {
items: (data.items ?? []).map((item) => ({
...item,
id: String(item.id),
})),
total: data.total ?? 0,
page: data.page ?? page,
pageSize: data.pageSize ?? pageSize,
};
}
export async function addMeshkeeGalleryItems(
businessId: string,
input: { files?: File[]; mediaIds?: string[] },
) {
const token = getAccessToken();
const base =
process.env.NEXT_PUBLIC_MESHKEE_API_URL?.replace(/\/$/, "") ||
"http://localhost:3010/api/v1";
if (input.files?.length) {
const form = new FormData();
for (const file of input.files) {
form.append("files", file);
}
if (input.mediaIds?.length) {
form.append("mediaIds", JSON.stringify(input.mediaIds));
}
const response = await fetch(`${base}/businesses/${businessId}/gallery`, {
method: "POST",
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
body: form,
});
const payload = await response.json().catch(() => null);
if (!response.ok) {
const message =
typeof payload?.message === "string"
? payload.message
: Array.isArray(payload?.message)
? payload.message.join(", ")
: `Upload failed (${response.status})`;
throw new Error(message);
}
return {
items: ((payload?.items ?? []) as MeshkeeMediaItem[]).map((item) => ({
...item,
id: String(item.id),
})),
};
}
const data = await apiRequest<{ items: MeshkeeMediaItem[] }>(
`/businesses/${businessId}/gallery`,
{
method: "POST",
auth: true,
body: { mediaIds: input.mediaIds ?? [] },
},
);
return {
items: (data.items ?? []).map((item) => ({
...item,
id: String(item.id),
})),
};
}
export async function deleteMeshkeeGalleryItem(
businessId: string,
mediaId: string,
) {
await apiRequest(`/businesses/${businessId}/gallery/${mediaId}`, {
method: "DELETE",
auth: true,
});
}
export async function listMeshkeeBusinessCategories(businessId: string) {
const data = await apiRequest<{ items: MeshkeeBusinessCategory[] }>(
`/businesses/${businessId}/categories`,
{ auth: true },
);
return (data.items ?? []).map((item) => ({
...item,
id: String(item.id),
parentId: item.parentId == null ? null : String(item.parentId),
}));
}
export async function deleteMeshkeeProduct(
businessId: string,
productId: string,
) {
await apiRequest(`/businesses/${businessId}/products/${productId}`, {
method: "DELETE",
auth: true,
});
}
export async function deleteMeshkeeBlog(businessId: string, blogId: string) {
await apiRequest(`/businesses/${businessId}/blogs/${blogId}`, {
method: "DELETE",
auth: true,
});
}
export async function deleteMeshkeeMedia(businessId: string, mediaId: string) {
await apiRequest(`/businesses/${businessId}/media/${mediaId}`, {
method: "DELETE",
auth: true,
});
}
export async function deleteMeshkeePortfolio(
businessId: string,
portfolioId: string,
) {
await apiRequest(`/businesses/${businessId}/portfolios/${portfolioId}`, {
method: "DELETE",
auth: true,
});
}
/** Collect category id plus all descendant ids. */
export function collectCategorySubtreeIds(
categories: MeshkeeBusinessCategory[],
rootId: string,
): Set<string> {
const ids = new Set<string>([rootId]);
let grew = true;
while (grew) {
grew = false;
for (const category of categories) {
if (
category.parentId &&
ids.has(category.parentId) &&
!ids.has(category.id)
) {
ids.add(category.id);
grew = true;
}
}
}
return ids;
}