Compare commits
No commits in common. "c6771a70983dfa75fabd2150ba80c5e1d0f46ac4" and "c8de57af22041e67de661eeb5c082b62a333d786" have entirely different histories.
c6771a7098
...
c8de57af22
|
|
@ -6,47 +6,39 @@ import config from '@payload-config'
|
|||
* API Route: Reset All Data
|
||||
* POST /api/admin/reset-data
|
||||
*
|
||||
* Body: { mode?: 'full' | 'medusa-only' }
|
||||
*
|
||||
* full (默认): 清理 Payload + 清理 Medusa + 导入 Medusa seed 数据
|
||||
* medusa-only: 仅清理 Medusa + 导入 Medusa seed 数据(不动 Payload)
|
||||
* 执行完整的数据重置流程:
|
||||
* 1. 清理 Payload CMS 数据
|
||||
* 2. 清理 Medusa 数据
|
||||
* 3. 导入 Medusa seed 数据
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json().catch(() => ({}))
|
||||
const mode: 'full' | 'medusa-only' = body.mode === 'medusa-only' ? 'medusa-only' : 'full'
|
||||
const MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL || 'http://localhost:9000'
|
||||
const results: any = {
|
||||
steps: [],
|
||||
success: true,
|
||||
mode,
|
||||
}
|
||||
|
||||
// ==================== 步骤 1: 清理 Payload 数据(full 模式才执行)====================
|
||||
if (mode === 'full') {
|
||||
console.log('🧹 [1/3] 开始清理 Payload CMS 数据...')
|
||||
const payloadResult = await cleanPayloadData()
|
||||
results.steps.push({
|
||||
step: 1,
|
||||
name: 'Clean Payload',
|
||||
success: payloadResult.success,
|
||||
deleted: payloadResult.totalDeleted,
|
||||
details: payloadResult.details,
|
||||
})
|
||||
// ==================== 步骤 1: 清理 Payload 数据 ====================
|
||||
console.log('🧹 [1/3] 开始清理 Payload CMS 数据...')
|
||||
const payloadResult = await cleanPayloadData()
|
||||
results.steps.push({
|
||||
step: 1,
|
||||
name: 'Clean Payload',
|
||||
success: payloadResult.success,
|
||||
deleted: payloadResult.totalDeleted,
|
||||
details: payloadResult.details,
|
||||
})
|
||||
|
||||
if (!payloadResult.success) {
|
||||
results.success = false
|
||||
return NextResponse.json(results, { status: 500 })
|
||||
}
|
||||
} else {
|
||||
console.log('⏭️ [1/3] medusa-only 模式,跳过 Payload 清理')
|
||||
results.steps.push({ step: 1, name: 'Clean Payload', success: true, skipped: true })
|
||||
if (!payloadResult.success) {
|
||||
results.success = false
|
||||
return NextResponse.json(results, { status: 500 })
|
||||
}
|
||||
|
||||
// ==================== 步骤 2: 清理 Medusa 数据 ====================
|
||||
console.log('🧹 [2/3] 开始清理 Medusa 数据...')
|
||||
try {
|
||||
const cleanResponse = await fetch(`${MEDUSA_BACKEND_URL}/hooks/clean`, {
|
||||
const cleanResponse = await fetch(`${MEDUSA_BACKEND_URL}/admin/custom/clean`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
@ -55,8 +47,7 @@ export async function POST(request: NextRequest) {
|
|||
})
|
||||
|
||||
if (!cleanResponse.ok) {
|
||||
const bodyText = await cleanResponse.text().catch(() => '')
|
||||
throw new Error(`Medusa clean failed (${cleanResponse.status}): ${bodyText || cleanResponse.statusText}`)
|
||||
throw new Error(`Medusa clean failed: ${cleanResponse.statusText}`)
|
||||
}
|
||||
|
||||
const cleanData = await cleanResponse.json()
|
||||
|
|
@ -68,34 +59,30 @@ export async function POST(request: NextRequest) {
|
|||
})
|
||||
console.log('✅ Medusa 数据清理完成')
|
||||
} catch (error) {
|
||||
const errMsg = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('❌ Medusa 清理失败:', errMsg)
|
||||
console.error('❌ Medusa 清理失败:', error)
|
||||
results.steps.push({
|
||||
step: 2,
|
||||
name: 'Clean Medusa',
|
||||
success: false,
|
||||
error: errMsg,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
})
|
||||
results.success = false
|
||||
results.error = `[步骤2] ${errMsg}`
|
||||
return NextResponse.json(results, { status: 500 })
|
||||
}
|
||||
|
||||
// ==================== 步骤 3: Seed Medusa 数据 ====================
|
||||
console.log('🌱 [3/3] 开始导入 Medusa 数据...')
|
||||
try {
|
||||
const seedResponse = await fetch(`${MEDUSA_BACKEND_URL}/hooks/seed-pro`, {
|
||||
const seedResponse = await fetch(`${MEDUSA_BACKEND_URL}/admin/custom/seed-pro`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-payload-api-key': process.env.PAYLOAD_API_KEY || '',
|
||||
},
|
||||
// seed:pro 可能需要较长时间
|
||||
})
|
||||
|
||||
if (!seedResponse.ok) {
|
||||
const bodyText = await seedResponse.text().catch(() => '')
|
||||
throw new Error(`Medusa seed failed (${seedResponse.status}): ${bodyText || seedResponse.statusText}`)
|
||||
throw new Error(`Medusa seed failed: ${seedResponse.statusText}`)
|
||||
}
|
||||
|
||||
const seedData = await seedResponse.json()
|
||||
|
|
@ -107,24 +94,20 @@ export async function POST(request: NextRequest) {
|
|||
})
|
||||
console.log('✅ Medusa 数据导入完成')
|
||||
} catch (error) {
|
||||
const errMsg = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('❌ Medusa seed 失败:', errMsg)
|
||||
console.error('❌ Medusa seed 失败:', error)
|
||||
results.steps.push({
|
||||
step: 3,
|
||||
name: 'Seed Medusa',
|
||||
success: false,
|
||||
error: errMsg,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
})
|
||||
results.success = false
|
||||
results.error = `[步骤3] ${errMsg}`
|
||||
return NextResponse.json(results, { status: 500 })
|
||||
}
|
||||
|
||||
// ==================== 完成 ====================
|
||||
console.log('✨ 数据重置完成!')
|
||||
results.message = mode === 'medusa-only'
|
||||
? 'Medusa 数据重置完成!现在可以同步 Medusa 商品到 Payload CMS。'
|
||||
: '数据重置完成!现在可以同步 Medusa 商品到 Payload CMS。'
|
||||
results.message = '数据重置完成!现在可以同步 Medusa 商品到 Payload CMS。'
|
||||
|
||||
return NextResponse.json(results, { status: 200 })
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
|
||||
/**
|
||||
* POST /api/preorders/increment-count
|
||||
* 内部接口:在预购商品的 orderCount 上直接累加(由 Medusa subscriber 调用)
|
||||
*
|
||||
* Body: { medusaId: string; increment: number }
|
||||
* Auth: x-payload-api-key header
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const apiKey = req.headers.get('x-payload-api-key')
|
||||
if (!apiKey || apiKey !== process.env.PAYLOAD_API_KEY) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const { medusaId, increment } = await req.json()
|
||||
|
||||
if (!medusaId || typeof increment !== 'number') {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'medusaId 和 increment (number) 为必填' },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
// 查找对应的预购商品
|
||||
const result = await payload.find({
|
||||
collection: 'preorder-products',
|
||||
where: { medusaId: { equals: medusaId } },
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
const product = result.docs[0]
|
||||
if (!product) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `未找到 medusaId=${medusaId} 的预购商品` },
|
||||
{ status: 404 },
|
||||
)
|
||||
}
|
||||
|
||||
const currentCount = typeof product.orderCount === 'number' ? product.orderCount : 0
|
||||
const newCount = Math.max(0, currentCount + increment)
|
||||
|
||||
await payload.update({
|
||||
collection: 'preorder-products',
|
||||
id: product.id,
|
||||
data: { orderCount: newCount },
|
||||
})
|
||||
|
||||
console.log(
|
||||
`[increment-count] ✅ ${product.title}: ${currentCount} → ${newCount} (+${increment})`,
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
title: product.title,
|
||||
previousCount: currentCount,
|
||||
newCount,
|
||||
increment,
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('[increment-count] ❌', error?.message)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error?.message || 'Unknown error' },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -98,31 +98,18 @@ export async function GET(request: NextRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
// 在非 forceUpdate 模式下,跳过已存在的产品(只同步新产品)
|
||||
if (!forceUpdate && existingProduct) {
|
||||
results.skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
if (existingProduct) {
|
||||
// 构建更新数据:forceUpdate 时覆盖所有字段,否则只更新 Medusa 来源字段(保留 Payload 编辑内容)
|
||||
// 强制更新:更新所有 Medusa 同步字段
|
||||
const updateData: any = {
|
||||
lastSyncedAt: productData.lastSyncedAt,
|
||||
medusaId: productData.medusaId,
|
||||
seedId: productData.seedId,
|
||||
title: productData.title,
|
||||
handle: productData.handle,
|
||||
description: productData.description,
|
||||
startPrice: productData.startPrice,
|
||||
tags: productData.tags,
|
||||
type: productData.type,
|
||||
collection: productData.collection,
|
||||
category: productData.category,
|
||||
height: productData.height,
|
||||
width: productData.width,
|
||||
length: productData.length,
|
||||
weight: productData.weight,
|
||||
midCode: productData.midCode,
|
||||
hsCode: productData.hsCode,
|
||||
countryOfOrigin: productData.countryOfOrigin,
|
||||
// thumbnail: forceUpdate 时覆盖,否则保留 Payload 已有值
|
||||
thumbnail: forceUpdate
|
||||
? (productData.thumbnail || existingProduct.thumbnail)
|
||||
: (existingProduct.thumbnail || productData.thumbnail),
|
||||
...productData,
|
||||
// thumbnail 保留 Payload 已有值(除非 forceUpdate 或为空)
|
||||
thumbnail: existingProduct.thumbnail || productData.thumbnail,
|
||||
}
|
||||
|
||||
// 如果需要跨 collection 移动
|
||||
|
|
@ -141,10 +128,7 @@ export async function GET(request: NextRequest) {
|
|||
// 新建
|
||||
await payload.create({
|
||||
collection: targetCollection,
|
||||
data: {
|
||||
...productData,
|
||||
status: (productData.status as 'draft' | 'published') ?? 'draft',
|
||||
},
|
||||
data: { ...productData },
|
||||
})
|
||||
results.created++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,74 +9,69 @@ interface Props {
|
|||
/**
|
||||
* Reset Data Button
|
||||
* 一键重置所有数据:清理 Payload + 清理 Medusa + Seed Medusa
|
||||
* 或仅重置 Medusa:清理 Medusa + Seed Medusa(不动 Payload)
|
||||
*/
|
||||
export function ResetDataButton({ className }: Props) {
|
||||
const [loading, setLoading] = useState<'full' | 'medusa-only' | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
const [details, setDetails] = useState<any>(null)
|
||||
|
||||
const handleReset = async (mode: 'full' | 'medusa-only') => {
|
||||
const confirmMsg = mode === 'medusa-only'
|
||||
? '⚠️ 重置 Medusa 数据\n\n此操作将:\n1. 清理所有 Medusa 数据\n2. 重新导入 Medusa seed 数据\n\nPayload CMS 数据不受影响。\n\n⚠️ 此操作不可撤销!确认继续吗?'
|
||||
: '⚠️ 危险操作:重置所有数据\n\n此操作将:\n1. 清理所有 Payload CMS 数据(保留用户)\n2. 清理所有 Medusa 数据\n3. 重新导入 Medusa seed 数据\n\n⚠️ 此操作不可撤销!确认要继续吗?'
|
||||
const handleResetData = async () => {
|
||||
if (!confirm(
|
||||
'⚠️ 危险操作:重置所有数据\n\n' +
|
||||
'此操作将:\n' +
|
||||
'1. 清理所有 Payload CMS 数据(保留用户)\n' +
|
||||
'2. 清理所有 Medusa 数据\n' +
|
||||
'3. 重新导入 Medusa seed 数据\n\n' +
|
||||
'⚠️ 此操作不可撤销!\n\n' +
|
||||
'确认要继续吗?'
|
||||
)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!confirm(confirmMsg)) return
|
||||
|
||||
setLoading(mode)
|
||||
setLoading(true)
|
||||
setMessage('🔄 开始数据重置流程...')
|
||||
setDetails(null)
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/reset-data', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mode }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (!result.success) {
|
||||
// 优先显示顶级 error,否则找第一个失败步骤的错误
|
||||
const stepError = result.steps?.find((s: any) => !s.success && !s.skipped && s.error)?.error
|
||||
throw new Error(result.error || stepError || 'Reset failed')
|
||||
throw new Error(result.error || 'Reset failed')
|
||||
}
|
||||
|
||||
setDetails(result)
|
||||
setMessage(
|
||||
mode === 'medusa-only'
|
||||
? '✅ Medusa 数据重置完成!\n\n下一步:\n1. 同步 Medusa 商品到 Payload CMS'
|
||||
: '✅ 数据重置完成!\n\n下一步:\n1. 同步 Medusa 商品到 Payload CMS\n2. 设置 ProductRecommendations\n3. 配置 PreorderProducts 的预购设置'
|
||||
'✅ 数据重置完成!\n\n' +
|
||||
'下一步:\n' +
|
||||
'1. 同步 Medusa 商品到 Payload CMS\n' +
|
||||
'2. 设置 ProductRecommendations\n' +
|
||||
'3. 配置 PreorderProducts 的预购设置'
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('数据重置失败:', error)
|
||||
setMessage('❌ 重置失败: ' + (error instanceof Error ? error.message : 'Unknown error'))
|
||||
} finally {
|
||||
setLoading(null)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetData = () => handleReset('full')
|
||||
const handleResetMedusaOnly = () => handleReset('medusa-only')
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap', marginBottom: '1rem' }}>
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<Button
|
||||
onClick={handleResetData}
|
||||
buttonStyle="error"
|
||||
disabled={loading !== null}
|
||||
disabled={loading}
|
||||
size="medium"
|
||||
>
|
||||
{loading === 'full' ? '🔄 重置中...' : '🗑️ 重置所有数据'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleResetMedusaOnly}
|
||||
buttonStyle="secondary"
|
||||
disabled={loading !== null}
|
||||
size="medium"
|
||||
>
|
||||
{loading === 'medusa-only' ? '🔄 重置中...' : '🔄 仅重置 Medusa'}
|
||||
{loading ? '🔄 重置中...' : '🗑️ 重置所有数据'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -113,8 +108,8 @@ export function ResetDataButton({ className }: Props) {
|
|||
<strong>
|
||||
[{step.step}/3] {step.name}:{' '}
|
||||
</strong>
|
||||
<span style={{ color: step.skipped ? '#888' : step.success ? 'green' : 'red' }}>
|
||||
{step.skipped ? '⏭️ 跳过' : step.success ? '✅ 成功' : '❌ 失败'}
|
||||
<span style={{ color: step.success ? 'green' : 'red' }}>
|
||||
{step.success ? '✅ 成功' : '❌ 失败'}
|
||||
</span>
|
||||
{step.deleted !== undefined && (
|
||||
<span style={{ marginLeft: '0.5rem' }}>
|
||||
|
|
|
|||
|
|
@ -297,9 +297,9 @@ export function transformMedusaProductToPayload(product: MedusaProduct) {
|
|||
).filter(price => typeof price === 'number' && price > 0)
|
||||
|
||||
if (allPrices.length > 0) {
|
||||
// 价格以美元存储(项目约定),直接取最小值,保留两位小数
|
||||
const minPrice = Math.min(...allPrices)
|
||||
startPrice = Math.round(minPrice * 100) / 100
|
||||
// 将美分转换为美元(保留两位小数)
|
||||
const minPriceInCents = Math.min(...allPrices)
|
||||
startPrice = Math.round(minPriceInCents) / 100
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue