feat: 添加MSW模拟服务和数据源集成
refactor: 重构页面组件移除冗余Layout组件 feat: 实现WebSocket和事件总线系统 feat: 添加队列和调度系统 docs: 更新架构文档和服务映射 style: 清理重复接口定义使用数据源 chore: 更新依赖项配置 feat: 添加运行时系统和领域引导 ci: 配置ESLint边界检查规则 build: 添加Redis和WebSocket依赖 test: 添加MSW浏览器环境入口 perf: 优化数据获取逻辑使用统一数据源 fix: 修复类型定义和状态管理问题
This commit is contained in:
161
server/src/api/routes/certificate.ts
Normal file
161
server/src/api/routes/certificate.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { Router } from 'express';
|
||||
import { CertificateService } from '../../services/CertificateService';
|
||||
import { requireTraceContext } from '../../core/guards/trace-context.guard';
|
||||
import { requirePermission } from '../../core/guards/rbac.guard';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const certificateService = new CertificateService();
|
||||
|
||||
/**
|
||||
* [FE-COM001] 证书管理 API
|
||||
*/
|
||||
|
||||
router.get('/certificates', requireTraceContext, requirePermission('certificate:read'), async (req, res) => {
|
||||
try {
|
||||
const { tenantId, shopId } = req.traceContext;
|
||||
const { status, type, page = 1, pageSize = 10 } = req.query;
|
||||
|
||||
const result = await certificateService.query({
|
||||
tenantId,
|
||||
shopId,
|
||||
status: status as string,
|
||||
type: type as string,
|
||||
page: parseInt(page as string),
|
||||
pageSize: parseInt(pageSize as string)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.items,
|
||||
total: result.total,
|
||||
page: parseInt(page as string),
|
||||
pageSize: parseInt(pageSize as string)
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[CertificateRoutes] GET /certificates error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '获取证书列表失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/certificates/:id', requireTraceContext, requirePermission('certificate:read'), async (req, res) => {
|
||||
try {
|
||||
const { tenantId, shopId } = req.traceContext;
|
||||
const { id } = req.params;
|
||||
|
||||
const certificate = await certificateService.getById(id, tenantId, shopId);
|
||||
|
||||
if (!certificate) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: '证书不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: certificate
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[CertificateRoutes] GET /certificates/:id error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '获取证书详情失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/certificates', requireTraceContext, requirePermission('certificate:create'), async (req, res) => {
|
||||
try {
|
||||
const { tenantId, shopId, traceId } = req.traceContext;
|
||||
const certificateData = req.body;
|
||||
|
||||
const id = await certificateService.create({
|
||||
...certificateData,
|
||||
tenantId,
|
||||
shopId,
|
||||
traceId
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { id }
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[CertificateRoutes] POST /certificates error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '创建证书失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/certificates/:id', requireTraceContext, requirePermission('certificate:update'), async (req, res) => {
|
||||
try {
|
||||
const { tenantId, shopId, traceId } = req.traceContext;
|
||||
const { id } = req.params;
|
||||
const certificateData = req.body;
|
||||
|
||||
await certificateService.update(id, {
|
||||
...certificateData,
|
||||
tenantId,
|
||||
shopId,
|
||||
traceId
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[CertificateRoutes] PUT /certificates/:id error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '更新证书失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/certificates/:id', requireTraceContext, requirePermission('certificate:delete'), async (req, res) => {
|
||||
try {
|
||||
const { tenantId, shopId, traceId } = req.traceContext;
|
||||
const { id } = req.params;
|
||||
|
||||
await certificateService.delete(id, tenantId, shopId, traceId);
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[CertificateRoutes] DELETE /certificates/:id error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '删除证书失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/certificates/:id/status', requireTraceContext, requirePermission('certificate:approve'), async (req, res) => {
|
||||
try {
|
||||
const { tenantId, shopId, traceId } = req.traceContext;
|
||||
const { id } = req.params;
|
||||
const { status, approvedBy } = req.body;
|
||||
|
||||
await certificateService.updateStatus(id, tenantId, shopId, traceId, status, approvedBy);
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[CertificateRoutes] PUT /certificates/:id/status error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '更新证书状态失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
283
server/src/api/routes/leaderboard.ts
Normal file
283
server/src/api/routes/leaderboard.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import LeaderboardService from '../../services/LeaderboardService';
|
||||
import MerchantMetricsService from '../../services/MerchantMetricsService';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { authenticate, authorize } from '../middleware/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/leaderboard/:type', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { type } = req.params;
|
||||
const period = (req.query.period as 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME') || 'MONTHLY';
|
||||
const limit = parseInt(req.query.limit as string) || 10;
|
||||
|
||||
if (!['REVENUE', 'ROI', 'GROWTH'].includes(type)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid leaderboard type. Must be REVENUE, ROI, or GROWTH'
|
||||
});
|
||||
}
|
||||
|
||||
const leaderboard = await LeaderboardService.getLeaderboard(
|
||||
type as 'REVENUE' | 'ROI' | 'GROWTH',
|
||||
period
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
type,
|
||||
period,
|
||||
rankings: leaderboard.slice(0, limit),
|
||||
total: leaderboard.length,
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error fetching leaderboard:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch leaderboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/leaderboard/all', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const period = (req.query.period as 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME') || 'MONTHLY';
|
||||
|
||||
const [revenue, roi, growth] = await Promise.all([
|
||||
LeaderboardService.getLeaderboard('REVENUE', period),
|
||||
LeaderboardService.getLeaderboard('ROI', period),
|
||||
LeaderboardService.getLeaderboard('GROWTH', period)
|
||||
]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
revenue: { rankings: revenue, total: revenue.length },
|
||||
roi: { rankings: roi, total: roi.length },
|
||||
growth: { rankings: growth, total: growth.length },
|
||||
period,
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error fetching all leaderboards:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch leaderboards'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/leaderboard/stats', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stats = await LeaderboardService.getLeaderboardStats();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: stats
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error fetching leaderboard stats:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch leaderboard stats'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/leaderboard/my-rank', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const tenantId = req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
const period = (req.query.period as 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME') || 'MONTHLY';
|
||||
|
||||
const [revenueRank, roiRank, growthRank] = await Promise.all([
|
||||
LeaderboardService.getMerchantRank(tenantId, 'REVENUE', period),
|
||||
LeaderboardService.getMerchantRank(tenantId, 'ROI', period),
|
||||
LeaderboardService.getMerchantRank(tenantId, 'GROWTH', period)
|
||||
]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
revenue: revenueRank,
|
||||
roi: roiRank,
|
||||
growth: growthRank,
|
||||
period
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error fetching merchant rank:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch merchant rank'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/metrics/my-metrics', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const tenantId = req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
const metrics = await MerchantMetricsService.getLatestMetrics(tenantId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: metrics
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error fetching merchant metrics:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch merchant metrics'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/metrics/history', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const tenantId = req.user?.tenantId;
|
||||
if (!tenantId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
const startDate = req.query.startDate
|
||||
? new Date(req.query.startDate as string)
|
||||
: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
||||
const endDate = req.query.endDate
|
||||
? new Date(req.query.endDate as string)
|
||||
: new Date();
|
||||
|
||||
const history = await MerchantMetricsService.getMetricsHistory(tenantId, startDate, endDate);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
history,
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error fetching metrics history:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch metrics history'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/metrics/calculate', authenticate, authorize(['ADMIN', 'MANAGER']), async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { tenantId, shopId, period = 'MONTHLY' } = req.body;
|
||||
|
||||
if (!tenantId || !shopId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'tenantId and shopId are required'
|
||||
});
|
||||
}
|
||||
|
||||
const metrics = await MerchantMetricsService.calculateAndStoreMetrics({
|
||||
tenantId,
|
||||
shopId,
|
||||
period
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: metrics,
|
||||
message: 'Metrics calculated and stored successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error calculating metrics:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to calculate metrics'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/leaderboard/refresh', authenticate, authorize(['ADMIN']), async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { type, period } = req.body;
|
||||
|
||||
if (type && period) {
|
||||
await LeaderboardService.refreshLeaderboard(type, period);
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Leaderboard ${type} - ${period} refreshed successfully`
|
||||
});
|
||||
} else {
|
||||
await LeaderboardService.refreshAllLeaderboards();
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'All leaderboards refreshed successfully'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error refreshing leaderboard:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to refresh leaderboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/metrics/verify/:metricsId', authenticate, authorize(['ADMIN', 'FINANCE']), async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { metricsId } = req.params;
|
||||
|
||||
await MerchantMetricsService.verifyMetrics(metricsId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Metrics verified successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error verifying metrics:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to verify metrics'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/metrics/suspicious', authenticate, authorize(['ADMIN', 'FINANCE']), async (req: Request, res: Response) => {
|
||||
try {
|
||||
const suspicious = await MerchantMetricsService.flagSuspiciousMetrics();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: suspicious,
|
||||
message: `Found ${suspicious.length} potentially suspicious metrics`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[LeaderboardRoutes] Error fetching suspicious metrics:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch suspicious metrics'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,47 +1,386 @@
|
||||
import { Router } from 'express';
|
||||
import { BizStrategyController } from '../controllers/BizStrategyController';
|
||||
import { requireTraceContext } from '../../core/guards/trace-context.guard';
|
||||
import { requirePermission } from '../../core/guards/rbac.guard';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import StrategyService from '../../services/StrategyService';
|
||||
import StrategyRecommendationService from '../../services/StrategyRecommendationService';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { authenticate, authorize } from '../middleware/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* [BIZ_AI_16] 交互式策略建议 (Actionable Insights)
|
||||
*/
|
||||
router.get('/advice', requireTraceContext, requirePermission('strategy:read'), BizStrategyController.getAdvice);
|
||||
router.post('/advice/approve', requireTraceContext, requirePermission('strategy:execute'), BizStrategyController.approveAdvice);
|
||||
router.get('/advice/:adviceId/explanation', requireTraceContext, requirePermission('strategy:read'), BizStrategyController.getAdviceExplanation);
|
||||
router.get('/strategies', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { category, isActive, isFeatured, limit, offset } = req.query;
|
||||
|
||||
/**
|
||||
* [UX_IAT_01] 自治控制中心:停机与模式切换
|
||||
*/
|
||||
router.post('/kill-switch', requireTraceContext, requirePermission('strategy:kill'), BizStrategyController.toggleKillSwitch);
|
||||
router.post('/mode', requireTraceContext, requirePermission('strategy:write'), BizStrategyController.updateAutonomousMode);
|
||||
router.get('/status', requireTraceContext, BizStrategyController.getAutonomousStatus);
|
||||
const result = await StrategyService.getAllStrategies({
|
||||
category: category as string,
|
||||
isActive: isActive === 'true' ? true : isActive === 'false' ? false : undefined,
|
||||
isFeatured: isFeatured === 'true' ? true : isFeatured === 'false' ? false : undefined,
|
||||
limit: limit ? parseInt(limit as string) : undefined,
|
||||
offset: offset ? parseInt(offset as string) : undefined
|
||||
});
|
||||
|
||||
/**
|
||||
* [FE_SB_01] 策略仿真沙盒 (Sandbox Dashboard)
|
||||
*/
|
||||
router.post('/sandbox/run', requireTraceContext, requirePermission('strategy:write'), BizStrategyController.runSandboxSimulation);
|
||||
router.get('/sandbox/results', requireTraceContext, requirePermission('strategy:read'), BizStrategyController.getSandboxResults);
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.strategies,
|
||||
total: result.total
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching strategies:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch strategies'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* [BIZ_SC_11] 供应链询盘流 (Semi-Inquiry)
|
||||
*/
|
||||
router.post('/inquiry/start', requireTraceContext, BizStrategyController.startInquiry);
|
||||
router.post('/inquiry/:inquiryId/review', requireTraceContext, BizStrategyController.reviewInquiry);
|
||||
router.post('/inquiry/:inquiryId/send', requireTraceContext, BizStrategyController.sendInquiry);
|
||||
router.post('/inquiry/:inquiryId/accept', requireTraceContext, BizStrategyController.acceptAndPurchase);
|
||||
router.get('/strategies/featured', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 5;
|
||||
const strategies = await StrategyService.getFeaturedStrategies(limit);
|
||||
|
||||
/**
|
||||
* [BIZ_TRADE_02] 多仓库存编排 (Inventory Orchestration)
|
||||
*/
|
||||
router.get('/inventory/orchestrate', requireTraceContext, BizStrategyController.orchestrateInventory);
|
||||
router.post('/inventory/transfer', requireTraceContext, BizStrategyController.approveTransfer);
|
||||
res.json({
|
||||
success: true,
|
||||
data: strategies
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching featured strategies:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch featured strategies'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* [BIZ_AIS_01] 独立站套利回流 (Pixel Feedback)
|
||||
*/
|
||||
router.post('/pixel/event', requireTraceContext, BizStrategyController.collectPixelEvent);
|
||||
router.get('/strategies/trending', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 5;
|
||||
const strategies = await StrategyRecommendationService.getTrendingStrategies(limit);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: strategies
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching trending strategies:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch trending strategies'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/strategies/search', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { q } = req.query;
|
||||
|
||||
if (!q || typeof q !== 'string') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Search query is required'
|
||||
});
|
||||
}
|
||||
|
||||
const strategies = await StrategyService.searchStrategies(q);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: strategies
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error searching strategies:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to search strategies'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/strategies/category/:category', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { category } = req.params;
|
||||
const strategies = await StrategyService.getStrategiesByCategory(category);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: strategies
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching strategies by category:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch strategies by category'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/strategies/:id', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const strategy = await StrategyService.getStrategyById(id);
|
||||
|
||||
if (!strategy) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Strategy not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: strategy
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching strategy:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch strategy'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/strategies/:id/similar', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const strategies = await StrategyRecommendationService.getSimilarStrategies(id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: strategies
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching similar strategies:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch similar strategies'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/strategies', authenticate, authorize('strategy:create'), async (req: Request, res: Response) => {
|
||||
try {
|
||||
const strategy = await StrategyService.createStrategy({
|
||||
...req.body,
|
||||
created_by: (req as any).user?.id || 'system'
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: strategy,
|
||||
message: 'Strategy created successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error creating strategy:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to create strategy'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/strategies/:id/activate', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
const merchantStrategy = await StrategyService.activateStrategy({
|
||||
tenantId,
|
||||
strategyId: id,
|
||||
config: req.body.config
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: merchantStrategy,
|
||||
message: 'Strategy activated successfully'
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[StrategyRoutes] Error activating strategy:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to activate strategy'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/my-strategies', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
const { status } = req.query;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
const strategies = await StrategyService.getMerchantStrategies(
|
||||
tenantId,
|
||||
status as 'ACTIVE' | 'PAUSED' | 'COMPLETED' | 'FAILED'
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: strategies
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching merchant strategies:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch merchant strategies'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/my-strategies/:id/pause', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
await StrategyService.pauseStrategy(id, tenantId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Strategy paused successfully'
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[StrategyRoutes] Error pausing strategy:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to pause strategy'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/my-strategies/:id/resume', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
await StrategyService.resumeStrategy(id, tenantId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Strategy resumed successfully'
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[StrategyRoutes] Error resuming strategy:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to resume strategy'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/my-strategies/:id/complete', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
const { roi, revenue } = req.body;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (roi === undefined || revenue === undefined) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'ROI and revenue are required'
|
||||
});
|
||||
}
|
||||
|
||||
await StrategyService.completeStrategy(id, tenantId, { roi, revenue });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Strategy completed successfully'
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[StrategyRoutes] Error completing strategy:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to complete strategy'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/recommendations', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
const recommendations = await StrategyRecommendationService.getPersonalizedRecommendations(tenantId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: recommendations
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching recommendations:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch recommendations'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/recommendations/category/:category', authenticate, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { category } = req.params;
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Tenant ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
const recommendations = await StrategyRecommendationService.getCategoryRecommendations(
|
||||
category,
|
||||
tenantId
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: recommendations
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[StrategyRoutes] Error fetching category recommendations:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch category recommendations'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user