Kaynağa Gözat

feat(channel): add category and tag resolution methods for channel creation and updates
feat(prisma): introduce CategoryTag model and update existing models for category-tag relationships

Dave 1 ay önce
ebeveyn
işleme
f149bd7576

+ 53 - 6
apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.service.ts

@@ -41,6 +41,34 @@ export class ChannelService {
     return typeof value === 'string' ? value.trim() : value;
   }
 
+  private async resolveCategoriesByIds(ids: string[]) {
+    if (!ids.length) return [];
+
+    const categories = await this.mongoPrismaService.category.findMany({
+      where: { id: { in: ids } },
+      select: { id: true, name: true },
+    });
+
+    return categories.map((c) => ({
+      id: c.id,
+      name: c.name,
+    }));
+  }
+
+  private async resolveTagsByIds(ids: string[]) {
+    if (!ids.length) return [];
+
+    const tags = await this.mongoPrismaService.tag.findMany({
+      where: { id: { in: ids } },
+      select: { id: true, name: true },
+    });
+
+    return tags.map((t) => ({
+      id: t.id,
+      name: t.name,
+    }));
+  }
+
   async create(dto: CreateChannelDto) {
     // Check for duplicate channel name
     const existingChannel = await this.mongoPrismaService.channel.findFirst({
@@ -67,6 +95,14 @@ export class ChannelService {
     const isDefault = (await this.mongoPrismaService.channel.count()) === 0;
 
     const now = this.now();
+    const categoryIds = dto.categories?.map((c) => c.id) ?? [];
+    const tagIds = dto.tags?.map((t) => t.id) ?? [];
+
+    const [categories, tags] = await Promise.all([
+      this.resolveCategoriesByIds(categoryIds),
+      this.resolveTagsByIds(tagIds),
+    ]);
+    const tagNames = tags.map((t) => t.name.trim());
 
     const channel = await this.mongoPrismaService.channel.create({
       data: {
@@ -79,9 +115,9 @@ export class ChannelService {
         clientNotice: this.trimOptional(dto.clientNotice) ?? null,
         remark: this.trimOptional(dto.remark) ?? null,
         isDefault,
-        categories: (dto.categories as any) || null,
-        tags: (dto.tags as any) || null,
-        tagNames: dto.tagNames || [],
+        categories: categories.length ? categories : null,
+        tags: tags.length ? tags : null,
+        tagNames: tagNames,
         createAt: now,
         updateAt: now,
       },
@@ -129,6 +165,14 @@ export class ChannelService {
     }
 
     const now = this.now();
+    const categoryIds = dto.categories?.map((c) => c.id) ?? [];
+    const tagIds = dto.tags?.map((t) => t.id) ?? [];
+
+    const [categories, tags] = await Promise.all([
+      this.resolveCategoriesByIds(categoryIds),
+      this.resolveTagsByIds(tagIds),
+    ]);
+    const tagNames = tags.map((t) => t.name.trim());
 
     try {
       const channel = await this.mongoPrismaService.channel.update({
@@ -142,9 +186,12 @@ export class ChannelService {
           clientName: this.trimOptional(dto.clientName) ?? null,
           clientNotice: this.trimOptional(dto.clientNotice) ?? null,
           remark: this.trimOptional(dto.remark) ?? null,
-          categories: (dto.categories as any) || null,
-          tags: (dto.tags as any) || null,
-          tagNames: dto.tagNames || [],
+
+          // 🔑 resolved by backend
+          categories: categories.length ? categories : null,
+          tags: tags.length ? tags : null,
+
+          tagNames: tagNames,
           updateAt: now,
         },
       });

+ 12 - 0
prisma/mongo/schema/category-tag.prisma

@@ -0,0 +1,12 @@
+model CategoryTag {
+  id         String @id @map("_id") @default(auto()) @db.ObjectId
+
+  categoryId String @db.ObjectId
+  tagId      String @db.ObjectId
+
+  @@unique([categoryId, tagId])
+  @@index([categoryId])
+  @@index([tagId])
+
+  @@map("categoryTag")
+}

+ 16 - 12
prisma/mongo/schema/category.prisma

@@ -1,18 +1,22 @@
 model Category {
-  id          String     @id @map("_id") @default(auto()) @db.ObjectId
-  name        String                          // 分类名称
-  subtitle    String?                         // 副标题
-  seq         Int        @default(0)         // 排序
-  status      Int        @default(1)        // 状态 0: 禁用; 1: 启用
+  id        String   @id @map("_id") @default(auto()) @db.ObjectId
+  name      String                               // 分类名称
+  subtitle  String?                              // 副标题
+  seq       Int      @default(0)                 // 排序
+  status    Int      @default(1)                 // 状态 0: 禁用; 1: 启用
 
-  createAt    BigInt     @default(0)          // 创建时间
-  updateAt    BigInt     @default(0)          // 更新时间
+  // epoch seconds stored as BigInt
+  createAt  BigInt   @default(0)                 // 创建时间 (秒)
+  updateAt  BigInt   @default(0)                 // 更新时间 (秒)
 
-  // Relations - storing tag references as objects (id + name)
-  tags        Json?                           // Array of { id, name }
-  
-  // Tag names only for search optimization
-  tagNames    String[]                        // Array of tag names
+  // Legacy/optional: denormalized tag refs (to be discussed)
+  tags      Json?                                // Array of { id, name }
 
+  // Search optimization / browsing helper (keep)
+  tagNames  String[]                             // Array of tag names
+
+  @@index([status])
+  @@index([seq])
+  @@index([tagNames])
   @@map("category")
 }

+ 22 - 21
prisma/mongo/schema/channel.prisma

@@ -1,27 +1,28 @@
 model Channel {
-  id            String    @id @map("_id") @default(auto()) @db.ObjectId
-  channelId     String    @unique
-  name          String                          // 渠道名称
-  landingUrl    String                          // 最新网址
-  videoCdn      String?                         // 视频CDN
-  coverCdn      String?                         // 封面CDN
-  clientName    String?                         // 客户端名称
-  clientNotice  String?                         // 客户端公告
-  remark        String?                         // 备注
-  isDefault     Boolean   @default(false)       // 默认渠道
+  id           String   @id @map("_id") @default(auto()) @db.ObjectId
+  channelId    String   @unique
+  name         String                             // 渠道名称
+  landingUrl   String                             // 最新网址
+  videoCdn     String?                            // 视频CDN
+  coverCdn     String?                            // 封面CDN
+  clientName   String?                            // 客户端名称
+  clientNotice String?                            // 客户端公告
+  remark       String?                            // 备注
+  isDefault    Boolean  @default(false)            // 默认渠道
 
-  // epoch (recommended: ms) stored as BigInt
-  createAt      BigInt     @default(0)          // 创建时间
-  updateAt      BigInt     @default(0)          // 更新时间
+  // epoch seconds stored as BigInt
+  createAt     BigInt   @default(0)               // 创建时间 (秒)
+  updateAt     BigInt   @default(0)               // 更新时间 (秒)
 
-  // Relations - storing category references as objects (id + name)
-  categories    Json?                           // Array of { id, name }
-  
-  // Relations - storing tag references as objects (id + name)
-  tags          Json?                           // Array of { id, name }
-  
-  // Tag names only for search optimization
-  tagNames      String[]                        // Array of tag names
+  // Channel enabled categories (UI: 片库分类 - category selection)
+  categories   Json?                              // Array of { id, name }
 
+  // Homepage recommended tags (UI: 首页推荐)
+  tags         Json?                              // Array of { id, name }
+
+  // Denormalized names derived from tags[].name (trimmed) for quick query/display
+  tagNames     String[]                           // Array of tag names
+
+  @@index([isDefault])
   @@map("channel")
 }

+ 13 - 8
prisma/mongo/schema/tag.prisma

@@ -1,15 +1,20 @@
 model Tag {
-  id          String     @id @map("_id") @default(auto()) @db.ObjectId
-  name        String                        // 标签名称
+  id         String   @id @map("_id") @default(auto()) @db.ObjectId
+  name       String                               // 标签名称
 
-  // DB field is "catergoryId", but we use "categoryId" in code
-  categoryId  String     @map("catergoryId") @db.ObjectId  // 分类 ID
+  // Legacy field: DB is "catergoryId"; tag can exist without Category now
+  categoryId String?  @map("catergoryId") @db.ObjectId
 
-  seq         Int        @default(0)        // 排序
-  status      Int        @default(1)        // 状态 0: 禁用; 1: 启用
+  seq        Int      @default(0)                 // 排序
+  status     Int      @default(1)                 // 状态 0: 禁用; 1: 启用
 
-  createAt    BigInt     @default(0)          // 创建时间
-  updateAt    BigInt     @default(0)          // 更新时间
+  // epoch seconds stored as BigInt
+  createAt   BigInt   @default(0)                 // 创建时间 (秒)
+  updateAt   BigInt   @default(0)                 // 更新时间 (秒)
 
+  @@index([name])
+  @@index([status])
+  @@index([seq])
+  @@index([categoryId])
   @@map("tag")
 }