소스 검색

refactor: remove ProvinceCity module and related files

Dave 2 달 전
부모
커밋
6cc778a302

+ 0 - 2
apps/box-mgnt-api/src/mgnt-backend/feature/feature.module.ts

@@ -1,7 +1,6 @@
 import { Module } from '@nestjs/common';
 // import { OssModule } from './oss/oss.module';
 import { S3Module } from './s3/s3.module';
-import { ProvinceCityModule } from './province-city/province-city.module';
 import { SystemParamsModule } from './system-params/system-params.module';
 import { MgntHttpServiceModule } from './mgnt-http-service/mgnt-http-service.module';
 import { SyncVideomediaModule } from './sync-videomedia/sync-videomedia.module';
@@ -10,7 +9,6 @@ import { SyncVideomediaModule } from './sync-videomedia/sync-videomedia.module';
   imports: [
     // OssModule,
     S3Module,
-    ProvinceCityModule,
     SystemParamsModule,
     MgntHttpServiceModule,
     SyncVideomediaModule,

+ 0 - 74
apps/box-mgnt-api/src/mgnt-backend/feature/province-city/province-city.controller.ts

@@ -1,74 +0,0 @@
-import {
-  BadRequestException,
-  Controller,
-  Get,
-  Param,
-  Post,
-  Req,
-  UploadedFile,
-  UseInterceptors,
-} from '@nestjs/common';
-import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
-import { ProvinceCityService } from './province-city.service';
-import { FastifyRequest } from 'fastify';
-
-@ApiTags('Province-City')
-@Controller('province-city')
-export class ProvinceCityController {
-  constructor(private readonly provinceCityService: ProvinceCityService) {}
-
-  @Get()
-  @ApiOperation({ summary: 'Get all provinces and their cities' })
-  @ApiResponse({
-    status: 200,
-    description: 'List of all provinces and their cities.',
-  })
-  findAll() {
-    return this.provinceCityService.findAll();
-  }
-
-  @Get(':province')
-  @ApiOperation({ summary: 'Get cities by province' })
-  @ApiParam({ name: 'province', description: 'Name of the province' })
-  @ApiResponse({
-    status: 200,
-    description: 'List of cities for the specified province.',
-  })
-  @ApiResponse({ status: 404, description: 'Province not found.' })
-  findOne(@Param('province') province: string) {
-    return this.provinceCityService.findOne(province);
-  }
-
-  @Post('upload')
-  async uploadProvinces(@Req() req: FastifyRequest) {
-    // 1. Ensure multipart
-    if (
-      typeof (req as any).isMultipart !== 'function' ||
-      !(req as any).isMultipart()
-    ) {
-      throw new BadRequestException('Request must be multipart/form-data');
-    }
-
-    // 2. Parse the parts
-    const parts = (req as any).parts();
-    for await (const part of parts) {
-      if (part.type === 'file' && part.fieldname === 'file') {
-        // Accepts only .json
-        if (!part.filename.endsWith('.json')) {
-          throw new BadRequestException('File must be .json');
-        }
-        // Read file content as buffer/string
-        const buffer = await part.toBuffer();
-        let provinceData: Record<string, string[]>;
-        try {
-          provinceData = JSON.parse(buffer.toString());
-        } catch {
-          throw new BadRequestException('Invalid JSON file');
-        }
-        await this.provinceCityService.flushAndSeedProvinces(provinceData);
-        return { message: 'Provinces table flushed and seeded with new data.' };
-      }
-    }
-    throw new BadRequestException('File not found in upload');
-  }
-}

+ 0 - 143
apps/box-mgnt-api/src/mgnt-backend/feature/province-city/province-city.helper.ts

@@ -1,143 +0,0 @@
-// province-city.helper.ts
-
-const MUNICIPALITIES = new Set(['北京', '上海', '天津', '重庆']);
-const SARS = new Set(['香港', '澳门']);
-
-export function normalizeName(s?: string | null): string {
-  if (!s) return '';
-  let n = String(s).trim();
-
-  // squeeze spaces
-  n = n.replace(/\s+/g, '');
-
-  // remove specific long suffixes first
-  n = n
-    .replace(/(市辖区)$/u, '')
-    .replace(/(自治州)$/u, '') // keep this
-    .replace(/(自治县)$/u, '')
-    .replace(/(地区)$/u, '')
-    .replace(/(自治区)$/u, '')
-    .replace(/(特别行政区)$/u, '');
-
-  // IMPORTANT: do NOT strip generic "州"
-  // only remove 市 / 盟 / 区 / 县
-  n = n.replace(/(市|盟|区|县)$/u, '');
-
-  return n.trim();
-}
-
-function buildCityToProvinceIndex(provinceCityMap: Record<string, string[]>) {
-  const cityToProv = new Map<string, string>();
-  for (const [prov, cities] of Object.entries(provinceCityMap)) {
-    const np = normalizeName(prov);
-    const allowed = (cities || []).map((c) => normalizeName(c)).filter(Boolean);
-    for (const nc of allowed) {
-      if (!cityToProv.has(nc)) cityToProv.set(nc, np);
-    }
-    if (MUNICIPALITIES.has(np) || SARS.has(np)) {
-      if (!cityToProv.has(np)) cityToProv.set(np, np);
-    }
-  }
-  return cityToProv;
-}
-
-let _memoKey = '';
-let _memoIdx: Map<string, string> | null = null;
-function getCityIdx(provinceCityMap: Record<string, string[]>) {
-  const key = JSON.stringify(Object.keys(provinceCityMap));
-  if (!_memoIdx || _memoKey !== key) {
-    _memoIdx = buildCityToProvinceIndex(provinceCityMap);
-    _memoKey = key;
-  }
-  return _memoIdx!;
-}
-
-function matchCityInProvince(
-  nCity: string,
-  allowedCities: string[],
-): string | null {
-  if (!nCity) return null;
-  // allowedCities expected normalized
-  if (allowedCities.includes(nCity)) return nCity;
-  const hit = allowedCities.find((c) => nCity.includes(c) || c.includes(nCity));
-  return hit ?? null;
-}
-
-/** Returns a *normalized* pair */
-export function resolveProvinceCity(
-  incoming: { city?: string | null; province?: string | null },
-  provinceCityMap: Record<string, string[]>,
-): { province: string; city: string } | null {
-  const cityIdx = getCityIdx(provinceCityMap);
-
-  const nCity = normalizeName(incoming.city ?? '');
-  const nProv = normalizeName(incoming.province ?? '');
-
-  // 全国 (only if both are 全国 or province empty)
-  if (nCity === '全国' && (!nProv || nProv === '全国')) {
-    return { province: '全国', city: '全国' };
-  }
-
-  // prefer provided province; else infer from city
-  const provExists = !!nProv && !!provinceCityMap[nProv];
-  const inferredProv = nCity ? cityIdx.get(nCity) : undefined;
-  const finalProvince = provExists ? nProv : inferredProv;
-  if (!finalProvince) return null;
-
-  const allowedCities = (provinceCityMap[finalProvince] || []).map(
-    normalizeName,
-  );
-
-  // Municipality / SAR → city == province
-  if (MUNICIPALITIES.has(finalProvince) || SARS.has(finalProvince)) {
-    return { province: finalProvince, city: finalProvince };
-  }
-
-  if (nCity) {
-    const matched = matchCityInProvince(nCity, allowedCities);
-    if (matched) return { province: finalProvince, city: matched };
-    // a city was supplied but didn’t match → don’t downgrade to 全国
-    return null;
-  }
-
-  // no city provided → allow province-level 全国 if present
-  if (allowedCities.includes('全国')) {
-    return { province: finalProvince, city: '全国' };
-  }
-
-  // no city; keep empty city (or return null if you prefer)
-  return { province: finalProvince, city: '' as any };
-}
-
-/** Map normalized to canonical labels present in your map */
-export function toCanonicalProvinceCity(
-  resolved: { province: string; city: string } | null,
-  provinceCityMap: Record<string, string[]>,
-): { province: string; city: string } | null {
-  if (!resolved) return null;
-
-  const nProv = normalizeName(resolved.province);
-  const nCity = normalizeName(resolved.city);
-
-  const provKey = Object.keys(provinceCityMap).find(
-    (p) => normalizeName(p) === nProv,
-  );
-  if (!provKey) return null;
-
-  if (MUNICIPALITIES.has(nProv) || SARS.has(nProv)) {
-    return { province: provKey, city: provKey };
-  }
-
-  const cities = provinceCityMap[provKey] ?? [];
-  const found =
-    cities.find((c) => normalizeName(c) === nCity) ??
-    cities.find((c) => {
-      const nc = normalizeName(c);
-      return nCity.includes(nc) || nc.includes(nCity);
-    }) ??
-    (nCity === '全国' && cities.some((c) => normalizeName(c) === '全国')
-      ? '全国'
-      : null);
-
-  return { province: provKey, city: found ?? resolved.city };
-}

+ 0 - 13
apps/box-mgnt-api/src/mgnt-backend/feature/province-city/province-city.module.ts

@@ -1,13 +0,0 @@
-// src/feature/province-city/province-city.module.ts
-import { Module } from '@nestjs/common';
-import { ProvinceCityService } from './province-city.service';
-import { ProvinceCityController } from './province-city.controller';
-import { PrismaModule } from '@box/db/prisma/prisma.module';
-
-@Module({
-  imports: [PrismaModule],
-  providers: [ProvinceCityService],
-  controllers: [ProvinceCityController],
-  exports: [ProvinceCityService],
-})
-export class ProvinceCityModule {}

+ 0 - 61
apps/box-mgnt-api/src/mgnt-backend/feature/province-city/province-city.service.ts

@@ -1,61 +0,0 @@
-import { Injectable, NotFoundException } from '@nestjs/common';
-import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
-import { pinyin } from 'pinyin-pro';
-
-@Injectable()
-export class ProvinceCityService {
-  constructor(private readonly mongoPrismaService: MongoPrismaService) {}
-
-  async findAll() {
-    const data = await this.mongoPrismaService.provinceCity.findMany({
-      select: { province: true, cities: true },
-    });
-
-    // 按拼音首字母排序
-    data.sort((a, b) => {
-      return pinyin(a.province, { toneType: 'none', type: 'array' })
-        .join('')
-        .localeCompare(
-          pinyin(b.province, { toneType: 'none', type: 'array' }).join(''),
-        );
-    });
-
-    // 每个城市列表也排序
-    for (const item of data) {
-      item.cities.sort((a: string, b: string) => {
-        return pinyin(a, { toneType: 'none', type: 'array' })
-          .join('')
-          .localeCompare(
-            pinyin(b, { toneType: 'none', type: 'array' }).join(''),
-          );
-      });
-    }
-
-    return data;
-  }
-
-  async findOne(province: string) {
-    const provinceCity = await this.mongoPrismaService.provinceCity.findUnique({
-      select: { province: true, cities: true },
-      where: { province },
-    });
-
-    if (!provinceCity) {
-      throw new NotFoundException(`Province: ${province} not found`);
-    }
-
-    return provinceCity;
-  }
-
-  async flushAndSeedProvinces(provinceData: Record<string, string[]>) {
-    // 1. Remove all
-    await this.mongoPrismaService.provinceCity.deleteMany();
-
-    // 2. Insert all new
-    for (const [province, cities] of Object.entries(provinceData)) {
-      await this.mongoPrismaService.provinceCity.create({
-        data: { province, cities },
-      });
-    }
-  }
-}

+ 0 - 2
apps/box-mgnt-api/src/mgnt-backend/mgnt-backend.module.ts

@@ -11,7 +11,6 @@ import { LoginLogModule } from './core/logging/login-log/login-log.module';
 import { OperationLogModule } from './core/logging/operation-log/operation-log.module';
 import { QuotaLogModule } from './core/logging/quota-log/quota-log.module';
 import { S3Module } from './feature/s3/s3.module';
-import { ProvinceCityModule } from './feature/province-city/province-city.module';
 import { SystemParamsModule } from './feature/system-params/system-params.module';
 import { SyncVideomediaModule } from './feature/sync-videomedia/sync-videomedia.module';
 
@@ -31,7 +30,6 @@ import { SyncVideomediaModule } from './feature/sync-videomedia/sync-videomedia.
           OperationLogModule,
           QuotaLogModule,
           S3Module,
-          ProvinceCityModule,
           SystemParamsModule,
           SyncVideomediaModule,
         ],

+ 0 - 2
box-mgnt-note.md

@@ -6,8 +6,6 @@ pnpm install
 tree -L 2 -I 'node_modules|.git'
 
 
-
-
 echo "20" > .nvmrc
 nvm install
 nvm use

+ 22 - 0
mongo-db-seeds.md

@@ -0,0 +1,22 @@
+adsModule
+
+```bash
+db.adsModule.drop()
+db.adsModule.insertMany([
+{ adsModule: "启动页(10:21)" },
+{ adsModule: "轮播(2:1)" },
+{ adsModule: "弹窗-图标(1:1)" },
+{ adsModule: "弹窗-图片(2:3)" },
+{ adsModule: "弹窗-官方(2:3)" },
+{ adsModule: "瀑布流-图标(1:1)" },
+{ adsModule: "瀑布流-文字" },
+{ adsModule: "布流-视频(8:5)" },
+{ adsModule: "悬浮-底部(1:1)" },
+{ adsModule: "悬浮-边缘(1:1)" },
+{ adsModule: "banner(4:1)" },
+{ adsModule: "片头(8:5)" },
+{ adsModule: "暂停(2:1)" }
+])
+
+
+```

+ 7 - 0
prisma/mongo/schema/ads-module.prisma

@@ -0,0 +1,7 @@
+model AdsModule {
+  id          String    @id @default(auto()) @map("_id") @db.ObjectId
+  adsModule   String    /// 广告模块
+
+  @@map("adsModule")
+}
+

+ 24 - 0
prisma/mongo/schema/ads.prisma

@@ -0,0 +1,24 @@
+model Ads {
+  id           String     @id @map("_id") @default(auto()) @db.ObjectId
+  channelId    String     @db.ObjectId        // 渠道 ID
+  adsModule    String                        // 广告模块 (banner/startup/轮播等)
+  advertiser   String                        // 广告商 (业务上限制 max 20 字符)
+  title        String                        // 标题 (业务上限制 max 20 字符)
+  adsContent   String?                       // 广告文案 (业务上限制 max 500 字符)
+  adsCoverImg  String?                       // 广告图片
+  adsUrl       String?                       // 广告链接
+
+  // 有效期,使用 BigInt epoch
+  startDt      BigInt                        // 开始时间
+  expiryDt     BigInt                        // 到期时间
+
+  seq          Int        @default(0)        // 排序
+  status       Int        @default(1)        // 状态 0: 禁用; 1: 启用
+  createAt     BigInt     @default(0)        // 创建时间
+  updateAt     BigInt     @default(0)        // 更新时间
+
+  // Relations
+  channel      Channel    @relation(fields: [channelId], references: [id])
+
+  @@map("ads")  // collection name
+}

+ 17 - 0
prisma/mongo/schema/category.prisma

@@ -0,0 +1,17 @@
+model Category {
+  id          String     @id @map("_id") @default(auto()) @db.ObjectId
+  name        String                          // 分类名称
+  subtitle    String?                         // 副标题
+  channelId   String     @db.ObjectId        // 渠道 ID
+  seq         Int        @default(0)         // 排序
+  status      Int                              // 状态 0: 禁用; 1: 启用
+
+  createAt    BigInt     @default(0)          // 创建时间
+  updateAt    BigInt     @default(0)          // 更新时间
+
+  // Relations
+  channel     Channel    @relation(fields: [channelId], references: [id])
+  tags        Tag[]
+
+  @@map("category")
+}

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

@@ -0,0 +1,21 @@
+model Channel {
+  id            String     @id @map("_id") @default(auto()) @db.ObjectId
+  name          String                          // 渠道名称
+  landingUrl    String                          // 最新网址
+  videoCdn      String?                         // 视频CDN
+  coverCdn      String?                         // 封面CDN
+  clientName    String?                         // 客户端名称
+  clientNotice  String?                         // 客户端公告
+  remark        String?                         // 备注
+
+  // epoch (recommended: ms) stored as BigInt
+  createAt      BigInt     @default(0)          // 创建时间
+  updateAt      BigInt     @default(0)          // 更新时间
+
+  // Relations
+  categories    Category[]
+  ads           Ads[]
+  tags          Tag[]
+
+  @@map("channel")
+}

+ 0 - 20
prisma/mongo/schema/online-log.prisma

@@ -1,20 +0,0 @@
-model OnlineLog {
-  id      String   @id @default(auto()) @map("_id") @db.ObjectId // 内部ID
-  uid     Int       // 用户唯一ID
-  day     Int       // 注册时间到现在的天数
-  login   DateTime  // 登入时间
-  logout  DateTime? // 登出时间
-  ip      String[] // List<String> becomes String[] // IP
-  beat    Int     // 心跳次数
-
-  // Optional fields you commented out in Java
-  // create   DateTime?
-  // bind     Int?
-  // device   Int?         // e.g., enum later if needed
-  // channel  String?
-  // machine  String?
-
-  @@unique([uid, day], name: "uid_index")
-  @@index([login], name: "login_index") // TTL index — needs manual handling outside Prisma
-  @@index([day], name: "day_index")
-}

+ 0 - 7
prisma/mongo/schema/province-city.prisma

@@ -1,7 +0,0 @@
-model ProvinceCity {
-  id         String          @id @default(auto()) @map("_id") @db.ObjectId
-  province   String   @unique
-  cities String[]
-
-  @@map("provinceCity")
-}

+ 0 - 9
prisma/mongo/schema/stats-config.prisma

@@ -1,9 +0,0 @@
-model StatsConfig {
-  id              String  @id @map("_id")
-  status          Boolean /// 扣量总开关:是否开启扣量
-  percentRange    Int[]   @db.Int /// 扣量配置:${扣量},百分比范围,如10%-20%,每天取一个范围内随机整数值来计算,初期实现麻烦的话可以仅取固定百分比,如20%-20%,必选项
-  timeRangeStatus Boolean /// 是否开启扣量时段
-  timeRange       Int[] /// 扣量时段:${时段-时段}内启用扣量配置
-
-  @@map("statsConfig")
-}

+ 35 - 35
prisma/mongo/schema/system-param.prisma

@@ -1,35 +1,35 @@
-// enums
-enum ParamSide {
-  client   // 客户端
-  server   // 后端
-}
-
-enum ParamDataType {
-  url
-  number
-  time     // store as unix seconds (string/int in API; here we keep Int)
-  switch   // 0/1
-  text
-}
-
-model SystemParam {
-  // UI numeric ID (you’ll set this in service: last+1)
-  id        Int          @id @map("_id") @db.Int
-
-  // 业务字段
-  side      ParamSide    // 参数类型: client/server
-  type      ParamDataType @map("type")  // 数据类型
-  name      String
-  value     String       // <= 500 chars (enforce in service/DTO)
-  remark    String?      // <= 500
-
-  createAt  Int          @map("create_at")
-  updateAt  Int          @map("update_at")
-
-  // indexes
-  @@index([updateAt(sort: Desc)])
-  @@index([side, type])
-  @@unique([side, name]) // avoid duplicate key names under same side
-
-  @@map("systemParams")
-}
+// enums
+enum ParamSide {
+  client   // 客户端
+  server   // 后端
+}
+
+enum ParamDataType {
+  number
+  text
+  url
+  time     // store as unix seconds (string/int in API; here we keep Int)
+  switch   // 0/1
+}
+
+model SystemParam {
+  // UI numeric ID (you’ll set this in service: last+1)
+  id        Int          @id @map("_id") @db.Int
+
+  // 业务字段
+  side      ParamSide       // 参数类型: client/server
+  dataType  ParamDataType   // 数据类型
+  name      String
+  value     String          // <= 500 chars (enforce in service/DTO)
+  remark    String?         // <= 500
+
+  createAt  BigInt     @default(0)       // 创建时间
+  updateAt  BigInt     @default(0)       // 更新时间
+
+  // indexes
+  @@index([updateAt(sort: Desc)])
+  @@index([side, type])
+  @@unique([side, name]) // avoid duplicate key names under same side
+
+  @@map("systemParams")
+}

+ 20 - 0
prisma/mongo/schema/tag.prisma

@@ -0,0 +1,20 @@
+model Tag {
+  id          String     @id @map("_id") @default(auto()) @db.ObjectId
+  name        String                        // 标签名称
+  channelId   String     @db.ObjectId      // 渠道 ID
+
+  // DB field is "catergoryId", but we use "categoryId" in code
+  categoryId  String     @map("catergoryId") @db.ObjectId  // 分类 ID
+
+  seq         Int        @default(0)        // 排序
+  status      Int        @default(1)        // 状态 0: 禁用; 1: 启用
+
+  createAt  BigInt     @default(0)          // 创建时间
+  updateAt  BigInt     @default(0)          // 更新时间
+
+  // Relations
+  channel     Channel    @relation(fields: [channelId], references: [id])
+  category    Category   @relation(fields: [categoryId], references: [id])
+
+  @@map("tag")
+}