feat(orders): migrate orders to backend API
This commit is contained in:
+123
-64
@@ -1,21 +1,87 @@
|
||||
import type { Actor } from "@/lib/actor"
|
||||
import { allow, deny } from "@/lib/decision"
|
||||
import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop"
|
||||
import type { ApiDecision } from "@/lib/errors"
|
||||
import { useAuthStore } from "@/store/auth"
|
||||
import { useOrderStore } from "@/store/orders"
|
||||
import { useShopStore } from "@/store/shops"
|
||||
import { isApiError, toApiError, type ApiDecision } from "@/lib/errors"
|
||||
import type { Order, OrderStatus } from "@/lib/types"
|
||||
|
||||
export function listOrders() {
|
||||
return useOrderStore.getState().orders
|
||||
import { httpJson } from "./http"
|
||||
|
||||
type Paginated<T> = {
|
||||
items: T[]
|
||||
meta: {
|
||||
total: number
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrderById(orderId: string) {
|
||||
return useOrderStore.getState().orders.find((order) => order.id === orderId)
|
||||
export type ListOrdersOptions = {
|
||||
role?: "consumer" | "player" | "owner"
|
||||
status?: OrderStatus
|
||||
offset?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export function listOrdersByConsumer(consumerId: string) {
|
||||
return useOrderStore.getState().orders.filter((order) => order.consumerId === consumerId)
|
||||
type ActionResult = { decision: ApiDecision; order?: Order }
|
||||
|
||||
function withOffsetLimit(path: string, options?: ListOrdersOptions): string {
|
||||
const offset = options?.offset ?? 0
|
||||
const limit = options?.limit ?? 1000
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
})
|
||||
|
||||
if (options?.role) searchParams.set("role", options.role)
|
||||
if (options?.status) searchParams.set("status", options.status)
|
||||
|
||||
return `${path}?${searchParams.toString()}`
|
||||
}
|
||||
|
||||
function unwrapOrder(value: unknown): Order | undefined {
|
||||
if (typeof value !== "object" || value === null) return undefined
|
||||
if ("order" in value) {
|
||||
const envelope = value as { order?: Order }
|
||||
return envelope.order
|
||||
}
|
||||
return value as Order
|
||||
}
|
||||
|
||||
function denyFromError(error: unknown): ApiDecision {
|
||||
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
||||
return deny(401, "请先登录")
|
||||
}
|
||||
|
||||
const apiError = toApiError(error)
|
||||
return deny(apiError.code, apiError.msg)
|
||||
}
|
||||
|
||||
export async function listOrders(options?: ListOrdersOptions): Promise<Order[]> {
|
||||
const res = await httpJson<Paginated<Order>>(withOffsetLimit("/api/v1/orders", options), {
|
||||
cache: "no-store",
|
||||
})
|
||||
return res.items
|
||||
}
|
||||
|
||||
export async function getOrderById(orderId: string): Promise<Order | undefined> {
|
||||
try {
|
||||
const res = await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(orderId)}`, {
|
||||
cache: "no-store",
|
||||
})
|
||||
return unwrapOrder(res)
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
||||
throw error
|
||||
}
|
||||
if (isApiError(error) && error.code === 404) {
|
||||
return undefined
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function listOrdersByConsumer(consumerId: string): Promise<Order[]> {
|
||||
const items = await listOrders({ role: "consumer" })
|
||||
return items.filter((order) => order.consumerId === consumerId)
|
||||
}
|
||||
|
||||
interface CreatePaidOrderInput {
|
||||
@@ -26,69 +92,62 @@ interface CreatePaidOrderInput {
|
||||
note?: string
|
||||
}
|
||||
|
||||
function resolveActorContext(): { actor?: Actor; decision: ApiDecision } {
|
||||
const auth = useAuthStore.getState()
|
||||
if (!auth.user?.id) {
|
||||
return { decision: deny(401, "请先登录") }
|
||||
}
|
||||
|
||||
const shopId =
|
||||
auth.currentRole === "owner"
|
||||
? resolveOwnerShop(auth.user.id, useShopStore.getState().shops)?.id
|
||||
: undefined
|
||||
|
||||
return {
|
||||
actor: {
|
||||
userId: auth.user.id,
|
||||
role: auth.currentRole,
|
||||
shopId,
|
||||
},
|
||||
decision: allow(),
|
||||
export async function createPaidOrder(input: CreatePaidOrderInput): Promise<ActionResult> {
|
||||
try {
|
||||
const res = await httpJson<unknown>("/api/v1/orders/paid", {
|
||||
method: "POST",
|
||||
cache: "no-store",
|
||||
json: input,
|
||||
})
|
||||
const order = unwrapOrder(res)
|
||||
if (!order) {
|
||||
return { decision: deny(500, "订单创建失败") }
|
||||
}
|
||||
return { decision: allow(), order }
|
||||
} catch (error) {
|
||||
return { decision: denyFromError(error) }
|
||||
}
|
||||
}
|
||||
|
||||
export function createPaidOrder(input: CreatePaidOrderInput) {
|
||||
const { actor, decision } = resolveActorContext()
|
||||
if (!actor) return { decision }
|
||||
return useOrderStore.getState().createPaidOrder(input, actor)
|
||||
async function postOrderAction(orderId: string, action: string): Promise<ActionResult> {
|
||||
try {
|
||||
const res = await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(orderId)}/${action}`, {
|
||||
method: "POST",
|
||||
cache: "no-store",
|
||||
})
|
||||
|
||||
const order = unwrapOrder(res)
|
||||
if (order) {
|
||||
return { decision: allow(), order }
|
||||
}
|
||||
|
||||
const refetched = await getOrderById(orderId).catch(() => undefined)
|
||||
if (refetched) {
|
||||
return { decision: allow(), order: refetched }
|
||||
}
|
||||
|
||||
return { decision: allow() }
|
||||
} catch (error) {
|
||||
return { decision: denyFromError(error) }
|
||||
}
|
||||
}
|
||||
|
||||
export function payOrder(orderId: string) {
|
||||
const { actor, decision } = resolveActorContext()
|
||||
if (!actor) return { decision }
|
||||
return useOrderStore.getState().payOrder(orderId, actor)
|
||||
export async function payOrder(orderId: string): Promise<ActionResult> {
|
||||
return postOrderAction(orderId, "pay")
|
||||
}
|
||||
|
||||
export function acceptOrder(orderId: string) {
|
||||
const { actor, decision } = resolveActorContext()
|
||||
if (!actor) return { decision }
|
||||
return useOrderStore.getState().acceptOrder(orderId, actor)
|
||||
export async function acceptOrder(orderId: string): Promise<ActionResult> {
|
||||
return postOrderAction(orderId, "accept")
|
||||
}
|
||||
|
||||
export function acceptOrderAsActor(orderId: string, actor: Actor) {
|
||||
return useOrderStore.getState().acceptOrder(orderId, actor)
|
||||
export async function requestClose(orderId: string): Promise<ActionResult> {
|
||||
return postOrderAction(orderId, "request-close")
|
||||
}
|
||||
|
||||
export function requestClose(orderId: string) {
|
||||
const { actor, decision } = resolveActorContext()
|
||||
if (!actor) return { decision }
|
||||
return useOrderStore.getState().requestClose(orderId, actor)
|
||||
export async function confirmClose(orderId: string): Promise<ActionResult> {
|
||||
return postOrderAction(orderId, "confirm-close")
|
||||
}
|
||||
|
||||
export function confirmClose(orderId: string) {
|
||||
const { actor, decision } = resolveActorContext()
|
||||
if (!actor) return { decision }
|
||||
return useOrderStore.getState().confirmClose(orderId, actor)
|
||||
}
|
||||
|
||||
export function cancelPreAccept(orderId: string) {
|
||||
const { actor, decision } = resolveActorContext()
|
||||
if (!actor) return { decision }
|
||||
return useOrderStore.getState().cancelPreAccept(orderId, actor)
|
||||
}
|
||||
|
||||
export function markDisputed(orderId: string) {
|
||||
const { actor, decision } = resolveActorContext()
|
||||
if (!actor) return { decision }
|
||||
return useOrderStore.getState().markDisputed(orderId, actor)
|
||||
export async function cancelPreAccept(orderId: string): Promise<ActionResult> {
|
||||
return postOrderAction(orderId, "cancel")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user