434 lines
12 KiB
TypeScript
434 lines
12 KiB
TypeScript
import { getPayload } from 'payload'
|
||
import config from '@payload-config'
|
||
import { NextResponse } from 'next/server'
|
||
import {
|
||
getAllMedusaProducts,
|
||
transformMedusaProductToPayload,
|
||
getMedusaProductsPaginated,
|
||
getProductCollection,
|
||
} from '@/lib/medusa'
|
||
|
||
/**
|
||
* 同步 Medusa 商品到 Payload CMS
|
||
* GET /api/sync-medusa
|
||
* GET /api/sync-medusa?medusaId=prod_xxx (通过 Medusa ID 同步单个商品)
|
||
* GET /api/sync-medusa?payloadId=123 (通过 Payload ID 同步单个商品)
|
||
*/
|
||
export async function GET(request: Request) {
|
||
try {
|
||
const { searchParams } = new URL(request.url)
|
||
const medusaId = searchParams.get('medusaId')
|
||
const payloadId = searchParams.get('payloadId')
|
||
const collection = searchParams.get('collection')
|
||
const forceUpdate = searchParams.get('forceUpdate') === 'true'
|
||
|
||
const payload = await getPayload({ config })
|
||
|
||
// 同步单个商品(通过 Medusa ID)
|
||
if (medusaId) {
|
||
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate)
|
||
return NextResponse.json(result)
|
||
}
|
||
|
||
// 同步单个商品(通过 Payload ID)
|
||
if (payloadId) {
|
||
const result = await syncSingleProductByPayloadId(
|
||
payload,
|
||
payloadId,
|
||
collection || '',
|
||
forceUpdate,
|
||
)
|
||
return NextResponse.json(result)
|
||
}
|
||
|
||
// 同步所有商品
|
||
const result = await syncAllProducts(payload, forceUpdate)
|
||
return NextResponse.json(result)
|
||
} catch (error) {
|
||
console.error('Sync error:', error)
|
||
return NextResponse.json(
|
||
{
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Unknown error',
|
||
},
|
||
{ status: 500 },
|
||
)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通过 Medusa ID 同步单个商品
|
||
*/
|
||
async function syncSingleProductByMedusaId(payload: any, medusaId: string, forceUpdate: boolean) {
|
||
try {
|
||
// 从 Medusa 获取商品数据
|
||
const medusaProducts = await getAllMedusaProducts()
|
||
const medusaProduct = medusaProducts.find((p) => p.id === medusaId)
|
||
|
||
if (!medusaProduct) {
|
||
return {
|
||
success: false,
|
||
action: 'not_found',
|
||
message: `Medusa 中未找到商品 ${medusaId}`,
|
||
}
|
||
}
|
||
|
||
// 确定应该同步到哪个 collection
|
||
const targetCollection = getProductCollection(medusaProduct)
|
||
const otherCollection =
|
||
targetCollection === 'preorder-products' ? 'products' : 'preorder-products'
|
||
|
||
// 在目标 collection 中检查是否已存在
|
||
const existingInTarget = await payload.find({
|
||
collection: targetCollection,
|
||
where: {
|
||
medusaId: { equals: medusaId },
|
||
},
|
||
limit: 1,
|
||
})
|
||
|
||
// 在另一个 collection 中检查是否存在(产品类型可能改变)
|
||
const existingInOther = await payload.find({
|
||
collection: otherCollection,
|
||
where: {
|
||
medusaId: { equals: medusaId },
|
||
},
|
||
limit: 1,
|
||
})
|
||
|
||
const productData = transformMedusaProductToPayload(medusaProduct)
|
||
|
||
// 如果在另一个 collection 中存在,需要删除并在正确的 collection 中创建
|
||
if (existingInOther.docs[0]) {
|
||
await payload.delete({
|
||
collection: otherCollection,
|
||
id: existingInOther.docs[0].id,
|
||
})
|
||
|
||
const created = await payload.create({
|
||
collection: targetCollection,
|
||
data: productData,
|
||
})
|
||
|
||
return {
|
||
success: true,
|
||
action: 'moved',
|
||
message: `商品 ${medusaId} 已从 ${otherCollection} 移动到 ${targetCollection}`,
|
||
productId: created.id,
|
||
collection: targetCollection,
|
||
}
|
||
}
|
||
|
||
const existingProduct = existingInTarget.docs[0]
|
||
|
||
// 如果存在且不强制更新,跳过
|
||
if (existingProduct && !forceUpdate) {
|
||
return {
|
||
success: true,
|
||
action: 'skipped',
|
||
message: `商品 ${medusaId} 已存在于 ${targetCollection}`,
|
||
productId: existingProduct.id,
|
||
collection: targetCollection,
|
||
}
|
||
}
|
||
|
||
if (existingProduct) {
|
||
// 更新现有商品
|
||
const updated = await payload.update({
|
||
collection: targetCollection,
|
||
id: existingProduct.id,
|
||
data: productData,
|
||
})
|
||
|
||
return {
|
||
success: true,
|
||
action: 'updated',
|
||
message: `商品 ${medusaId} 已更新于 ${targetCollection}`,
|
||
productId: updated.id,
|
||
collection: targetCollection,
|
||
}
|
||
} else {
|
||
// 创建新商品
|
||
const created = await payload.create({
|
||
collection: targetCollection,
|
||
data: productData,
|
||
})
|
||
|
||
return {
|
||
success: true,
|
||
action: 'created',
|
||
message: `商品 ${medusaId} 已创建于 ${targetCollection}`,
|
||
productId: created.id,
|
||
collection: targetCollection,
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error(`Error syncing product ${medusaId}:`, error)
|
||
return {
|
||
success: false,
|
||
action: 'error',
|
||
message: `同步商品 ${medusaId} 失败`,
|
||
error: error instanceof Error ? error.message : 'Unknown error',
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通过 Payload ID 同步单个商品
|
||
*/
|
||
async function syncSingleProductByPayloadId(
|
||
payload: any,
|
||
payloadId: string,
|
||
collection: string,
|
||
forceUpdate: boolean,
|
||
) {
|
||
try {
|
||
// 如果未指定 collection,尝试在两个 collections 中查找
|
||
let product: any = null
|
||
let foundCollection: string = collection
|
||
|
||
if (collection) {
|
||
product = await payload.findByID({
|
||
collection,
|
||
id: payloadId,
|
||
})
|
||
} else {
|
||
// 尝试 preorder-products
|
||
try {
|
||
product = await payload.findByID({
|
||
collection: 'preorder-products',
|
||
id: payloadId,
|
||
})
|
||
foundCollection = 'preorder-products'
|
||
} catch {
|
||
// 最后尝试旧的 products
|
||
product = await payload.findByID({
|
||
collection: 'products',
|
||
id: payloadId,
|
||
})
|
||
foundCollection = 'products'
|
||
}
|
||
}
|
||
|
||
if (!product) {
|
||
return {
|
||
success: false,
|
||
action: 'not_found',
|
||
message: `未找到商品 ID: ${payloadId}`,
|
||
}
|
||
}
|
||
|
||
// 如果没有 medusaId,无法同步
|
||
if (!product.medusaId) {
|
||
return {
|
||
success: false,
|
||
action: 'no_medusa_id',
|
||
message: `商品 ${product.title} 没有关联的 Medusa ID,无法同步`,
|
||
}
|
||
}
|
||
|
||
// 使用 medusaId 同步
|
||
return await syncSingleProductByMedusaId(payload, product.medusaId, forceUpdate)
|
||
} catch (error) {
|
||
console.error(`Error syncing product by Payload ID ${payloadId}:`, error)
|
||
return {
|
||
success: false,
|
||
action: 'error',
|
||
message: `同步商品 ID ${payloadId} 失败`,
|
||
error: error instanceof Error ? error.message : 'Unknown error',
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 同步所有商品
|
||
*/
|
||
async function syncAllProducts(payload: any, forceUpdate: boolean) {
|
||
try {
|
||
let offset = 0
|
||
const limit = 100
|
||
let hasMore = true
|
||
const results = {
|
||
total: 0,
|
||
created: 0,
|
||
updated: 0,
|
||
skipped: 0,
|
||
errors: 0,
|
||
details: [] as any[],
|
||
}
|
||
|
||
while (hasMore) {
|
||
// 分页获取 Medusa 商品
|
||
const { products: medusaProducts, count } = await getMedusaProductsPaginated(offset, limit)
|
||
|
||
if (medusaProducts.length === 0) {
|
||
hasMore = false
|
||
break
|
||
}
|
||
|
||
results.total += medusaProducts.length
|
||
|
||
// 处理每个商品
|
||
for (const medusaProduct of medusaProducts) {
|
||
try {
|
||
// 确定应该同步到哪个 collection
|
||
const targetCollection = getProductCollection(medusaProduct)
|
||
const otherCollection =
|
||
targetCollection === 'preorder-products' ? 'products' : 'preorder-products'
|
||
|
||
// 在目标 collection 中检查是否已存在
|
||
const existingInTarget = await payload.find({
|
||
collection: targetCollection,
|
||
where: {
|
||
medusaId: { equals: medusaProduct.id },
|
||
},
|
||
limit: 1,
|
||
})
|
||
|
||
// 在另一个 collection 中检查是否存在(产品类型可能改变)
|
||
const existingInOther = await payload.find({
|
||
collection: otherCollection,
|
||
where: {
|
||
medusaId: { equals: medusaProduct.id },
|
||
},
|
||
limit: 1,
|
||
})
|
||
|
||
const productData = transformMedusaProductToPayload(medusaProduct)
|
||
|
||
// 如果在错误的 collection 中,移动它
|
||
if (existingInOther.docs[0]) {
|
||
await payload.delete({
|
||
collection: otherCollection,
|
||
id: existingInOther.docs[0].id,
|
||
})
|
||
|
||
await payload.create({
|
||
collection: targetCollection,
|
||
data: productData,
|
||
})
|
||
|
||
results.updated++
|
||
results.details.push({
|
||
medusaId: medusaProduct.id,
|
||
title: medusaProduct.title,
|
||
action: 'moved',
|
||
from: otherCollection,
|
||
to: targetCollection,
|
||
})
|
||
continue
|
||
}
|
||
|
||
const existingProduct = existingInTarget.docs[0]
|
||
|
||
// 如果存在且不强制更新,跳过
|
||
if (existingProduct && !forceUpdate) {
|
||
results.skipped++
|
||
results.details.push({
|
||
medusaId: medusaProduct.id,
|
||
title: medusaProduct.title,
|
||
action: 'skipped',
|
||
collection: targetCollection,
|
||
})
|
||
continue
|
||
}
|
||
|
||
if (existingProduct) {
|
||
// 更新
|
||
await payload.update({
|
||
collection: targetCollection,
|
||
id: existingProduct.id,
|
||
data: productData,
|
||
})
|
||
results.updated++
|
||
results.details.push({
|
||
medusaId: medusaProduct.id,
|
||
title: medusaProduct.title,
|
||
action: 'updated',
|
||
collection: targetCollection,
|
||
})
|
||
} else {
|
||
// 创建
|
||
await payload.create({
|
||
collection: targetCollection,
|
||
data: productData,
|
||
})
|
||
results.created++
|
||
results.details.push({
|
||
medusaId: medusaProduct.id,
|
||
title: medusaProduct.title,
|
||
action: 'created',
|
||
collection: targetCollection,
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error(`Error processing product ${medusaProduct.id}:`, error)
|
||
results.errors++
|
||
results.details.push({
|
||
medusaId: medusaProduct.id,
|
||
title: medusaProduct.title,
|
||
action: 'error',
|
||
error: error instanceof Error ? error.message : 'Unknown error',
|
||
})
|
||
}
|
||
}
|
||
|
||
// 更新偏移量
|
||
offset += limit
|
||
if (offset >= count) {
|
||
hasMore = false
|
||
}
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
message: `同步完成: ${results.created} 个创建, ${results.updated} 个更新, ${results.skipped} 个跳过, ${results.errors} 个错误`,
|
||
results,
|
||
}
|
||
} catch (error) {
|
||
console.error('Error syncing all products:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* POST /api/sync-medusa
|
||
* 触发手动同步(需要认证)
|
||
*/
|
||
export async function POST(request: Request) {
|
||
try {
|
||
const payload = await getPayload({ config })
|
||
|
||
// 可以在这里添加认证检查
|
||
// const { user } = await payload.auth({ headers: request.headers })
|
||
// if (!user) {
|
||
// return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||
// }
|
||
|
||
const body = await request.json()
|
||
const { medusaId, payloadId, collection = '', forceUpdate = true } = body
|
||
|
||
if (medusaId) {
|
||
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate)
|
||
return NextResponse.json(result)
|
||
}
|
||
|
||
if (payloadId) {
|
||
const result = await syncSingleProductByPayloadId(payload, payloadId, collection, forceUpdate)
|
||
return NextResponse.json(result)
|
||
}
|
||
|
||
const result = await syncAllProducts(payload, forceUpdate)
|
||
return NextResponse.json(result)
|
||
} catch (error) {
|
||
console.error('Sync error:', error)
|
||
return NextResponse.json(
|
||
{
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Unknown error',
|
||
},
|
||
{ status: 500 },
|
||
)
|
||
}
|
||
}
|