feat: 联盟返佣系统规范化并打通技能广场(无 ICP 证合规变现主路径)
- 新增 AffiliateProgram / AffiliateLink / AffiliateClick 规范化 Prisma 模型 取代原手写裸表 affiliate_stats / affiliate_clicks - 新增迁移 prisma/migrations/20260711000000_add_affiliate_models - 重构 affiliate.service.ts 改用 Prisma ORM,消除 $queryRawUnsafe SQL 注入 - 重构 affiliate.controller.ts 接口:programs / links(?skillId,?toolId) / stats / click - 前端 affiliate 页接入真实接口,移除硬编码 demo 数据 - 技能详情页新增「学此技能推荐使用的工具」联盟链接区块 - 新增 affiliate.service.spec.ts(4 用例通过)与幂等种子 seed-affiliate.ts - 更新 docs/progress/current.md,明确无 ICP 经营许可证下以联盟返佣为合规变现主路径 - 含此前工作区未提交改动(工具 slug 路由、支付/订单、SEO 等) Co-Authored-By: opencode <opencode@anthropic.com>
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ToolsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findAll(params: { page?: number; pageSize?: number; categoryId?: number; isFeatured?: boolean }) {
|
||||
async findAll(params: { page?: number; pageSize?: number; categoryId?: number; isFeatured?: boolean; tag?: string }) {
|
||||
const page = Number(params.page ?? 1);
|
||||
const pageSize = Number(params.pageSize ?? 20);
|
||||
const { categoryId, isFeatured } = params;
|
||||
const { categoryId, isFeatured, tag } = params;
|
||||
const where: any = { status: 'PUBLISHED', deletedAt: null };
|
||||
if (categoryId) where.categoryId = categoryId;
|
||||
if (isFeatured !== undefined) where.isFeatured = isFeatured;
|
||||
if (tag) where.tags = { contains: tag };
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.tool.findMany({
|
||||
@@ -27,7 +28,35 @@ export class ToolsService {
|
||||
return { items, total, page, pageSize };
|
||||
}
|
||||
|
||||
async create(data: { name: string; description?: string; url: string; icon?: string; affiliateLink?: string; categoryId?: number; tags?: string }) {
|
||||
async findBySlug(slug: string) {
|
||||
const tool = await this.prisma.tool.findUnique({
|
||||
where: { slug },
|
||||
include: { category: true },
|
||||
});
|
||||
if (!tool || tool.deletedAt) {
|
||||
throw new NotFoundException('工具不存在');
|
||||
}
|
||||
// 增加浏览量
|
||||
await this.prisma.tool.update({
|
||||
where: { id: tool.id },
|
||||
data: { viewCount: { increment: 1 } },
|
||||
});
|
||||
return tool;
|
||||
}
|
||||
|
||||
async create(data: {
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
url: string;
|
||||
icon?: string;
|
||||
affiliateLink?: string;
|
||||
categoryId?: number;
|
||||
tags?: string;
|
||||
metaTitle?: string;
|
||||
metaDesc?: string;
|
||||
content?: string;
|
||||
}) {
|
||||
return this.prisma.tool.create({ data });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user