91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
import { allow, deny } from "@/lib/decision"
|
|
import { toApiError, type ApiDecision } from "@/lib/errors"
|
|
import type { Review } from "@/lib/types"
|
|
|
|
import { httpJson } from "./http"
|
|
|
|
type Paginated<T> = {
|
|
items: T[] | null
|
|
meta: {
|
|
total: number
|
|
offset: number
|
|
limit: number
|
|
}
|
|
}
|
|
|
|
export type ListReviewsOptions = {
|
|
offset?: number
|
|
limit?: number
|
|
}
|
|
|
|
function withOffsetLimit(path: string, options?: ListReviewsOptions): string {
|
|
const offset = options?.offset ?? 0
|
|
const limit = options?.limit ?? 100
|
|
|
|
const searchParams = new URLSearchParams({
|
|
offset: String(offset),
|
|
limit: String(limit),
|
|
})
|
|
return `${path}?${searchParams.toString()}`
|
|
}
|
|
|
|
function unwrapItems<T>(value: unknown): T[] {
|
|
if (Array.isArray(value)) return value as T[]
|
|
if (typeof value !== "object" || value === null) {
|
|
throw new Error("Invalid response")
|
|
}
|
|
if ("items" in value) {
|
|
const envelope = value as { items?: unknown }
|
|
if (Array.isArray(envelope.items)) return envelope.items as T[]
|
|
if (envelope.items === null) return []
|
|
}
|
|
throw new Error("Invalid response")
|
|
}
|
|
|
|
export async function listReviews(options?: ListReviewsOptions): Promise<Review[]> {
|
|
const res = await httpJson<Paginated<Review> | Review[]>(
|
|
withOffsetLimit("/api/v1/reviews", options),
|
|
{
|
|
cache: "no-store",
|
|
},
|
|
)
|
|
return unwrapItems<Review>(res)
|
|
}
|
|
|
|
export async function listReviewsByOrder(orderId: string): Promise<Review[]> {
|
|
const res = await httpJson<Paginated<Review> | Review[]>(
|
|
`/api/v1/orders/${encodeURIComponent(orderId)}/reviews`,
|
|
{ cache: "no-store" },
|
|
)
|
|
return unwrapItems<Review>(res)
|
|
}
|
|
|
|
export async function listReviewsByTargetUser(userId: string): Promise<Review[]> {
|
|
const res = await httpJson<Paginated<Review> | Review[]>(
|
|
`/api/v1/users/${encodeURIComponent(userId)}/reviews`,
|
|
{ cache: "no-store" },
|
|
)
|
|
return unwrapItems<Review>(res)
|
|
}
|
|
|
|
export async function submitReview(input: {
|
|
orderId: string
|
|
rating: number
|
|
content?: string
|
|
}): Promise<ApiDecision> {
|
|
try {
|
|
await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(input.orderId)}/review`, {
|
|
method: "POST",
|
|
cache: "no-store",
|
|
json: { rating: input.rating, content: input.content },
|
|
})
|
|
return allow()
|
|
} catch (error) {
|
|
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
|
return deny(401, "请先登录")
|
|
}
|
|
const apiError = toApiError(error)
|
|
return deny(apiError.code, apiError.msg)
|
|
}
|
|
}
|