forked from yoone/API
1462 lines
55 KiB
TypeScript
1462 lines
55 KiB
TypeScript
import { Controller, Get, Inject, Param, Query, Body, Post, Put, Del } from '@midwayjs/core';
|
||
import { ApiOkResponse, ApiBody } from '@midwayjs/swagger';
|
||
import {
|
||
UploadMediaDTO,
|
||
UnifiedMediaPaginationDTO,
|
||
UnifiedOrderDTO,
|
||
UnifiedOrderPaginationDTO,
|
||
UnifiedProductDTO,
|
||
UnifiedProductPaginationDTO,
|
||
UnifiedSearchParamsDTO,
|
||
UnifiedSubscriptionPaginationDTO,
|
||
UnifiedCustomerDTO,
|
||
UnifiedCustomerPaginationDTO,
|
||
UnifiedReviewPaginationDTO,
|
||
UnifiedReviewDTO,
|
||
CreateReviewDTO,
|
||
UpdateReviewDTO,
|
||
UnifiedWebhookDTO,
|
||
CreateWebhookDTO,
|
||
UpdateWebhookDTO,
|
||
UnifiedPaginationDTO,
|
||
ShipOrderDTO,
|
||
CancelShipOrderDTO,
|
||
BatchShipOrdersDTO,
|
||
} from '../dto/site-api.dto';
|
||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||
import { SiteApiService } from '../service/site-api.service';
|
||
import { errorResponse, successResponse } from '../utils/response.util';
|
||
import { ILogger } from '@midwayjs/core';
|
||
|
||
|
||
@Controller('/site-api')
|
||
export class SiteApiController {
|
||
@Inject()
|
||
siteApiService: SiteApiService;
|
||
|
||
@Inject()
|
||
logger: ILogger;
|
||
|
||
|
||
|
||
|
||
|
||
@Post('/:siteId/media/create')
|
||
@ApiOkResponse({ type: UnifiedMediaPaginationDTO })
|
||
@ApiBody({ type: UploadMediaDTO })
|
||
async createMedia(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: UploadMediaDTO,
|
||
) {
|
||
this.logger.debug(`[Site API] 上传媒体文件开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.createMedia(body.file);
|
||
this.logger.debug(`[Site API] 上传媒体文件成功, siteId: ${siteId}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 上传媒体文件失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
|
||
@Get('/:siteId/reviews')
|
||
@ApiOkResponse({ type: UnifiedReviewPaginationDTO })
|
||
async getReviews(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.debug(`[Site API] 获取评论列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getReviews(query);
|
||
this.logger.debug(`[Site API] 获取评论列表成功, siteId: ${siteId}, 共获取到 ${data.total} 条评论`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取评论列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/reviews')
|
||
@ApiOkResponse({ type: UnifiedReviewDTO })
|
||
async createReview(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: CreateReviewDTO
|
||
) {
|
||
this.logger.debug(`[Site API] 创建评论开始, siteId: ${siteId}, body: ${JSON.stringify(body)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.createReview(body);
|
||
this.logger.debug(`[Site API] 创建评论成功, siteId: ${siteId}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 创建评论失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Put('/:siteId/reviews/:id')
|
||
@ApiOkResponse({ type: UnifiedReviewDTO })
|
||
async updateReview(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: number,
|
||
@Body() body: UpdateReviewDTO
|
||
) {
|
||
this.logger.debug(`[Site API] 更新评论开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.updateReview(id, body);
|
||
this.logger.debug(`[Site API] 更新评论成功, siteId: ${siteId}, id: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 更新评论失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Del('/:siteId/reviews/:id')
|
||
@ApiOkResponse({ type: Boolean })
|
||
async deleteReview(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: number
|
||
) {
|
||
this.logger.debug(`[Site API] 删除评论开始, siteId: ${siteId}, id: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.deleteReview(id);
|
||
this.logger.debug(`[Site API] 删除评论成功, siteId: ${siteId}, id: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 删除评论失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/webhooks')
|
||
@ApiOkResponse({ type: UnifiedPaginationDTO<UnifiedWebhookDTO> })
|
||
async getWebhooks(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.debug(`[Site API] 获取webhooks列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getWebhooks(query);
|
||
this.logger.debug(`[Site API] 获取webhooks列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个webhooks`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取webhooks列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/webhooks/:id')
|
||
@ApiOkResponse({ type: UnifiedWebhookDTO })
|
||
async getWebhook(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.debug(`[Site API] 获取单个webhook开始, siteId: ${siteId}, id: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getWebhook(id);
|
||
this.logger.debug(`[Site API] 获取单个webhook成功, siteId: ${siteId}, id: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取单个webhook失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/webhooks')
|
||
@ApiOkResponse({ type: UnifiedWebhookDTO })
|
||
@ApiBody({ type: CreateWebhookDTO })
|
||
async createWebhook(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: CreateWebhookDTO
|
||
) {
|
||
this.logger.debug(`[Site API] 创建webhook开始, siteId: ${siteId}, body: ${JSON.stringify(body)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.createWebhook(body);
|
||
this.logger.debug(`[Site API] 创建webhook成功, siteId: ${siteId}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 创建webhook失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Put('/:siteId/webhooks/:id')
|
||
@ApiOkResponse({ type: UnifiedWebhookDTO })
|
||
@ApiBody({ type: UpdateWebhookDTO })
|
||
async updateWebhook(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: UpdateWebhookDTO
|
||
) {
|
||
this.logger.debug(`[Site API] 更新webhook开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.updateWebhook(id, body);
|
||
this.logger.debug(`[Site API] 更新webhook成功, siteId: ${siteId}, id: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 更新webhook失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Del('/:siteId/webhooks/:id')
|
||
@ApiOkResponse({ type: Boolean })
|
||
async deleteWebhook(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.debug(`[Site API] 删除webhook开始, siteId: ${siteId}, id: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.deleteWebhook(id);
|
||
this.logger.debug(`[Site API] 删除webhook成功, siteId: ${siteId}, id: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 删除webhook失败, siteId: ${siteId}, id: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/products')
|
||
@ApiOkResponse({ type: UnifiedProductPaginationDTO })
|
||
async getProducts(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.debug(`[Site API] 获取产品列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getProducts(query);
|
||
|
||
// 如果包含ERP产品信息,则增强商品数据
|
||
if (data && data.items && data.items.length > 0) {
|
||
const enrichedItems = await this.siteApiService.enrichSiteProductsWithErpInfo(siteId, data.items);
|
||
data.items = enrichedItems;
|
||
}
|
||
|
||
this.logger.debug(`[Site API] 获取产品列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个产品`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取产品列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/products/export')
|
||
async exportProducts(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const perPage = (query.per_page) || 100;
|
||
let page = 1;
|
||
const all: any[] = [];
|
||
while (true) {
|
||
const data = await adapter.getProducts({ ...query, page, per_page: perPage });
|
||
const items = data.items || [];
|
||
all.push(...items);
|
||
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||
if (!items.length || page >= totalPages) break;
|
||
page += 1;
|
||
}
|
||
let items = all;
|
||
if (query.where?.ids) {
|
||
const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||
items = items.filter(i => ids.has(String(i.id)));
|
||
}
|
||
const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'price', 'stock_status', 'stock_quantity', 'image_src'];
|
||
const rows = items.map((p: any) => [
|
||
p.id,
|
||
p.name,
|
||
p.type,
|
||
p.status,
|
||
p.sku,
|
||
p.regular_price,
|
||
p.sale_price,
|
||
p.price,
|
||
p.stock_status,
|
||
p.stock_quantity,
|
||
((p.images && p.images[0]?.src) || ''),
|
||
]);
|
||
const toCsvValue = (val: any) => {
|
||
const s = String(val ?? '');
|
||
const escaped = s.replace(/"/g, '""');
|
||
return `"${escaped}"`;
|
||
};
|
||
const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n');
|
||
return successResponse({ csv });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
// 平台特性:产品导出(特殊CSV,走平台服务)
|
||
@Get('/:siteId/links')
|
||
async getLinks(
|
||
@Param('siteId') siteId: number
|
||
) {
|
||
this.logger.debug(`[Site API] 获取站点链接列表开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getLinks();
|
||
this.logger.debug(`[Site API] 获取站点链接列表成功, siteId: ${siteId}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取站点链接列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/products/export-special')
|
||
async exportProductsSpecial(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
try {
|
||
const site = await this.siteApiService.siteService.get(siteId, true);
|
||
if (site.type === 'woocommerce') {
|
||
const page = query.page || 1;
|
||
const perPage = (query.per_page) || 100;
|
||
const res = await this.siteApiService.wpService.getProducts(site, page, perPage);
|
||
const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'stock_status', 'stock_quantity'];
|
||
const rows = (res.items || []).map((p: any) => [p.id, p.name, p.type, p.status, p.sku, p.regular_price, p.sale_price, p.stock_status, p.stock_quantity]);
|
||
const toCsvValue = (val: any) => {
|
||
const s = String(val ?? '');
|
||
const escaped = s.replace(/"/g, '""');
|
||
return `"${escaped}"`;
|
||
};
|
||
const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n');
|
||
return successResponse({ csv });
|
||
}
|
||
if (site.type === 'shopyy') {
|
||
const res = await this.siteApiService.shopyyService.getProducts(site, query.page || 1, query.per_page || 100);
|
||
const header = ['id', 'name', 'type', 'status', 'sku', 'price', 'stock_status', 'stock_quantity'];
|
||
const rows = (res.items || []).map((p: any) => [p.id, p.name, p.type, p.status, p.sku, p.price, p.stock_status, p.stock_quantity]);
|
||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||
return successResponse({ csv });
|
||
}
|
||
throw new Error('Unsupported site type for special export');
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/products/:id')
|
||
@ApiOkResponse({ type: UnifiedProductDTO })
|
||
async getProduct(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 获取单个产品开始, siteId: ${siteId}, productId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getProduct(id);
|
||
|
||
// 如果获取到商品数据,则增强ERP产品信息
|
||
if (data) {
|
||
const enrichedData = await this.siteApiService.enrichSiteProductWithErpInfo(siteId, data);
|
||
this.logger.info(`[Site API] 获取单个产品成功, siteId: ${siteId}, productId: ${id}`);
|
||
return successResponse(enrichedData);
|
||
}
|
||
|
||
this.logger.info(`[Site API] 获取单个产品成功, siteId: ${siteId}, productId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取单个产品失败, siteId: ${siteId}, productId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/products')
|
||
@ApiOkResponse({ type: UnifiedProductDTO })
|
||
async createProduct(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: UnifiedProductDTO
|
||
) {
|
||
this.logger.info(`[Site API] 创建产品开始, siteId: ${siteId}, 产品名称: ${body.name}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.createProduct(body);
|
||
this.logger.info(`[Site API] 创建产品成功, siteId: ${siteId}, 产品ID: ${data.id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 创建产品失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/products/import')
|
||
@ApiOkResponse({ type: Object })
|
||
async importProducts(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: { items?: any[]; csv?: string }
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
let items = body.items || [];
|
||
if (!items.length && body.csv) {
|
||
const lines = body.csv.split(/\r?\n/).filter(Boolean);
|
||
const header = lines.shift()?.split(',') || [];
|
||
items = lines.map((line) => {
|
||
const cols = line.split(',');
|
||
const obj: any = {};
|
||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||
return obj;
|
||
});
|
||
}
|
||
const created: any[] = [];
|
||
const failed: any[] = [];
|
||
for (const item of items) {
|
||
try {
|
||
const data = await adapter.createProduct(item);
|
||
created.push(data);
|
||
} catch (e) {
|
||
failed.push({ item, error: (e as any).message });
|
||
}
|
||
}
|
||
return successResponse({ created, failed });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
// 平台特性:产品导入(特殊CSV,走平台服务)
|
||
@Post('/:siteId/products/import-special')
|
||
@ApiOkResponse({ type: Object })
|
||
async importProductsSpecial(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: { csv?: string; items?: any[] }
|
||
) {
|
||
try {
|
||
const site = await this.siteApiService.siteService.get(siteId, true);
|
||
const csvText = body.csv || '';
|
||
const items = body.items || [];
|
||
const created: any[] = [];
|
||
const failed: any[] = [];
|
||
if (site.type === 'woocommerce') {
|
||
// 解析 CSV 为对象数组(若传入 items 则优先 items)
|
||
let payloads = items;
|
||
if (!payloads.length && csvText) {
|
||
const lines = csvText.split(/\r?\n/).filter(Boolean);
|
||
const header = lines.shift()?.split(',') || [];
|
||
payloads = lines.map((line) => {
|
||
const cols = line.split(',');
|
||
const obj: any = {};
|
||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||
return obj;
|
||
});
|
||
}
|
||
for (const item of payloads) {
|
||
try {
|
||
const res = await this.siteApiService.wpService.createProduct(site, item);
|
||
created.push(res);
|
||
} catch (e) {
|
||
failed.push({ item, error: (e as any).message });
|
||
}
|
||
}
|
||
return successResponse({ created, failed });
|
||
}
|
||
if (site.type === 'shopyy') {
|
||
throw new Error('ShopYY 暂不支持特殊CSV导入');
|
||
}
|
||
throw new Error('Unsupported site type for special import');
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Put('/:siteId/products/:id')
|
||
@ApiOkResponse({ type: UnifiedProductDTO })
|
||
async updateProduct(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: UnifiedProductDTO
|
||
) {
|
||
this.logger.info(`[Site API] 更新产品开始, siteId: ${siteId}, productId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.updateProduct(id, body);
|
||
this.logger.info(`[Site API] 更新产品成功, siteId: ${siteId}, productId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 更新产品失败, siteId: ${siteId}, productId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Put('/:siteId/products/:productId/variations/:variationId')
|
||
@ApiOkResponse({ type: Object })
|
||
async updateVariation(
|
||
@Param('siteId') siteId: number,
|
||
@Param('productId') productId: string,
|
||
@Param('variationId') variationId: string,
|
||
@Body() body: any
|
||
) {
|
||
this.logger.info(`[Site API] 更新产品变体开始, siteId: ${siteId}, productId: ${productId}, variationId: ${variationId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.updateVariation(productId, variationId, body);
|
||
this.logger.info(`[Site API] 更新产品变体成功, siteId: ${siteId}, productId: ${productId}, variationId: ${variationId}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 更新产品变体失败, siteId: ${siteId}, productId: ${productId}, variationId: ${variationId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Del('/:siteId/products/:id')
|
||
@ApiOkResponse({ type: Boolean })
|
||
async deleteProduct(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 删除产品开始, siteId: ${siteId}, productId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const success = await adapter.deleteProduct(id);
|
||
this.logger.info(`[Site API] 删除产品成功, siteId: ${siteId}, productId: ${id}`);
|
||
return successResponse(success);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 删除产品失败, siteId: ${siteId}, productId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/products/batch')
|
||
@ApiOkResponse({ type: BatchOperationResultDTO })
|
||
async batchProducts(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: BatchOperationDTO
|
||
) {
|
||
this.logger.info(`[Site API] 批量处理产品开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
if (adapter.batchProcessProducts) {
|
||
const res = await adapter.batchProcessProducts(body);
|
||
this.logger.info(`[Site API] 批量处理产品成功, siteId: ${siteId}`);
|
||
return successResponse(res);
|
||
}
|
||
const created: any[] = [];
|
||
const updated: any[] = [];
|
||
const deleted: Array<string | number> = [];
|
||
const errors: Array<{identifier: string, error: string}> = [];
|
||
|
||
if (body.create?.length) {
|
||
for (const item of body.create) {
|
||
try {
|
||
const data = await adapter.createProduct(item);
|
||
created.push(data);
|
||
} catch (e) {
|
||
errors.push({
|
||
identifier: String(item.id || item.sku || 'unknown'),
|
||
error: (e as any).message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
if (body.update?.length) {
|
||
for (const item of body.update) {
|
||
try {
|
||
const id = item.id;
|
||
const data = await adapter.updateProduct(id, item);
|
||
updated.push(data);
|
||
} catch (e) {
|
||
errors.push({
|
||
identifier: String(item.id || 'unknown'),
|
||
error: (e as any).message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
if (body.delete?.length) {
|
||
for (const id of body.delete) {
|
||
try {
|
||
const ok = await adapter.deleteProduct(id);
|
||
if (ok) deleted.push(id);
|
||
else errors.push({
|
||
identifier: String(id),
|
||
error: 'delete failed'
|
||
});
|
||
} catch (e) {
|
||
errors.push({
|
||
identifier: String(id),
|
||
error: (e as any).message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
this.logger.info(`[Site API] 批量处理产品完成, siteId: ${siteId}`);
|
||
return successResponse({
|
||
total: (body.create?.length || 0) + (body.update?.length || 0) + (body.delete?.length || 0),
|
||
processed: created.length + updated.length + deleted.length,
|
||
created: created.length,
|
||
updated: updated.length,
|
||
deleted: deleted.length,
|
||
errors: errors
|
||
});
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 批量处理产品失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/orders')
|
||
@ApiOkResponse({ type: UnifiedOrderPaginationDTO })
|
||
async getOrders(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.info(`[Site API] 获取订单列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const where = { ...(query.where || {}) };
|
||
const data = await adapter.getOrders({ ...query, where });
|
||
this.logger.info(`[Site API] 获取订单列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订单`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取订单列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/customers/:customerId/orders')
|
||
@ApiOkResponse({ type: UnifiedOrderPaginationDTO })
|
||
async getCustomerOrders(
|
||
@Param('siteId') siteId: number,
|
||
@Param('customerId') customerId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.info(`[Site API] 获取客户订单列表开始, siteId: ${siteId}, customerId: ${customerId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const where = { ...(query.where || {}), customer: customerId };
|
||
const data = await adapter.getOrders({ ...query, where });
|
||
this.logger.info(`[Site API] 获取客户订单列表成功, siteId: ${siteId}, customerId: ${customerId}, 共获取到 ${data.total} 个订单`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取客户订单列表失败, siteId: ${siteId}, customerId: ${customerId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/orders/export')
|
||
async exportOrders(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const perPage = (query.per_page) || 100;
|
||
let page = 1;
|
||
const all: any[] = [];
|
||
while (true) {
|
||
const data = await adapter.getOrders({ ...query, page, per_page: perPage });
|
||
const items = data.items || [];
|
||
all.push(...items);
|
||
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||
if (!items.length || page >= totalPages) break;
|
||
page += 1;
|
||
}
|
||
let items = all;
|
||
if (query.where?.ids) {
|
||
const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||
items = items.filter(i => ids.has(String(i.id)));
|
||
}
|
||
const header = ['id', 'number', 'status', 'currency', 'total', 'customer_id', 'customer_name', 'email', 'payment_method', 'phone', 'billing_full_address', 'shipping_full_address', 'date_created'];
|
||
const rows = items.map((o: any) => [
|
||
o.id,
|
||
o.number,
|
||
o.status,
|
||
o.currency,
|
||
o.total,
|
||
o.customer_id,
|
||
o.customer_name,
|
||
o.email,
|
||
o.payment_method,
|
||
(o.shipping?.phone || o.billing?.phone || ''),
|
||
(o.billing_full_address || ''),
|
||
(o.shipping_full_address || ''),
|
||
o.date_created,
|
||
]);
|
||
const toCsvValue = (val: any) => {
|
||
const s = String(val ?? '');
|
||
const escaped = s.replace(/"/g, '""');
|
||
return `"${escaped}"`;
|
||
};
|
||
const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n');
|
||
return successResponse({ csv });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/orders/:id')
|
||
@ApiOkResponse({ type: UnifiedOrderDTO })
|
||
async getOrder(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 获取单个订单开始, siteId: ${siteId}, orderId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getOrder(id);
|
||
this.logger.info(`[Site API] 获取单个订单成功, siteId: ${siteId}, orderId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取单个订单失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/orders')
|
||
@ApiOkResponse({ type: UnifiedOrderDTO })
|
||
async createOrder(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: any
|
||
) {
|
||
this.logger.info(`[Site API] 创建订单开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.createOrder(body);
|
||
this.logger.info(`[Site API] 创建订单成功, siteId: ${siteId}, orderId: ${data.id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 创建订单失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/orders/import')
|
||
@ApiOkResponse({ type: Object })
|
||
async importOrders(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: { items?: any[]; csv?: string }
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
let items = body.items || [];
|
||
if (!items.length && body.csv) {
|
||
const lines = body.csv.split(/\r?\n/).filter(Boolean);
|
||
const header = lines.shift()?.split(',') || [];
|
||
items = lines.map((line) => {
|
||
const cols = line.split(',');
|
||
const obj: any = {};
|
||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||
return obj;
|
||
});
|
||
}
|
||
const created: any[] = [];
|
||
const failed: any[] = [];
|
||
for (const item of items) {
|
||
try {
|
||
const data = await adapter.createOrder(item);
|
||
created.push(data);
|
||
} catch (e) {
|
||
failed.push({ item, error: (e as any).message });
|
||
}
|
||
}
|
||
return successResponse({ created, failed });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Put('/:siteId/orders/:id')
|
||
@ApiOkResponse({ type: Boolean })
|
||
async updateOrder(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: any
|
||
) {
|
||
this.logger.info(`[Site API] 更新订单开始, siteId: ${siteId}, orderId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const ok = await adapter.updateOrder(id, body);
|
||
this.logger.info(`[Site API] 更新订单成功, siteId: ${siteId}, orderId: ${id}`);
|
||
return successResponse(ok);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 更新订单失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Del('/:siteId/orders/:id')
|
||
@ApiOkResponse({ type: Boolean })
|
||
async deleteOrder(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 删除订单开始, siteId: ${siteId}, orderId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const ok = await adapter.deleteOrder(id);
|
||
this.logger.info(`[Site API] 删除订单成功, siteId: ${siteId}, orderId: ${id}`);
|
||
return successResponse(ok);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 删除订单失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/orders/batch')
|
||
@ApiOkResponse({ type: BatchOperationResultDTO })
|
||
async batchOrders(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: BatchOperationDTO
|
||
) {
|
||
this.logger.info(`[Site API] 批量处理订单开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const created: any[] = [];
|
||
const updated: any[] = [];
|
||
const deleted: Array<string | number> = [];
|
||
const errors: Array<{identifier: string, error: string}> = [];
|
||
|
||
if (body.create?.length) {
|
||
for (const item of body.create) {
|
||
try {
|
||
const data = await adapter.createOrder(item);
|
||
created.push(data);
|
||
} catch (e) {
|
||
errors.push({
|
||
identifier: String(item.id || item.order_number || 'unknown'),
|
||
error: (e as any).message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
if (body.update?.length) {
|
||
for (const item of body.update) {
|
||
try {
|
||
const id = item.id;
|
||
const ok = await adapter.updateOrder(id, item);
|
||
if (ok) updated.push(item);
|
||
else errors.push({
|
||
identifier: String(item.id || 'unknown'),
|
||
error: 'update failed'
|
||
});
|
||
} catch (e) {
|
||
errors.push({
|
||
identifier: String(item.id || 'unknown'),
|
||
error: (e as any).message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
if (body.delete?.length) {
|
||
for (const id of body.delete) {
|
||
try {
|
||
const ok = await adapter.deleteOrder(id);
|
||
if (ok) deleted.push(id);
|
||
else errors.push({
|
||
identifier: String(id),
|
||
error: 'delete failed'
|
||
});
|
||
} catch (e) {
|
||
errors.push({
|
||
identifier: String(id),
|
||
error: (e as any).message
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
this.logger.info(`[Site API] 批量处理订单完成, siteId: ${siteId}`);
|
||
return successResponse({
|
||
total: (body.create?.length || 0) + (body.update?.length || 0) + (body.delete?.length || 0),
|
||
processed: created.length + updated.length + deleted.length,
|
||
created: created.length,
|
||
updated: updated.length,
|
||
deleted: deleted.length,
|
||
errors: errors
|
||
});
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 批量处理订单失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/orders/:id/notes')
|
||
@ApiOkResponse({ type: Object })
|
||
async getOrderNotes(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 获取订单备注开始, siteId: ${siteId}, orderId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getOrderNotes(id);
|
||
this.logger.info(`[Site API] 获取订单备注成功, siteId: ${siteId}, orderId: ${id}, 共获取到 ${data.length} 条备注`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取订单备注失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/orders/:id/notes')
|
||
@ApiOkResponse({ type: Object })
|
||
async createOrderNote(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: any
|
||
) {
|
||
this.logger.info(`[Site API] 创建订单备注开始, siteId: ${siteId}, orderId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.createOrderNote(id, body);
|
||
this.logger.info(`[Site API] 创建订单备注成功, siteId: ${siteId}, orderId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 创建订单备注失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/orders/:id/ship')
|
||
@ApiOkResponse({ type: Object })
|
||
async shipOrder(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: ShipOrderDTO
|
||
) {
|
||
this.logger.info(`[Site API] 订单发货开始, siteId: ${siteId}, orderId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.shipOrder(id, body);
|
||
this.logger.info(`[Site API] 订单发货成功, siteId: ${siteId}, orderId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 订单发货失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/orders/:id/cancel-ship')
|
||
@ApiOkResponse({ type: Object })
|
||
async cancelShipOrder(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: CancelShipOrderDTO
|
||
) {
|
||
this.logger.info(`[Site API] 取消订单发货开始, siteId: ${siteId}, orderId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.cancelShipOrder(id, body);
|
||
this.logger.info(`[Site API] 取消订单发货成功, siteId: ${siteId}, orderId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 取消订单发货失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/orders/batch-ship')
|
||
@ApiOkResponse({ type: Object })
|
||
async batchShipOrders(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: BatchShipOrdersDTO
|
||
) {
|
||
this.logger.info(`[Site API] 批量订单发货开始, siteId: ${siteId}, 订单数量: ${body.orders.length}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const results = await Promise.allSettled(
|
||
body.orders.map(order =>
|
||
adapter.shipOrder(order.order_id, {
|
||
tracking_number: order.tracking_number,
|
||
shipping_provider: order.shipping_provider,
|
||
shipping_method: order.shipping_method,
|
||
items: order.items,
|
||
}).catch(error => ({
|
||
order_id: order.order_id,
|
||
success: false,
|
||
error: error.message
|
||
}))
|
||
)
|
||
);
|
||
|
||
const successful = results
|
||
.filter(result => result.status === 'fulfilled')
|
||
.map(result => (result as PromiseFulfilledResult<any>).value);
|
||
|
||
const failed = results
|
||
.filter(result => result.status === 'rejected')
|
||
.map(result => (result as PromiseRejectedResult).reason);
|
||
|
||
this.logger.info(`[Site API] 批量订单发货完成, siteId: ${siteId}, 成功: ${successful.length}, 失败: ${failed.length}`);
|
||
return successResponse({
|
||
successful: successful.length,
|
||
failed: failed.length,
|
||
results: {
|
||
successful,
|
||
failed
|
||
}
|
||
});
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 批量订单发货失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/subscriptions')
|
||
@ApiOkResponse({ type: UnifiedSubscriptionPaginationDTO })
|
||
async getSubscriptions(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.info(`[Site API] 获取订阅列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getSubscriptions(query);
|
||
this.logger.info(`[Site API] 获取订阅列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订阅`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取订阅列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/subscriptions/export')
|
||
async exportSubscriptions(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const perPage = (query.per_page) || 100;
|
||
let page = 1;
|
||
const all: any[] = [];
|
||
while (true) {
|
||
const data = await adapter.getSubscriptions({ ...query, page, per_page: perPage });
|
||
const items = data.items || [];
|
||
all.push(...items);
|
||
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||
if (!items.length || page >= totalPages) break;
|
||
page += 1;
|
||
}
|
||
let items = all;
|
||
if (query.where?.ids) {
|
||
const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||
items = items.filter(i => ids.has(String(i.id)));
|
||
}
|
||
const header = ['id', 'status', 'customer_id', 'billing_period', 'billing_interval', 'start_date', 'next_payment_date'];
|
||
const rows = items.map((s: any) => [s.id, s.status, s.customer_id, s.billing_period, s.billing_interval, s.start_date, s.next_payment_date]);
|
||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||
return successResponse({ csv });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/media')
|
||
@ApiOkResponse({ type: UnifiedMediaPaginationDTO })
|
||
async getMedia(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.info(`[Site API] 获取媒体列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getMedia(query);
|
||
this.logger.info(`[Site API] 获取媒体列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个媒体`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取媒体列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/media/export')
|
||
async exportMedia(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const perPage = (query.per_page) || 100;
|
||
let page = 1;
|
||
const all: any[] = [];
|
||
while (true) {
|
||
const data = await adapter.getMedia({ ...query, page, per_page: perPage });
|
||
const items = data.items || [];
|
||
all.push(...items);
|
||
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||
if (!items.length || page >= totalPages) break;
|
||
page += 1;
|
||
}
|
||
let items = all;
|
||
if (query.where?.ids) {
|
||
const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||
items = items.filter(i => ids.has(String(i.id)));
|
||
}
|
||
const header = ['id', 'title', 'media_type', 'mime_type', 'source_url', 'date_created'];
|
||
const rows = items.map((m: any) => [m.id, m.title, m.media_type, m.mime_type, m.source_url, m.date_created]);
|
||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||
return successResponse({ csv });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Del('/:siteId/media/:id')
|
||
@ApiOkResponse({ type: Boolean })
|
||
async deleteMedia(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 删除媒体开始, siteId: ${siteId}, mediaId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const api: any = adapter as any;
|
||
if (api.deleteMedia) {
|
||
const success = await api.deleteMedia(id);
|
||
this.logger.info(`[Site API] 删除媒体成功, siteId: ${siteId}, mediaId: ${id}`);
|
||
return successResponse(success);
|
||
}
|
||
throw new Error('Media delete not supported');
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 删除媒体失败, siteId: ${siteId}, mediaId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Put('/:siteId/media/:id')
|
||
@ApiOkResponse({ type: Object })
|
||
async updateMedia(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: any
|
||
) {
|
||
this.logger.info(`[Site API] 更新媒体开始, siteId: ${siteId}, mediaId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const api: any = adapter as any;
|
||
if (api.updateMedia) {
|
||
const res = await api.updateMedia(id, body);
|
||
this.logger.info(`[Site API] 更新媒体成功, siteId: ${siteId}, mediaId: ${id}`);
|
||
return successResponse(res);
|
||
}
|
||
throw new Error('Media update not supported');
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 更新媒体失败, siteId: ${siteId}, mediaId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/media/batch')
|
||
@ApiOkResponse({ type: Object })
|
||
async batchMedia(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: { update?: any[]; delete?: Array<string | number> }
|
||
) {
|
||
this.logger.info(`[Site API] 批量处理媒体开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const updated: any[] = [];
|
||
const deleted: Array<string | number> = [];
|
||
const failed: any[] = [];
|
||
const api: any = adapter as any;
|
||
if (body.update?.length) {
|
||
for (const item of body.update) {
|
||
try {
|
||
if (!api.updateMedia) throw new Error('Media update not supported');
|
||
const res = await api.updateMedia(item.id, item);
|
||
updated.push(res);
|
||
} catch (e) {
|
||
failed.push({ action: 'update', item, error: (e as any).message });
|
||
}
|
||
}
|
||
}
|
||
if (body.delete?.length) {
|
||
for (const id of body.delete) {
|
||
try {
|
||
if (!api.deleteMedia) throw new Error('Media delete not supported');
|
||
const ok = await api.deleteMedia(id);
|
||
if (ok) deleted.push(id);
|
||
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
||
} catch (e) {
|
||
failed.push({ action: 'delete', id, error: (e as any).message });
|
||
}
|
||
}
|
||
}
|
||
this.logger.info(`[Site API] 批量处理媒体完成, siteId: ${siteId}`);
|
||
return successResponse({ updated, deleted, failed });
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 批量处理媒体失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/media/convert-webp')
|
||
@ApiOkResponse({ type: Object })
|
||
async convertMediaToWebp(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: { ids: Array<string | number> }
|
||
) {
|
||
this.logger.info(`[Site API] 批量转换媒体为 webp 开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const api: any = adapter as any;
|
||
// 条件判断 如果未提供 ids 列表则抛出错误
|
||
if (!body?.ids || body.ids.length === 0) {
|
||
throw new Error('未提供需要转换的媒体ID列表');
|
||
}
|
||
if (!api.convertMediaToWebp) {
|
||
throw new Error('当前站点不支持媒体转换');
|
||
}
|
||
const res = await api.convertMediaToWebp(body.ids);
|
||
this.logger.info(`[Site API] 批量转换媒体为 webp 成功, siteId: ${siteId}`);
|
||
return successResponse(res);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 批量转换媒体为 webp 失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/customers')
|
||
@ApiOkResponse({ type: UnifiedCustomerPaginationDTO })
|
||
async getCustomers(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
this.logger.info(`[Site API] 获取客户列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getCustomers(query);
|
||
this.logger.info(`[Site API] 获取客户列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个客户`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取客户列表失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/customers/export')
|
||
async exportCustomers(
|
||
@Param('siteId') siteId: number,
|
||
@Query() query: UnifiedSearchParamsDTO
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const perPage = (query.per_page) || 100;
|
||
let page = 1;
|
||
const all: any[] = [];
|
||
while (true) {
|
||
const data = await adapter.getCustomers({ ...query, page, per_page: perPage});
|
||
const items = data.items || [];
|
||
all.push(...items);
|
||
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||
if (!items.length || page >= totalPages) break;
|
||
page += 1;
|
||
}
|
||
let items = all;
|
||
const header = ['id', 'email', 'first_name', 'last_name', 'fullname', 'username', 'phone', 'orders', 'total_spend', 'role', 'billing_full_address', 'shipping_full_address', 'date_created'];
|
||
const formatAddress = (addr: any) => [
|
||
addr?.fullname,
|
||
addr?.company,
|
||
addr?.address_1,
|
||
addr?.address_2,
|
||
addr?.city,
|
||
addr?.state,
|
||
addr?.postcode,
|
||
addr?.country,
|
||
addr?.phone,
|
||
].filter(Boolean).join(', ');
|
||
const rows = items.map((c: any) => [
|
||
c.id,
|
||
c.email,
|
||
c.first_name,
|
||
c.last_name,
|
||
c.fullname,
|
||
(c.username || c.raw?.username || ''),
|
||
(c.phone || c.billing?.phone || c.shipping?.phone || ''),
|
||
c.orders,
|
||
c.total_spend,
|
||
(c.role || c.raw?.role || ''),
|
||
formatAddress(c.billing || {}),
|
||
formatAddress(c.shipping || {}),
|
||
c.date_created,
|
||
]);
|
||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||
return successResponse({ csv });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Get('/:siteId/customers/:id')
|
||
@ApiOkResponse({ type: UnifiedCustomerDTO })
|
||
async getCustomer(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 获取单个客户开始, siteId: ${siteId}, customerId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.getCustomer(id);
|
||
this.logger.info(`[Site API] 获取单个客户成功, siteId: ${siteId}, customerId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 获取单个客户失败, siteId: ${siteId}, customerId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/customers')
|
||
@ApiOkResponse({ type: UnifiedCustomerDTO })
|
||
async createCustomer(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: UnifiedCustomerDTO
|
||
) {
|
||
this.logger.info(`[Site API] 创建客户开始, siteId: ${siteId}, 客户邮箱: ${body.email}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.createCustomer(body);
|
||
this.logger.info(`[Site API] 创建客户成功, siteId: ${siteId}, customerId: ${data.id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 创建客户失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/customers/import')
|
||
@ApiOkResponse({ type: Object })
|
||
async importCustomers(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: { items?: any[]; csv?: string }
|
||
) {
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
let items = body.items || [];
|
||
if (!items.length && body.csv) {
|
||
const lines = body.csv.split(/\r?\n/).filter(Boolean);
|
||
const header = lines.shift()?.split(',') || [];
|
||
items = lines.map((line) => {
|
||
const cols = line.split(',');
|
||
const obj: any = {};
|
||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||
return obj;
|
||
});
|
||
}
|
||
const created: any[] = [];
|
||
const failed: any[] = [];
|
||
for (const item of items) {
|
||
try {
|
||
const data = await adapter.createCustomer(item);
|
||
created.push(data);
|
||
} catch (e) {
|
||
failed.push({ item, error: (e as any).message });
|
||
}
|
||
}
|
||
return successResponse({ created, failed });
|
||
} catch (error) {
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Put('/:siteId/customers/:id')
|
||
@ApiOkResponse({ type: UnifiedCustomerDTO })
|
||
async updateCustomer(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string,
|
||
@Body() body: UnifiedCustomerDTO
|
||
) {
|
||
this.logger.info(`[Site API] 更新客户开始, siteId: ${siteId}, customerId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const data = await adapter.updateCustomer(id, body);
|
||
this.logger.info(`[Site API] 更新客户成功, siteId: ${siteId}, customerId: ${id}`);
|
||
return successResponse(data);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 更新客户失败, siteId: ${siteId}, customerId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Del('/:siteId/customers/:id')
|
||
@ApiOkResponse({ type: Boolean })
|
||
async deleteCustomer(
|
||
@Param('siteId') siteId: number,
|
||
@Param('id') id: string
|
||
) {
|
||
this.logger.info(`[Site API] 删除客户开始, siteId: ${siteId}, customerId: ${id}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const success = await adapter.deleteCustomer(id);
|
||
this.logger.info(`[Site API] 删除客户成功, siteId: ${siteId}, customerId: ${id}`);
|
||
return successResponse(success);
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 删除客户失败, siteId: ${siteId}, customerId: ${id}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
@Post('/:siteId/customers/batch')
|
||
@ApiOkResponse({ type: Object })
|
||
async batchCustomers(
|
||
@Param('siteId') siteId: number,
|
||
@Body() body: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||
) {
|
||
this.logger.info(`[Site API] 批量处理客户开始, siteId: ${siteId}`);
|
||
try {
|
||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||
const created: any[] = [];
|
||
const updated: any[] = [];
|
||
const deleted: Array<string | number> = [];
|
||
const failed: any[] = [];
|
||
if (body.create?.length) {
|
||
for (const item of body.create) {
|
||
try {
|
||
const data = await adapter.createCustomer(item);
|
||
created.push(data);
|
||
} catch (e) {
|
||
failed.push({ action: 'create', item, error: (e as any).message });
|
||
}
|
||
}
|
||
}
|
||
if (body.update?.length) {
|
||
for (const item of body.update) {
|
||
try {
|
||
const id = item.id;
|
||
const data = await adapter.updateCustomer(id, item);
|
||
updated.push(data);
|
||
} catch (e) {
|
||
failed.push({ action: 'update', item, error: (e as any).message });
|
||
}
|
||
}
|
||
}
|
||
if (body.delete?.length) {
|
||
for (const id of body.delete) {
|
||
try {
|
||
const ok = await adapter.deleteCustomer(id);
|
||
if (ok) deleted.push(id);
|
||
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
||
} catch (e) {
|
||
failed.push({ action: 'delete', id, error: (e as any).message });
|
||
}
|
||
}
|
||
}
|
||
this.logger.info(`[Site API] 批量处理客户完成, siteId: ${siteId}`);
|
||
return successResponse({ created, updated, deleted, failed });
|
||
} catch (error) {
|
||
this.logger.error(`[Site API] 批量处理客户失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||
return errorResponse(error.message);
|
||
}
|
||
}
|
||
|
||
|
||
}
|