admin/services/reviews.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

228 lines
6.8 KiB
TypeScript

import type {
AdminReviewResponseDto,
CreateReviewDto,
HostReplyDto,
OrganizerReviewResponseDto,
ReviewResponseDto,
UpdateReviewDto,
} from '@/api/generated/models'
import { getAdminReviews } from '@/api/generated/admin-reviews/admin-reviews'
import { getReviews } from '@/api/generated/reviews/reviews'
import { unwrapApiPayload } from '@/helpers/listResponse'
import { parseRemittanceList } from '@/helpers/listResponse'
import {
errorResult,
handleServiceError,
type ServiceCallOptions,
type ServiceResult,
shouldBubbleErrorToParent,
successResult,
} from '@/services/errorHandler'
export type EventReview = ReviewResponseDto
export type AdminEventReview = AdminReviewResponseDto
export type OrganizerReview = OrganizerReviewResponseDto
const reviewsApi = getReviews()
const adminReviewsApi = getAdminReviews()
export const EVENT_REVIEWS_PAGE_SIZE = 10
export const ORGANIZER_REVIEWS_PAGE_SIZE = 10
interface PaginatedReviews<T> {
items: T[]
response?: { totalItemsCount?: number; totalPages?: number; page?: number }
}
const unwrapItems = <T>(payload: unknown): T[] => {
if (Array.isArray(payload)) return payload as T[]
if (payload && typeof payload === 'object' && Array.isArray((payload as PaginatedReviews<T>).items)) {
return (payload as PaginatedReviews<T>).items
}
return []
}
export const LIST_EVENT_REVIEWS_PAGE = async (
eventId: string,
page = 1,
pageSize = EVENT_REVIEWS_PAGE_SIZE,
options?: ServiceCallOptions
): Promise<ServiceResult<{ items: EventReview[]; page: number; totalItemsCount: number; totalPages: number }>> => {
try {
const res = await reviewsApi.reviewsControllerListByEvent(eventId, {
sort: '-createdAt',
page,
pageSize,
})
const parsed = parseRemittanceList<EventReview>(res.data, 'items', page, pageSize)
return successResult({
items: parsed.items,
page: parsed.pagination.page,
totalItemsCount: parsed.pagination.totalItemsCount,
totalPages: parsed.pagination.totalPages ?? 0,
})
} catch (error) {
const normalizedError = handleServiceError(error, options)
if (shouldBubbleErrorToParent(options)) throw normalizedError
return errorResult(normalizedError)
}
}
/** Admin list for one event — includes published, hidden, and deleted reviews. */
export const LIST_ADMIN_EVENT_REVIEWS_PAGE = async (
eventId: string,
page = 1,
pageSize = EVENT_REVIEWS_PAGE_SIZE,
options?: ServiceCallOptions
): Promise<ServiceResult<{ items: AdminEventReview[]; page: number; totalItemsCount: number; totalPages: number }>> => {
try {
const res = await adminReviewsApi.adminReviewsControllerList({
sort: '-createdAt',
page,
pageSize,
'filters[eventId]': eventId,
})
const parsed = parseRemittanceList<AdminEventReview>(res.data, 'items', page, pageSize)
return successResult({
items: parsed.items,
page: parsed.pagination.page,
totalItemsCount: parsed.pagination.totalItemsCount,
totalPages: parsed.pagination.totalPages ?? 0,
})
} catch (error) {
const normalizedError = handleServiceError(error, options)
if (shouldBubbleErrorToParent(options)) throw normalizedError
return errorResult(normalizedError)
}
}
export const LIST_EVENT_REVIEWS = async (eventId: string, options?: ServiceCallOptions): Promise<ServiceResult<EventReview[]>> => {
const result = await LIST_EVENT_REVIEWS_PAGE(eventId, 1, 50, options)
return result.ok ? successResult(result.data.items) : result
}
export const LIST_ORGANIZER_REVIEWS = async (
organizerId: string,
options?: ServiceCallOptions
): Promise<ServiceResult<OrganizerReview[]>> => {
try {
const res = await reviewsApi.reviewsControllerListByOrganizer(organizerId, {
sort: '-createdAt',
page: 1,
pageSize: 50,
})
return successResult(unwrapItems<OrganizerReview>(unwrapApiPayload(res.data)))
} catch (error) {
const normalizedError = handleServiceError(error, options)
if (shouldBubbleErrorToParent(options)) throw normalizedError
return errorResult(normalizedError)
}
}
export const LIST_ORGANIZER_REVIEWS_PAGE = async (
organizerId: string,
page = 1,
pageSize = ORGANIZER_REVIEWS_PAGE_SIZE,
options?: ServiceCallOptions
): Promise<ServiceResult<{ items: OrganizerReview[]; page: number; totalItemsCount: number; totalPages: number }>> => {
try {
const res = await reviewsApi.reviewsControllerListByOrganizer(organizerId, {
sort: '-createdAt',
page,
pageSize,
})
const parsed = parseRemittanceList<OrganizerReview>(res.data, 'items', page, pageSize)
return successResult({
items: parsed.items,
page: parsed.pagination.page,
totalItemsCount: parsed.pagination.totalItemsCount,
totalPages: parsed.pagination.totalPages ?? 0,
})
} catch (error) {
const normalizedError = handleServiceError(error, options)
if (shouldBubbleErrorToParent(options)) throw normalizedError
return errorResult(normalizedError)
}
}
/** Published review by this user for the event, if any (from the public event list). */
export const FIND_MY_EVENT_REVIEW = async (
eventId: string,
userId: string,
options?: ServiceCallOptions
): Promise<ServiceResult<EventReview | null>> => {
const result = await LIST_EVENT_REVIEWS(eventId, options)
if (!result.ok) return result
return successResult(result.data.find((review) => review.userId === userId) ?? null)
}
export const CREATE_REVIEW = async (
eventId: string,
dto: CreateReviewDto,
options?: ServiceCallOptions
): Promise<ServiceResult<EventReview>> => {
try {
const res = await reviewsApi.reviewsControllerCreate(eventId, dto)
return successResult(unwrapApiPayload(res.data))
} catch (error) {
const normalizedError = handleServiceError(error, options)
if (shouldBubbleErrorToParent(options)) throw normalizedError
return errorResult(normalizedError)
}
}
export const UPDATE_REVIEW = async (
reviewId: string,
dto: UpdateReviewDto,
options?: ServiceCallOptions
): Promise<ServiceResult<EventReview>> => {
try {
const res = await reviewsApi.reviewsControllerUpdate(reviewId, dto)
return successResult(unwrapApiPayload(res.data))
} catch (error) {
const normalizedError = handleServiceError(error, options)
if (shouldBubbleErrorToParent(options)) throw normalizedError
return errorResult(normalizedError)
}
}
export const HOST_REPLY_REVIEW = async (
reviewId: string,
dto: HostReplyDto,
options?: ServiceCallOptions
): Promise<ServiceResult<EventReview>> => {
try {
const res = await reviewsApi.reviewsControllerHostReply(reviewId, dto)
return successResult(unwrapApiPayload(res.data))
} catch (error) {
const normalizedError = handleServiceError(error, options)
if (shouldBubbleErrorToParent(options)) throw normalizedError
return errorResult(normalizedError)
}
}