批量同步
This commit is contained in:
parent
1860affd69
commit
4def0e2c0b
|
|
@ -0,0 +1,123 @@
|
|||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getAllMedusaProducts } from '@/lib/medusa'
|
||||
|
||||
/**
|
||||
* Batch Sync Selected Products
|
||||
* POST /api/admin/batch-sync-medusa
|
||||
* Body: { ids: string[], collection: 'products' | 'preorder-products', forceUpdate?: boolean }
|
||||
*/
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { ids, collection, forceUpdate = false } = body
|
||||
|
||||
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'No product IDs provided' },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
if (!collection || !['products', 'preorder-products'].includes(collection)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Invalid collection' },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
// Get all Medusa products once
|
||||
const medusaProducts = await getAllMedusaProducts()
|
||||
const medusaProductMap = new Map(medusaProducts.map(p => [p.id, p]))
|
||||
|
||||
const results = {
|
||||
total: ids.length,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
details: [] as any[],
|
||||
}
|
||||
|
||||
// Sync each selected product
|
||||
for (const id of ids) {
|
||||
try {
|
||||
const product = await payload.findByID({
|
||||
collection: collection as 'products' | 'preorder-products',
|
||||
id,
|
||||
})
|
||||
|
||||
if (!product || !product.medusaId) {
|
||||
results.skipped++
|
||||
results.details.push({
|
||||
id,
|
||||
title: product?.title || 'Unknown',
|
||||
status: 'skipped',
|
||||
reason: 'No Medusa ID',
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const medusaProduct = medusaProductMap.get(product.medusaId)
|
||||
|
||||
if (!medusaProduct) {
|
||||
results.failed++
|
||||
results.details.push({
|
||||
id,
|
||||
medusaId: product.medusaId,
|
||||
title: product.title,
|
||||
status: 'failed',
|
||||
error: 'Product not found in Medusa',
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Update basic fields from Medusa
|
||||
const updateData: any = {
|
||||
lastSyncedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
if (forceUpdate || !product.title) updateData.title = medusaProduct.title
|
||||
if (forceUpdate || !product.thumbnail) updateData.thumbnail = medusaProduct.thumbnail
|
||||
|
||||
await payload.update({
|
||||
collection: collection as 'products' | 'preorder-products',
|
||||
id,
|
||||
data: updateData,
|
||||
})
|
||||
|
||||
results.success++
|
||||
results.details.push({
|
||||
id,
|
||||
medusaId: product.medusaId,
|
||||
title: product.title,
|
||||
status: 'success',
|
||||
})
|
||||
} catch (error) {
|
||||
results.failed++
|
||||
results.details.push({
|
||||
id,
|
||||
status: 'failed',
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Batch sync completed: ${results.success} success, ${results.failed} failed, ${results.skipped} skipped`,
|
||||
results,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[batch-sync-medusa] Error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,35 +22,38 @@ export function RestoreRecommendationsSeedButton({ className }: Props) {
|
|||
*/
|
||||
const findProductsBySeedIds = async (
|
||||
seedIds: string[],
|
||||
isPreorder: boolean = false,
|
||||
): Promise<Array<{ relationTo: string; value: string }>> => {
|
||||
const products: Array<{ relationTo: string; value: string }> = []
|
||||
const primaryCollection = isPreorder ? 'preorder-products' : 'products'
|
||||
const fallbackCollection = isPreorder ? 'products' : 'preorder-products'
|
||||
|
||||
for (const seedId of seedIds) {
|
||||
try {
|
||||
// Try products collection first
|
||||
const productsResponse = await fetch(
|
||||
`/api/products?where[seedId][equals]=${seedId}&limit=1`,
|
||||
// Try primary collection first based on preorder flag
|
||||
const primaryResponse = await fetch(
|
||||
`/api/${primaryCollection}?where[seedId][equals]=${seedId}&limit=1`,
|
||||
)
|
||||
const productsData = await productsResponse.json()
|
||||
const primaryData = await primaryResponse.json()
|
||||
|
||||
if (productsData.docs && productsData.docs.length > 0) {
|
||||
if (primaryData.docs && primaryData.docs.length > 0) {
|
||||
products.push({
|
||||
relationTo: 'products',
|
||||
value: productsData.docs[0].id,
|
||||
relationTo: primaryCollection,
|
||||
value: primaryData.docs[0].id,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Try preorder-products if not found
|
||||
const preorderResponse = await fetch(
|
||||
`/api/preorder-products?where[seedId][equals]=${seedId}&limit=1`,
|
||||
// Try fallback collection if not found
|
||||
const fallbackResponse = await fetch(
|
||||
`/api/${fallbackCollection}?where[seedId][equals]=${seedId}&limit=1`,
|
||||
)
|
||||
const preorderData = await preorderResponse.json()
|
||||
const fallbackData = await fallbackResponse.json()
|
||||
|
||||
if (preorderData.docs && preorderData.docs.length > 0) {
|
||||
if (fallbackData.docs && fallbackData.docs.length > 0) {
|
||||
products.push({
|
||||
relationTo: 'preorder-products',
|
||||
value: preorderData.docs[0].id,
|
||||
relationTo: fallbackCollection,
|
||||
value: fallbackData.docs[0].id,
|
||||
})
|
||||
} else {
|
||||
console.warn(`Product not found for seedId: ${seedId}`)
|
||||
|
|
@ -84,10 +87,11 @@ export function RestoreRecommendationsSeedButton({ className }: Props) {
|
|||
// Find all product IDs in polymorphic relationship format
|
||||
const listsWithProductIds = await Promise.all(
|
||||
seed.lists.map(async (list) => {
|
||||
const products = await findProductsBySeedIds(list.productSeedIds)
|
||||
const products = await findProductsBySeedIds(list.productSeedIds, list.preorder || false)
|
||||
return {
|
||||
title: list.title,
|
||||
subtitle: list.subtitle || '',
|
||||
preorder: list.preorder || false,
|
||||
products: products,
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
export interface RecommendationListSeed {
|
||||
title: string
|
||||
subtitle?: string
|
||||
preorder?: boolean
|
||||
productSeedIds: string[]
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ export const BATCH_02_RECOMMENDATIONS: RecommendationsSeed = {
|
|||
{
|
||||
title: 'PreGame - Preorder Games',
|
||||
subtitle: 'Selected indie games, now available for preorder! Support indie developers and get early access to new releases.',
|
||||
preorder: true,
|
||||
productSeedIds: [
|
||||
'game-urcicus', // Urcicus - GBA Game
|
||||
'game-mikoto-nikki', // Mikoto Nikki - GBA Game
|
||||
|
|
@ -36,6 +38,7 @@ export const BATCH_02_RECOMMENDATIONS: RecommendationsSeed = {
|
|||
{
|
||||
title: 'PreMod - Preorder Modifications',
|
||||
subtitle: 'Premium custom shells and consoles, full metal construction, artisan craftsmanship. Limited preorder available!',
|
||||
preorder: true,
|
||||
productSeedIds: [
|
||||
'shell-gba-sp-metal-unhinged', // Metal Shell - GBA SP (Unhinged Mod)
|
||||
'shell-gba-metal', // Metal Shell - GBA
|
||||
|
|
|
|||
|
|
@ -103,6 +103,21 @@ export const ProductRecommendations: GlobalConfig = {
|
|||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'preorder',
|
||||
type: 'checkbox',
|
||||
label: {
|
||||
en: 'Preorder Products',
|
||||
zh: '预购商品',
|
||||
},
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: {
|
||||
en: 'Check if this list contains preorder products',
|
||||
zh: '勾选表示此列表包含预购商品',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'products',
|
||||
type: 'relationship',
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,10 @@ export interface ProductRecommendation {
|
|||
| {
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
/**
|
||||
* Check if this list contains preorder products
|
||||
*/
|
||||
preorder?: boolean | null;
|
||||
/**
|
||||
* Select and drag to reorder products
|
||||
*/
|
||||
|
|
@ -1312,6 +1316,7 @@ export interface ProductRecommendationsSelect<T extends boolean = true> {
|
|||
| {
|
||||
title?: T;
|
||||
subtitle?: T;
|
||||
preorder?: T;
|
||||
products?: T;
|
||||
id?: T;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue