Просмотр исходного кода

feat(env): update environment configuration for MySQL and MongoDB connections across multiple files
feat(docs): add documentation for Redis cache keys and MySQL to MongoDB naming alignment action plans
refactor: remove unused ImageConfigService and related code
feat(prisma): add new seed script for mongo-stats

Dave 1 месяц назад
Родитель
Сommit
73ad3328dc

+ 4 - 5
.env.app

@@ -7,9 +7,9 @@ APP_ENV=test
 # MONGO_STATS_URL="mongodb://boxuser:dwR%3D%29whu2Ze@localhost:27017/box_stats?authSource=admin"
 
 # Dave local
-MYSQL_URL="mysql://root:123456@localhost:3306/box_admin"
-MONGO_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_admin?authSource=admin"
-MONGO_STATS_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_stats?authSource=admin"
+MYSQL_URL="mysql://root:rootpass@127.0.0.1:3306/box_admin"
+MONGO_URL="mongodb://boxadmin:boxpass@127.0.0.1:27017/box_admin?replicaSet=rs0&authSource=admin"
+MONGO_STATS_URL="mongodb://boxadmin:boxpass@127.0.0.1:27017/box_stats?replicaSet=rs0&authSource=admin"
 
 # office dev env
 # MYSQL_URL="mysql://root:123456@192.168.0.100:3306/box_admin"
@@ -37,7 +37,7 @@ JWT_SECRET=047df8aaa3d17dc1173c5a9a3052ba66c2b0bd96937147eb643319a0c90d132f
 JWT_ACCESS_TOKEN_TTL=43200
 
 # RabbitMQ Config
-RABBITMQ_URL="amqp://boxrabbit:BoxRabbit%232025@localhost:5672"
+RABBITMQ_URL="amqp://boxrabbit:BoxRabbit2025@127.0.0.1:5672"
 RABBITMQ_LOGIN_EXCHANGE="stats.user"
 RABBITMQ_LOGIN_QUEUE="stats.user.login.q"
 RABBITMQ_LOGIN_ROUTING_KEY="user.login"
@@ -83,4 +83,3 @@ UPLOAD_LIMIT_IMAGE=20
 UPLOAD_LIMIT_VIDEO=100
 UPLOAD_LIMIT_PDF=10
 UPLOAD_LIMIT_DEFAULT=10
-

+ 2 - 2
.env.docker

@@ -6,8 +6,8 @@ MYSQL_URL="mysql://root:rootpass@box-mysql:3306/box_admin"
 
 # MONGO_URL="mongodb://boxadmin:boxpass@box-mongodb:27017/box_admin?replicaSet=rs0&authSource=admin"
 # MONGO_STATS_URL="mongodb://boxadmin:boxpass@box-mongodb:27017/box_stats?replicaSet=rs0&authSource=admin"
-MONGO_URL="mongodb://box-mongodb:27017/box_admin?replicaSet=rs0"
-MONGO_STATS_URL="mongodb://box-mongodb:27017/box_stats?replicaSet=rs0"
+MONGO_URL="mongodb://boxadmin:boxpass@box-mongodb:27017/box_admin?replicaSet=rs0&authSource=admin"
+MONGO_STATS_URL="mongodb://boxadmin:boxpass@box-mongodb:27017/box_stats?replicaSet=rs0&authSource=admin"
 
 JWT_SECRET=047df8aaa3d17dc1173c5a9a3052ba66c2b0bd96937147eb643319a0c90d132f
 

+ 7 - 31
.env.mgnt

@@ -7,10 +7,13 @@ APP_ENV=test
 # MONGO_STATS_URL="mongodb://boxuser:dwR%3D%29whu2Ze@localhost:27017/box_stats?authSource=admin"
 
 # dave local
-MYSQL_URL="mysql://root:123456@localhost:3306/box_admin"
-MONGO_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_admin?authSource=admin"
-MONGO_STATS_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_stats?authSource=admin"
+# MYSQL_URL="mysql://root:123456@localhost:3306/box_admin"
+# MONGO_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_admin?authSource=admin"
+# MONGO_STATS_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_stats?authSource=admin"
 
+MYSQL_URL="mysql://root:rootpass@127.0.0.1:3306/box_admin"
+MONGO_URL="mongodb://boxadmin:boxpass@127.0.0.1:27017/box_admin?replicaSet=rs0&authSource=admin"
+MONGO_STATS_URL="mongodb://boxadmin:boxpass@127.0.0.1:27017/box_stats?replicaSet=rs0&authSource=admin"
 
 # office dev env
 # MYSQL_URL="mysql://root:123456@192.168.0.100:3306/box_admin"
@@ -26,7 +29,7 @@ REDIS_KEY_PREFIX=box:
 
 # RabbitMQ Config: RABBITMQ_URL="amqp://boxrabbit:BoxRabbit#2025@localhost:5672"
 # RabbitMQ Config
-RABBITMQ_URL="amqp://boxrabbit:BoxRabbit%232025@localhost:5672"
+RABBITMQ_URL="amqp://boxrabbit:BoxRabbit2025@127.0.0.1:5672"
 RABBITMQ_LOGIN_EXCHANGE="stats.user"
 RABBITMQ_LOGIN_QUEUE="stats.user.login.q"
 RABBITMQ_LOGIN_ROUTING_KEY="user.login"
@@ -56,32 +59,6 @@ APP_ISSUER=BOX-MGNT
 JWT_SECRET=047df8aaa3d17dc1173c5a9a3052ba66c2b0bd96937147eb643319a0c90d132f
 JWT_ACCESS_TOKEN_TTL=43200
 
-# JAVA 管理 API
-MGNT_API_URL=http://47.76.151.238:83
-MGNT_API_TOKEN=
-
-# IM Chat API
-IMCHAT_API_URL=http://47.76.151.238:88
-IMCHAT_API_TOKEN=
-
-# 游戏服配置
-GAME_SERVER_ADMIN_API_URL=http://119.28.182.132:83
-GAME_SERVER_TOKEN=
-
-# 第三方批量创建用户密钥
-B_BASE_URL=https://wwapi.hxc1t.com
-B_SIGN_SECRET=Z3VhbmNpbmV3ZWl4aWFvMTIzNDU2
-PARTNER_MD5_KEY=160360904be3dd23bf4f1278a74196efdbf3f9b834ce883ef6ae09eb05c5c652
-PARTNER_ITEMS_LIMIT=100
-B_BASE_ORDER_ADD=/open/open/order/add
-B_BASE_ORDER_UPDATE_STATUS=/open/open/order/updateStatus
-B_BASE_CHAT_ADD=/open/order/chat/send
-
-# Oss 配置
-OSS_ACCESS_KEY_ID=AKIA6GSNGR5PHKXR6O6H
-OSS_ACCESS_KEY_SECRET=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
-OSS_BUCKET=ww-buckets
-OSS_REGION=ap-east-1
 
 # LOCAL IMAGE STORAGE 配置
 # NOTE: Images are encrypted - frontend needs to access via appropriate endpoint
@@ -113,4 +90,3 @@ UPLOAD_LIMIT_VIDEO=100
 UPLOAD_LIMIT_PDF=10
 UPLOAD_LIMIT_DEFAULT=10
 
-

+ 4 - 4
.env.stats

@@ -7,9 +7,9 @@ APP_ENV=test
 # MONGO_STATS_URL="mongodb://boxuser:dwR%3D%29whu2Ze@localhost:27017/box_stats?authSource=admin"
 
 # dave local
-MYSQL_URL="mysql://root:123456@localhost:3306/box_admin"
-MONGO_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_admin?authSource=admin"
-MONGO_STATS_URL="mongodb://admin:ZXcv%21%21996@localhost:27017/box_stats?authSource=admin"
+MYSQL_URL="mysql://root:rootpass@127.0.0.1:3306/box_admin"
+MONGO_URL="mongodb://boxadmin:boxpass@127.0.0.1:27017/box_admin?replicaSet=rs0&authSource=admin"
+MONGO_STATS_URL="mongodb://boxadmin:boxpass@127.0.0.1:27017/box_stats?replicaSet=rs0&authSource=admin"
 
 
 # office dev env
@@ -28,7 +28,7 @@ JWT_SECRET=047df8aaa3d17dc1173c5a9a3052ba66c2b0bd96937147eb643319a0c90d132f
 JWT_ACCESS_TOKEN_TTL=43200
 
 # RabbitMQ Config
-RABBITMQ_URL="amqp://boxrabbit:BoxRabbit%232025@localhost:5672"
+RABBITMQ_URL="amqp://boxrabbit:BoxRabbit2025@127.0.0.1:5672"
 RABBITMQ_LOGIN_EXCHANGE="stats.user"
 RABBITMQ_LOGIN_QUEUE="stats.user.login.q"
 RABBITMQ_LOGIN_ROUTING_KEY="user.login"

+ 41 - 0
action-plans/20251220-ACT-01.md

@@ -0,0 +1,41 @@
+# Redis cache keys
+
+## In active use
+
+1. box:app:video:category:list:{categoryId} – LIST of video IDs pulled from Mongo’s videoMedia where categoryIds contains the category and status is Completed; writer is VideoCategoryCacheBuilder (video-category-cache.builder.ts (line 27)), and readers are VideoService.getVideoList/getVideosByCategoryListFallback (video.service.ts (line 824) and (line 305)).
+
+2. box:app:video:tag:list:{categoryId}:{tagId} – LIST of video IDs filtered by tag; same builder (video-category-cache.builder.ts (line 251)) and VideoService.getVideosByTagListFallback/searchVideosByTagName (video.service.ts (line 400) and (line 1200)).
+
+3. box:app:tag:list:{categoryId} – LIST of stringified tag objects (id, name, seq, status, timestamps, categoryId); writer VideoCategoryCacheBuilder (video-category-cache.builder.ts (line 338)), readers VideoService.getTagListForCategory and getCategoriesWithTags (video.service.ts (line 169) and (line 740)).
+
+4. app:video:list:category:{channelId}:{categoryId}:latest – ZSET of video IDs scored by editedAt/updatedAt; writer VideoListCacheBuilder (video-list-cache.builder.ts (line 85)), reader VideoService.getVideosByCategoryWithPaging (video.service.ts (line 230)).
+
+5. app:video:list:tag:{channelId}:{tagId}:latest – ZSET of tag-specific video IDs with the same scoring source; writer VideoListCacheBuilder (video-list-cache.builder.ts (line 138)), reader VideoService.getVideosByTagWithPaging (video.service.ts (line 400)).
+
+6. app:video:list:home:{channelId}:{section} – LIST of the latest top-N video IDs for each section (featured/latest/editorPick); builder exists (video-list-cache.builder.ts (line 190)) but buildHomeSectionsForChannel is never invoked in buildAll (calls are commented out at (line 65)), so the key is expected by VideoService.getHomeSectionVideos (video.service.ts (line 600)) but currently empty unless manually populated.
+
+7. app:category:all – JSON array of all categories plus their tag names (id, name, subtitle, seq, tags array from tag table); writer CategoryCacheBuilder (category-cache.builder.ts (line 1)), reader VideoService.getCategoriesWithTags (video.service.ts (line 740)).
+
+8. app:video:recommended – JSON array of sampled completed videos mapped into RecommendedVideoItem; writer RecommendedVideosCacheBuilder (recommended-videos-cache.builder.ts (line 30)), reader VideoService.getRecommendedVideos (video.service.ts (line 1532)).
+
+9. app:adpool:{adType} – JSON array of AdPayload entries populated from the Mongo ads and adsModule tables (status=1, valid date range, ordered by seq); writer AdPoolService.rebuildPoolForType (ad-pool.service.ts (line 59)), reader AdService.getAdForPlacement/listAdsByType (ad.service.ts (line 80)).
+
+10. app:ad:by-id:{adId} – JSON ad payload (id, advertiser, title, content/url/cover, adType) with TTL 5 minutes; writer AdCacheWarmupService (ad-cache-warmup.service.ts (line 18)), reader AdService.getAdForPlacement (same file region).
+
+11. app:video:detail:{videoId} – referenced by VideoService.getVideoDetail (video.service.ts (line 100)) but no current writer exists in the repo, so either the key is stale/empty or needs a fresh builder as part of the redesign.
+
+## “Ghost” keys (no consumer/endpoints)
+
+1. app:channel:all, app:channel:by-id:{channelId}, app:channel:with-categories:{channelId} – defined in CacheKeys (cache-keys.ts (line 18)) and written by ChannelCacheBuilder (channel-cache.builder.ts (line 24)), but no controller/service reads them today.
+
+2. app:category:{categoryId} and app:category:by-id:{categoryId} – written alongside app:category:all (category-cache.builder.ts (line 18)) but only accessed by CategoryCacheService (category-cache.service.ts (line 1)), which isn’t injected anywhere in the workspace.
+3. app:category:with-tags:{categoryId} – defined for tag refresh semantics (cache-keys.ts (line 36)) and even invalidated (cache-sync.service.ts (line 672)), yet no builder populates it or consumer reads it.
+4. app:tag:all – built by TagCacheBuilder (tag-cache.builder.ts (line 1)) but only exposed through TagCacheService (tag-cache.service.ts (line 1)), which is never used.
+5. Legacy list keys app:videolist:home:page:{page} and app:videolist:channel:{channelId}:page:{page} (cache-keys.ts (line 61)) are defined but not written to or read from anywhere today.
+
+## Redis cache key redesign
+
+- Inventory every actively used key: box:app:video:category:list:{categoryId}, box:app:video:tag:list:{categoryId}:{tagId}, box:app:tag:list:{categoryId}, app:video:list:category:{channelId}:{categoryId}:latest, app:video:list:tag:{channelId}:{tagId}:latest, app:video:list:home:{channelId}:{section}, app:category:all, app:video:recommended, app:adpool:{adType}, app:ad:by-id:{adId}, plus note app:video:detail:{videoId} has no writer now (video.service.ts (line 100)).
+- Capture “ghost” keys still defined in cache-keys.ts and their unused consumers (app:channel:_, app:category:_, app:category:with-tags:\*, app:tag:all, legacy paginated video list keys).
+- Document content schemas and sources (Mongo tables/fields) alongside the services writing/reading each key to guide the redesign without breaking flows.
+- Schedule verification for keys whose builders are currently unused/commented (e.g., VideoListCacheBuilder.buildTagPoolsForChannel and buildHomeSectionsForChannel) before changing namespaces or semantics.

+ 41 - 0
action-plans/20251220-ACT-02.md

@@ -0,0 +1,41 @@
+# 2025-12-20 ACT-02: MySQL → Mongo naming alignment
+## Objectives
+1. Keep existing `prisma/mysql/schema` files intact except for the explicitly deleted schema.
+2. Align Prisma/Mongo naming so Mongo side reads `sys_*` but retains the original MySQL table names.
+3. Drop unused `ImageConfig` schema once business confirms no dependency.
+
+## Tasks
+### A. Schema naming / retention
+- Keep the existing MySQL Prisma files (`user.prisma`, `role.prisma`, etc.) under `prisma/mysql/schema`.
+- When creating the Mongo-facing equivalents, rename as follows (with `@@map` to the original table names):
+  - `sys-user.prisma` → maps to `sys_user`.
+  - `sys-role.prisma` → `sys_role`.
+  - `sys-user-role.prisma` → `sys_user_role`.
+  - `sys-menu.prisma` → `sys_menu`.
+  - `sys-role-menu.prisma` → `sys_role_menu`.
+  - `sys-api-permission.prisma` → `sys_api_permission`.
+  - `sys-role-api-permission.prisma` → `sys_role_api_permission`.
+  - `sys-operation-log.prisma` → `sys_operation_log`.
+  - `sys-login-log.prisma` → `sys_login_log`.
+  - `sys-cache-sync-action.prisma` → `sys_cacheSyncAction`.
+
+### B. Additional collection splits
+- Extract `ProviderVideoSync` from `prisma/mysql/schema/main.prisma` into a dedicated `sys-providerVideoSync.prisma` whose `@@map` is `sys_providerVideoSync`.
+- Ensure `cache-sync-action.prisma` becomes `sys-cache-sync-action.prisma` while mapping the table to `sys_cacheSyncAction` and preserving BigInt PK/indices.
+- Do not migrate `sys_quota_log`; keep the MySQL schema file untouched and leave it out of Mongo plan.
+
+### C. Clean-up rules
+- Delete only `image-config.prisma` to remove the unused `image_config` table once the service no longer depends on it; do not touch other MySQL schema files.
+- Update documentation to reflect new Mongo schema filenames and their MySQL table mappings.
+
+### D. Verification & follow-up
+- Draft a migration checklist enumerating which mgnt services still depend on each schema to ensure feature parity.
+- Schedule follow-up on Mongo auto-increment/counters once naming is stabilized.
+
+### E. Coding action plan
+1. Add new Mongo-focused Prisma schema files under `prisma/mongo/schema/` using the `sys-*.prisma` names listed above, each importing the matching model definition and adding `@@map("<original_table>")` to preserve the existing table name; keep the existing MySQL files untouched for reference/use.
+2. For `ProviderVideoSync`, move the model definition out of `prisma/mysql/schema/main.prisma` into `prisma/mongo/schema/sys-providerVideoSync.prisma` and ensure it maps to `sys_providerVideoSync`; update any import/usage in the Mongo Prisma client config if needed.
+3. Replace `cache-sync-action.prisma` with `prisma/mongo/schema/sys-cache-sync-action.prisma` that maps to `sys_cacheSyncAction` while retaining the `id`, indexes, and payload JSON semantics; update Mongo Prisma client instantiation to load the new schema file.
+4. Keep `sys_quota_log` only in MySQL schema (document it as excluded) and ensure no routing/logic tries to read the Mongo counterpart.
+5. Delete `prisma/mysql/schema/image-config.prisma` as agreed; remove any references in documentation or migration scripts so future exports do not include the `image_config` table.
+6. Update `docs/MGNT_MYSQL_TO_MONGO_MIGRATION.md` or related migration notes with the new filenames and mappings, plus the coding steps above so the implementation story is clear for tomorrow’s work.

+ 0 - 69
libs/db/src/image-config.service.ts

@@ -1,69 +0,0 @@
-import { Injectable } from '@nestjs/common';
-
-import { MysqlPrismaService } from './prisma/mysql-prisma.service';
-
-type ImageConfigRecord = {
-  id: number;
-  channelId: number | null;
-  providerDecodeBase: string | null;
-  localBaseUrl: string | null;
-  s3BaseUrl: string | null;
-  preferredSource: string;
-  status: number;
-  createAt: bigint;
-  updateAt: bigint;
-};
-
-@Injectable()
-export class ImageConfigService {
-  private readonly cache = new Map<string, ImageConfigRecord | null>();
-
-  constructor(private readonly prisma: MysqlPrismaService) {}
-
-  private buildCacheKey(channelId?: number) {
-    return channelId != null ? `channel:${channelId}` : 'default';
-  }
-
-  async getConfig(channelId?: number): Promise<ImageConfigRecord | null> {
-    const cacheKey = this.buildCacheKey(channelId);
-    if (this.cache.has(cacheKey)) {
-      return this.cache.get(cacheKey) ?? null;
-    }
-
-    const client = this.prisma as unknown as {
-      imageConfig: {
-        findFirst: (args: {
-          where: Record<string, unknown>;
-          orderBy: { id: 'asc' | 'desc' };
-        }) => Promise<ImageConfigRecord | null>;
-      };
-    };
-
-    const config = await client.imageConfig.findFirst({
-      where: channelId != null ? { channelId } : { channelId: null },
-      orderBy: { id: 'desc' },
-    });
-
-    // Fallback to default (channelId null) when channel-specific config is missing
-    const finalConfig =
-      config ??
-      (channelId != null
-        ? await client.imageConfig.findFirst({
-            where: { channelId: null },
-            orderBy: { id: 'desc' },
-          })
-        : null);
-
-    this.cache.set(cacheKey, finalConfig ?? null);
-    return finalConfig ?? null;
-  }
-
-  async refresh(channelId?: number): Promise<ImageConfigRecord | null> {
-    this.cache.delete(this.buildCacheKey(channelId));
-    return this.getConfig(channelId);
-  }
-
-  clearCache() {
-    this.cache.clear();
-  }
-}

+ 2 - 3
libs/db/src/shared.module.ts

@@ -1,13 +1,12 @@
 import { HttpModule } from '@nestjs/axios';
 import { Global, Module } from '@nestjs/common';
 import { PrismaModule } from './prisma/prisma.module';
-import { ImageConfigService } from './image-config.service';
 import { UtilsService } from './utils.service';
 
 @Global()
 @Module({
   imports: [PrismaModule, HttpModule],
-  providers: [UtilsService, ImageConfigService],
-  exports: [PrismaModule, HttpModule, UtilsService, ImageConfigService],
+  providers: [UtilsService],
+  exports: [PrismaModule, HttpModule, UtilsService],
 })
 export class SharedModule {}

+ 10 - 12
package.json

@@ -12,19 +12,17 @@
     "dev:stats": "dotenv -e .env.stats -- nest start box-stats-api --watch",
     "build:stats": "nest build box-stats-api",
     "start:stats": "node dist/apps/box-stats-api/src/main.js",
-    "prisma:migrate:dev:mysql": "dotenv -e .env.mgnt -- prisma migrate dev --schema=prisma/mysql/schema",
-    "prisma:migrate:reset:mysql": "dotenv -e .env.mgnt -- prisma migrate reset --schema=prisma/mysql/schema",
+    "prisma:migrate:mysql": "dotenv -e .env.mgnt -- prisma migrate dev --schema=prisma/mysql/schema",
+    "prisma:migrate:box-admin": "dotenv -e .env.mgnt -- prisma db push --schema=prisma/mongo/schema",
+    "prisma:migrate:box-stats": "dotenv -e .env.mgnt -- prisma db push --schema=prisma/mongo-stats/schema",
+    "prisma:reset:mysql": "dotenv -e .env.mgnt -- prisma migrate reset --schema=prisma/mysql/schema",
     "prisma:generate:mysql": "dotenv -e .env.mgnt -- prisma generate --schema=prisma/mysql/schema",
-    "prisma:generate:mongo": "dotenv -e .env.mgnt -- prisma generate --schema=prisma/mongo/schema",
-    "prisma:generate:mongostats": "dotenv -e .env.mgnt -- prisma generate --schema=prisma/mongo-stats/schema",
-    "prisma:generate": "pnpm prisma:generate:mysql && pnpm prisma:generate:mongo && pnpm prisma:generate:mongostats",
+    "prisma:generate:box-admin": "dotenv -e .env.mgnt -- prisma generate --schema=prisma/mongo/schema",
+    "prisma:generate:box-stats": "dotenv -e .env.mgnt -- prisma generate --schema=prisma/mongo-stats/schema",
+    "prisma:generate": "pnpm prisma:generate:mysql && pnpm prisma:generate:box-admin && pnpm prisma:generate:box-stats",
     "prisma:seed:mysql": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mysql/seed.ts",
-    "prisma:seed:mongo": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mongo/seed.ts",
-    "prisma:setup:mysql": "pnpm prisma:migrate:dev:mysql && pnpm prisma:seed:mysql",
-    "prisma:migrate:deploy:mysql:test": "dotenv -e .env.mgnt -- prisma migrate deploy --schema=prisma/mysql/schema",
-    "prisma:seed:mysql:test": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mysql/seed.ts",
-    "prisma:seed:mongo:test": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mongo/seed.ts",
-    "prisma:setup:test": "pnpm prisma:migrate:deploy:mysql:test && pnpm prisma:seed:mysql:test && pnpm prisma:seed:mongo:test",
+    "prisma:seed:box-admin": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mongo/seed.ts",
+    "prisma:seed:box-stats": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mongo-stats/seed.ts",
     "typecheck": "tsc --noEmit --project tsconfig.base.json",
     "typecheck:watch": "tsc --noEmit --watch --project tsconfig.base.json",
     "lint": "eslint \"{apps,libs}/**/*.ts\" --max-warnings 0",
@@ -127,4 +125,4 @@
     "tsx": "^4.20.6",
     "typescript": "^5.4.5"
   }
-}
+}

+ 19 - 0
prisma/mongo-stats/seed.ts

@@ -0,0 +1,19 @@
+// prisma/mongo-stats/seed.ts
+import { PrismaClient } from '@prisma/mongo-stats/client';
+
+const prisma = new PrismaClient();
+
+async function main() {
+  await prisma.$connect();
+  console.log('mongo-stats seed: connected (no default documents defined yet).');
+}
+
+main()
+  .then(async () => {
+    await prisma.$disconnect();
+  })
+  .catch(async (error) => {
+    console.error('mongo-stats seed failed:', error);
+    await prisma.$disconnect();
+    process.exit(1);
+  });