create-pardx-scaffold 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/template/apps/api/libs/infra/clients/internal/ip-info/dto/ip-info.dto.ts +15 -0
  3. package/template/apps/api/libs/infra/clients/internal/ip-info/index.ts +3 -0
  4. package/template/apps/api/libs/infra/clients/internal/ip-info/ip-info.client.ts +57 -0
  5. package/template/apps/api/libs/infra/clients/internal/ip-info/ip-info.module.ts +17 -0
  6. package/template/apps/api/libs/infra/shared-services/file-storage/README.md +4 -4
  7. package/template/apps/api/libs/infra/shared-services/file-storage/bucket-resolver.ts +4 -4
  8. package/template/apps/api/libs/infra/shared-services/file-storage/file-storage.module.ts +3 -3
  9. package/template/apps/api/libs/infra/shared-services/ip-geo/continent-mapping.ts +86 -0
  10. package/template/apps/api/libs/infra/shared-services/ip-geo/index.ts +37 -0
  11. package/template/apps/api/libs/infra/shared-services/ip-geo/ip-geo.module.ts +21 -0
  12. package/template/apps/api/libs/infra/shared-services/ip-geo/ip-geo.service.ts +135 -0
  13. package/template/apps/api/libs/infra/shared-services/uploader/index.ts +1 -1
  14. package/template/apps/api/libs/infra/shared-services/uploader/uploader.service.ts +10 -2
  15. package/template/apps/api/src/app.module.ts +0 -13
  16. package/template/apps/api/src/modules/uploader/uploader.controller.ts +10 -25
  17. package/template/apps/web/components/index.ts +21 -0
  18. package/template/apps/web/components/skeletons.tsx +188 -0
  19. package/template/apps/web/components/suspense-utils.tsx +123 -0
  20. package/template/apps/web/lib/api/contracts/client.ts +4 -0
  21. package/template/apps/web/lib/deprecation-warning.ts +150 -0
  22. package/template/apps/web/lib/queries/optimistic-update.ts +204 -0
  23. package/template/packages/constants/src/index.ts +22 -0
  24. package/template/apps/api/src/modules/health/health.controller.ts +0 -13
  25. package/template/apps/api/src/modules/health/health.module.ts +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pardx-scaffold",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Scaffold a new project from PardxAI monorepo (git-tracked files)",
5
5
  "license": "MIT",
6
6
  "bin": "./cli.js",
@@ -0,0 +1,15 @@
1
+ /**
2
+ * IP Info API Response DTO
3
+ *
4
+ * @description ipinfo.io API 返回的数据结构
5
+ */
6
+ export interface IpInfoResponse {
7
+ ip: string;
8
+ country: string;
9
+ region?: string;
10
+ city?: string;
11
+ loc?: string;
12
+ org?: string;
13
+ postal?: string;
14
+ timezone?: string;
15
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ip-info.module';
2
+ export * from './ip-info.client';
3
+ export * from './dto/ip-info.dto';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * IP Info Client
3
+ *
4
+ * 职责:仅负责与 ipinfo.io API 通信
5
+ * - 调用 ipinfo.io API 获取 IP 地理位置信息
6
+ * - 不访问数据库
7
+ * - 不包含业务逻辑
8
+ */
9
+ import { Injectable, Inject } from '@nestjs/common';
10
+ import { HttpService } from '@nestjs/axios';
11
+ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
12
+ import { Logger } from 'winston';
13
+ import { ConfigService } from '@nestjs/config';
14
+ import { firstValueFrom } from 'rxjs';
15
+ import { IpInfoResponse } from './dto/ip-info.dto';
16
+ import { IpInfoConfig } from '@/config/validation';
17
+
18
+ @Injectable()
19
+ export class IpInfoClient {
20
+ private readonly config: IpInfoConfig;
21
+
22
+ constructor(
23
+ private readonly httpService: HttpService,
24
+ private readonly configService: ConfigService,
25
+ @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
26
+ ) {
27
+ this.config = configService.getOrThrow<IpInfoConfig>('ipinfo');
28
+ }
29
+
30
+ /**
31
+ * 获取 IP 地理位置信息
32
+ * @param ip IP 地址
33
+ * @returns IP 信息
34
+ */
35
+ async getIpInfo(ip: string): Promise<IpInfoResponse> {
36
+ const url = `${this.config.url}/${ip}?token=${this.config.token}`;
37
+
38
+ try {
39
+ const response = await firstValueFrom(
40
+ this.httpService.get<IpInfoResponse>(url, {
41
+ timeout: 10000,
42
+ }),
43
+ );
44
+
45
+ this.logger.debug(`IP info fetched for ${ip}`, {
46
+ country: response.data.country,
47
+ });
48
+
49
+ return response.data;
50
+ } catch (error) {
51
+ this.logger.error(`Failed to fetch IP info for ${ip}`, {
52
+ error: (error as Error).message,
53
+ });
54
+ throw error;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,17 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { HttpModule } from '@nestjs/axios';
3
+ import { ConfigModule } from '@nestjs/config';
4
+ import { IpInfoClient } from './ip-info.client';
5
+
6
+ @Module({
7
+ imports: [
8
+ ConfigModule,
9
+ HttpModule.register({
10
+ timeout: 10000,
11
+ maxRedirects: 3,
12
+ }),
13
+ ],
14
+ providers: [IpInfoClient],
15
+ exports: [IpInfoClient],
16
+ })
17
+ export class IpInfoClientModule {}
@@ -59,7 +59,7 @@ file-storage/
59
59
 
60
60
  ```typescript
61
61
  import { Module } from '@nestjs/common';
62
- import { FileStorageServiceModule } from '@app/services/file-storage';
62
+ import { FileStorageServiceModule } from '@app/shared-services/file-storage';
63
63
 
64
64
  @Module({
65
65
  imports: [FileStorageServiceModule],
@@ -71,7 +71,7 @@ export class VideoModule {}
71
71
 
72
72
  ```typescript
73
73
  import { Injectable } from '@nestjs/common';
74
- import { FileStorageService } from '@app/services/file-storage';
74
+ import { FileStorageService } from '@app/shared-services/file-storage';
75
75
  import { FileBucketVendor } from '@prisma/client';
76
76
 
77
77
  @Injectable()
@@ -244,7 +244,7 @@ const audioInfo = await fileStorage.getAudioInfo('oss', 'bucket', 'audio.mp3');
244
244
  用于高级场景,直接操作存储客户端。
245
245
 
246
246
  ```typescript
247
- import { FileStorageClientFactory } from '@app/services/file-storage';
247
+ import { FileStorageClientFactory } from '@app/shared-services/file-storage';
248
248
 
249
249
  // 获取客户端
250
250
  const client = factory.getClient('oss', 'my-bucket');
@@ -267,7 +267,7 @@ const configs = factory.getAllBucketConfigs();
267
267
  用于存储桶解析的高级场景。
268
268
 
269
269
  ```typescript
270
- import { BucketResolver } from '@app/services/file-storage';
270
+ import { BucketResolver } from '@app/shared-services/file-storage';
271
271
 
272
272
  // 解析存储桶
273
273
  const result = await resolver.resolve({
@@ -16,7 +16,7 @@ import { Logger } from 'winston';
16
16
  import { FileBucketVendor } from '@prisma/client';
17
17
 
18
18
  import { PardxUploader } from '@app/clients/internal/file-storage';
19
- import { IpInfoService } from '@app/services/ip-info';
19
+ import { IpGeoService } from '@app/shared-services/ip-geo';
20
20
  import { AppConfig } from '@/config/validation';
21
21
  import arrayUtil from '@/utils/array.util';
22
22
  import enviromentUtil from '@/utils/enviroment.util';
@@ -89,12 +89,12 @@ export class BucketResolver {
89
89
  * 构造函数
90
90
  *
91
91
  * @param {ConfigService} configService - NestJS 配置服务
92
- * @param {IpInfoService} ipInfoService - IP 信息服务
92
+ * @param {IpGeoService} ipGeoService - IP 地理位置服务(infra 层)
93
93
  * @param {Logger} logger - Winston 日志记录器
94
94
  */
95
95
  constructor(
96
96
  private readonly configService: ConfigService,
97
- private readonly ipInfoService: IpInfoService,
97
+ private readonly ipGeoService: IpGeoService,
98
98
  @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
99
99
  ) {
100
100
  this.bucketConfigs =
@@ -178,7 +178,7 @@ export class BucketResolver {
178
178
  // 确定区域
179
179
  let zone = enviromentUtil.getBaseZone();
180
180
  if (ip) {
181
- const continent = await this.ipInfoService.getContinent(ip);
181
+ const continent = await this.ipGeoService.getContinent(ip);
182
182
  zone = continent ?? zone;
183
183
  }
184
184
  locale = locale ?? zone;
@@ -12,7 +12,7 @@
12
12
  import { Module } from '@nestjs/common';
13
13
  import { ConfigModule } from '@nestjs/config';
14
14
  import { HttpModule } from '@nestjs/axios';
15
- import { IpInfoServiceModule } from '@app/services/ip-info';
15
+ import { IpGeoModule } from '@app/shared-services/ip-geo';
16
16
  import { RedisModule } from '@app/redis';
17
17
  import { FileStorageService } from './file-storage.service';
18
18
  import { FileStorageClientFactory } from './file-storage.factory';
@@ -32,7 +32,7 @@ import { BucketResolver } from './bucket-resolver';
32
32
  * - `ConfigModule`: 配置服务
33
33
  * - `HttpModule`: HTTP 客户端
34
34
  * - `RedisModule`: Redis 缓存
35
- * - `IpInfoServiceModule`: IP 信息服务(用于区域感知)
35
+ * - `IpGeoModule`: IP 地理位置服务(用于区域感知,纯 infra 层)
36
36
  *
37
37
  * @example
38
38
  * ```typescript
@@ -55,7 +55,7 @@ import { BucketResolver } from './bucket-resolver';
55
55
  * ```
56
56
  */
57
57
  @Module({
58
- imports: [ConfigModule, RedisModule, IpInfoServiceModule, HttpModule],
58
+ imports: [ConfigModule, RedisModule, IpGeoModule, HttpModule],
59
59
  providers: [BucketResolver, FileStorageClientFactory, FileStorageService],
60
60
  exports: [FileStorageService, FileStorageClientFactory, BucketResolver],
61
61
  })
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @fileoverview 国家代码到大洲的静态映射
3
+ *
4
+ * 本文件提供纯静态的国家代码到大洲映射,不依赖数据库。
5
+ * 用于 infra 层的 IP 地理位置服务,避免 infra 依赖 domain。
6
+ *
7
+ * @module ip-geo/continent-mapping
8
+ */
9
+
10
+ /**
11
+ * 大洲类型
12
+ */
13
+ export type Continent = 'as' | 'eu' | 'na' | 'sa' | 'af' | 'oc' | 'an';
14
+
15
+ /**
16
+ * 大洲到国家代码的映射
17
+ *
18
+ * 数据来源:ISO 3166-1 alpha-2 国家代码
19
+ */
20
+ export const CONTINENT_COUNTRIES: Record<Continent, string[]> = {
21
+ // 亚洲 (Asia)
22
+ as: [
23
+ 'AF', 'AM', 'AZ', 'BH', 'BD', 'BT', 'BN', 'KH', 'CN', 'CY', 'GE', 'HK',
24
+ 'IN', 'ID', 'IR', 'IQ', 'IL', 'JP', 'JO', 'KZ', 'KW', 'KG', 'LA', 'LB',
25
+ 'MO', 'MY', 'MV', 'MN', 'MM', 'NP', 'KP', 'OM', 'PK', 'PS', 'PH', 'QA',
26
+ 'SA', 'SG', 'KR', 'LK', 'SY', 'TW', 'TJ', 'TH', 'TL', 'TR', 'TM', 'AE',
27
+ 'UZ', 'VN', 'YE',
28
+ ],
29
+ // 欧洲 (Europe)
30
+ eu: [
31
+ 'AL', 'AD', 'AT', 'BY', 'BE', 'BA', 'BG', 'HR', 'CZ', 'DK', 'EE', 'FI',
32
+ 'FR', 'DE', 'GR', 'HU', 'IS', 'IE', 'IT', 'XK', 'LV', 'LI', 'LT', 'LU',
33
+ 'MT', 'MD', 'MC', 'ME', 'NL', 'MK', 'NO', 'PL', 'PT', 'RO', 'RU', 'SM',
34
+ 'RS', 'SK', 'SI', 'ES', 'SE', 'CH', 'UA', 'GB', 'VA',
35
+ ],
36
+ // 北美洲 (North America)
37
+ na: [
38
+ 'AG', 'BS', 'BB', 'BZ', 'CA', 'CR', 'CU', 'DM', 'DO', 'SV', 'GD', 'GT',
39
+ 'HT', 'HN', 'JM', 'MX', 'NI', 'PA', 'KN', 'LC', 'VC', 'TT', 'US',
40
+ ],
41
+ // 南美洲 (South America)
42
+ sa: [
43
+ 'AR', 'BO', 'BR', 'CL', 'CO', 'EC', 'GY', 'PY', 'PE', 'SR', 'UY', 'VE',
44
+ ],
45
+ // 非洲 (Africa)
46
+ af: [
47
+ 'DZ', 'AO', 'BJ', 'BW', 'BF', 'BI', 'CV', 'CM', 'CF', 'TD', 'KM', 'CG',
48
+ 'CD', 'DJ', 'EG', 'GQ', 'ER', 'SZ', 'ET', 'GA', 'GM', 'GH', 'GN', 'GW',
49
+ 'CI', 'KE', 'LS', 'LR', 'LY', 'MG', 'MW', 'ML', 'MR', 'MU', 'MA', 'MZ',
50
+ 'NA', 'NE', 'NG', 'RW', 'ST', 'SN', 'SC', 'SL', 'SO', 'ZA', 'SS', 'SD',
51
+ 'TZ', 'TG', 'TN', 'UG', 'ZM', 'ZW',
52
+ ],
53
+ // 大洋洲 (Oceania)
54
+ oc: [
55
+ 'AU', 'FJ', 'KI', 'MH', 'FM', 'NR', 'NZ', 'PW', 'PG', 'WS', 'SB', 'TO',
56
+ 'TV', 'VU',
57
+ ],
58
+ // 南极洲 (Antarctica) - 通常无常住人口
59
+ an: ['AQ'],
60
+ };
61
+
62
+ /**
63
+ * 国家代码到大洲的反向映射(运行时生成)
64
+ */
65
+ export const COUNTRY_TO_CONTINENT: Record<string, Continent> = {};
66
+
67
+ // 构建反向映射
68
+ for (const [continent, countries] of Object.entries(CONTINENT_COUNTRIES)) {
69
+ for (const country of countries) {
70
+ COUNTRY_TO_CONTINENT[country] = continent as Continent;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 根据国家代码获取大洲
76
+ *
77
+ * @param countryCode - ISO 3166-1 alpha-2 国家代码
78
+ * @param defaultContinent - 默认大洲(找不到时返回)
79
+ * @returns 大洲代码
80
+ */
81
+ export function getContinentByCountry(
82
+ countryCode: string,
83
+ defaultContinent: Continent = 'as',
84
+ ): Continent {
85
+ return COUNTRY_TO_CONTINENT[countryCode?.toUpperCase()] ?? defaultContinent;
86
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @fileoverview IP 地理位置服务导出入口(Infra 层)
3
+ *
4
+ * 本模块提供纯 infra 层的 IP 地理位置服务:
5
+ * - IpGeoService: IP 地理位置查询服务
6
+ * - IpGeoModule: NestJS 模块
7
+ * - getContinentByCountry: 静态大洲映射函数
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { IpGeoModule, IpGeoService } from '@app/shared-services/ip-geo';
12
+ *
13
+ * @Module({
14
+ * imports: [IpGeoModule],
15
+ * })
16
+ * export class MyModule {}
17
+ *
18
+ * @Injectable()
19
+ * class MyService {
20
+ * constructor(private readonly ipGeo: IpGeoService) {}
21
+ *
22
+ * async getRegion(ip: string) {
23
+ * return await this.ipGeo.getContinent(ip);
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * @module ip-geo
29
+ */
30
+ export { IpGeoModule } from './ip-geo.module';
31
+ export { IpGeoService } from './ip-geo.service';
32
+ export {
33
+ getContinentByCountry,
34
+ Continent,
35
+ CONTINENT_COUNTRIES,
36
+ COUNTRY_TO_CONTINENT,
37
+ } from './continent-mapping';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @fileoverview IP 地理位置服务模块(Infra 层)
3
+ *
4
+ * @module ip-geo/module
5
+ */
6
+ import { Module } from '@nestjs/common';
7
+ import { RedisModule } from '@app/redis';
8
+ import { IpInfoClientModule } from '@app/clients/internal/ip-info';
9
+ import { IpGeoService } from './ip-geo.service';
10
+
11
+ /**
12
+ * IP 地理位置服务模块
13
+ *
14
+ * @description 提供纯 infra 层的 IP 地理位置服务,不依赖 domain 层。
15
+ */
16
+ @Module({
17
+ imports: [RedisModule, IpInfoClientModule],
18
+ providers: [IpGeoService],
19
+ exports: [IpGeoService],
20
+ })
21
+ export class IpGeoModule {}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @fileoverview IP 地理位置服务(Infra 层)
3
+ *
4
+ * 本服务提供纯 infra 层的 IP 地理位置查询功能:
5
+ * - IP 信息查询(via IpInfoClient)
6
+ * - 国家代码查询
7
+ * - 大洲查询(使用静态映射,不依赖数据库)
8
+ *
9
+ * 注意:此服务不依赖 domain 层,可在 infra 层安全使用。
10
+ * 如需完整的 IP 信息服务(含数据库查询),请使用 domain/services/ip-info。
11
+ *
12
+ * @module ip-geo/service
13
+ */
14
+ import { Injectable, Inject } from '@nestjs/common';
15
+ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
16
+ import { Logger } from 'winston';
17
+ import { RedisService } from '@app/redis';
18
+ import { FastifyRequest } from 'fastify';
19
+ import ipUtil from '@/utils/ip.util';
20
+ import validateUtil from '@/utils/validate.util';
21
+ import { PardxApp } from '@/config/dto/config.dto';
22
+ import enviromentUtil from '@/utils/enviroment.util';
23
+ import { getContinentByCountry, Continent } from './continent-mapping';
24
+ import { IpInfoClient, IpInfoResponse } from '@app/clients/internal/ip-info';
25
+
26
+ /**
27
+ * IP 地理位置服务(Infra 层)
28
+ *
29
+ * @description 提供 IP 地理位置查询功能,使用静态大洲映射,不依赖数据库。
30
+ *
31
+ * @class IpGeoService
32
+ */
33
+ @Injectable()
34
+ export class IpGeoService {
35
+ protected ipinfoRedisKey = 'ipinfo';
36
+
37
+ constructor(
38
+ private readonly redis: RedisService,
39
+ private readonly ipInfoClient: IpInfoClient,
40
+ @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
41
+ ) {}
42
+
43
+ /**
44
+ * 从请求中提取 IP 地址
45
+ */
46
+ extractIp(req: FastifyRequest): string {
47
+ return ipUtil.extractIp(req);
48
+ }
49
+
50
+ /**
51
+ * 获取 IP 信息
52
+ */
53
+ async getIpInfo(ip: string): Promise<Partial<PardxApp.IPInfo>> {
54
+ if (enviromentUtil.getBaseZone() === 'cn') {
55
+ return {
56
+ ip,
57
+ country: 'CN',
58
+ region: 'Beijing',
59
+ city: 'Beijing',
60
+ loc: '1.2897,103.8501',
61
+ timezone: 'Asia/Shanghai',
62
+ };
63
+ }
64
+
65
+ if (validateUtil.isBlank(ip) || ip === '127.0.0.1') {
66
+ return {
67
+ ip,
68
+ country: 'SG',
69
+ region: 'Singapore',
70
+ city: 'Singapore',
71
+ loc: '1.2897,103.8501',
72
+ timezone: 'Asia/Singapore',
73
+ };
74
+ }
75
+
76
+ const ipinfo = await this.redis.getData(this.ipinfoRedisKey, ip);
77
+ if (ipinfo) return ipinfo;
78
+
79
+ try {
80
+ const response: IpInfoResponse = await this.ipInfoClient.getIpInfo(ip);
81
+ let ipInfoData: PardxApp.IPInfo = response;
82
+ this.logger.info('IP info:', { ipInfoData });
83
+ await this.redis.saveData(this.ipinfoRedisKey, ip, ipInfoData);
84
+
85
+ if (ipInfoData.country === 'CN') {
86
+ ipInfoData = {
87
+ ip,
88
+ country: 'SG',
89
+ region: 'Singapore',
90
+ city: 'Singapore',
91
+ loc: '1.2897,103.8501',
92
+ timezone: 'Asia/Singapore',
93
+ };
94
+ }
95
+ return ipInfoData;
96
+ } catch (error) {
97
+ this.logger.error('Failed to fetch IP info:', error);
98
+ return {
99
+ ip,
100
+ country: 'SG',
101
+ region: 'Singapore',
102
+ city: 'Singapore',
103
+ loc: '1.2897,103.8501',
104
+ timezone: 'Asia/Singapore',
105
+ };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 获取 IP 对应的国家代码
111
+ */
112
+ async getIpCountry(ip: string): Promise<string> {
113
+ const ipInfo = await this.getIpInfo(ip);
114
+ return ipInfo.country;
115
+ }
116
+
117
+ /**
118
+ * 获取 IP 对应的大洲
119
+ *
120
+ * @description 使用静态映射,不依赖数据库
121
+ */
122
+ async getContinent(ip: string): Promise<Continent> {
123
+ const countryCode = await this.getIpCountry(ip);
124
+ const defaultZone = enviromentUtil.getBaseZone() as Continent;
125
+ return getContinentByCountry(countryCode, defaultZone);
126
+ }
127
+
128
+ /**
129
+ * 获取 IP 对应的时区
130
+ */
131
+ async getTimeZone(ip: string): Promise<string> {
132
+ const ipInfo = await this.getIpInfo(ip);
133
+ return ipInfo.timezone;
134
+ }
135
+ }
@@ -1,2 +1,2 @@
1
1
  export { UploaderModule } from './uploader.module';
2
- export { UploaderService } from './uploader.service';
2
+ export { UploaderService, TokenRequest, SignatureData } from './uploader.service';
@@ -16,7 +16,11 @@ import enviromentUtil from '@/utils/enviroment.util';
16
16
  import fileUtil from '@/utils/file.util';
17
17
  import { FileBucketVendor } from '@prisma/client';
18
18
 
19
- interface SignatureData {
19
+ /**
20
+ * 签名数据接口
21
+ * 从加密签名中解析出的数据结构
22
+ */
23
+ export interface SignatureData {
20
24
  timestamp?: number;
21
25
  filename?: string;
22
26
  fileId?: string;
@@ -25,7 +29,11 @@ interface SignatureData {
25
29
  sha256?: string;
26
30
  }
27
31
 
28
- interface TokenRequest {
32
+ /**
33
+ * 上传令牌请求接口
34
+ * 用于获取上传凭证的请求参数
35
+ */
36
+ export interface TokenRequest {
29
37
  signature?: string;
30
38
  filename?: string;
31
39
  vendor?: FileBucketVendor;
@@ -17,8 +17,6 @@ import { HttpExceptionFilter } from '@/common/filter/exception/http.exception';
17
17
  import { WinstonModule } from 'nest-winston';
18
18
  import * as loggerUtil from '@/utils/logger.util';
19
19
 
20
- /** health module */
21
- import { HealthModule } from './modules/health/health.module';
22
20
  /** uploader module */
23
21
  import { UploaderModule } from './modules/uploader/uploader.module';
24
22
 
@@ -160,16 +158,6 @@ import { DbMetricsService } from '@app/prisma/db-metrics/src/db-metrics.service'
160
158
  };
161
159
  },
162
160
  }),
163
- WinstonModule.forRootAsync({
164
- imports: [ConfigModule],
165
- useFactory: (configService: ConfigService) => {
166
- const output =
167
- configService.get<loggerUtil.LogOutputMode>('app.nestLogOutput') ||
168
- 'file';
169
- return loggerUtil.getWinstonConfig(output);
170
- },
171
- inject: [ConfigService],
172
- }),
173
161
  IpInfoServiceModule,
174
162
  ScheduleModule.forRoot(),
175
163
  RedisModule,
@@ -181,7 +169,6 @@ import { DbMetricsService } from '@app/prisma/db-metrics/src/db-metrics.service'
181
169
  VerifyModule,
182
170
  SystemHealthModule,
183
171
  JwtModule,
184
- HealthModule,
185
172
  UploaderModule,
186
173
  ],
187
174
  providers: [
@@ -73,16 +73,10 @@ export class UploaderController {
73
73
  const userId = req.userId;
74
74
  const ip = ipUtil.extractIp(req);
75
75
 
76
- this.uploaderService.checkValidateAndReturnSignatureData(
77
- userId,
78
- body as any,
79
- );
76
+ // 签名验证 - body 类型由 ts-rest contract 的 Zod schema 推断
77
+ this.uploaderService.checkValidateAndReturnSignatureData(userId, body);
80
78
 
81
- const result = await this.uploaderService.uploadThumbToken(
82
- userId,
83
- body as any,
84
- ip,
85
- );
79
+ const result = await this.uploaderService.uploadThumbToken(userId, body, ip);
86
80
 
87
81
  return success({
88
82
  token: result.token,
@@ -100,11 +94,9 @@ export class UploaderController {
100
94
  const userId = req.userId;
101
95
  const ip = ipUtil.extractIp(req);
102
96
 
97
+ // 签名验证
103
98
  const signatureData =
104
- this.uploaderService.checkValidateAndReturnSignatureData(
105
- userId,
106
- body as any,
107
- );
99
+ this.uploaderService.checkValidateAndReturnSignatureData(userId, body);
108
100
 
109
101
  const vendor =
110
102
  body.vendor ?? (this.appConfig.defaultVendor as FileBucketVendor);
@@ -166,15 +158,10 @@ export class UploaderController {
166
158
  const userId = req.userId;
167
159
  const ip = ipUtil.extractIp(req);
168
160
 
169
- this.uploaderService.checkValidateAndReturnSignatureData(
170
- userId,
171
- body as any,
172
- );
161
+ // 签名验证
162
+ this.uploaderService.checkValidateAndReturnSignatureData(userId, body);
173
163
 
174
- const result = await this.uploaderService.getUploaderPresignedUrl(
175
- body as any,
176
- ip,
177
- );
164
+ const result = await this.uploaderService.getUploaderPresignedUrl(body, ip);
178
165
 
179
166
  const defaultBucket = await this.fileStorageService.getDefaultBucket();
180
167
 
@@ -193,11 +180,9 @@ export class UploaderController {
193
180
  const userId = req.userId;
194
181
  const ip = ipUtil.extractIp(req);
195
182
 
183
+ // 签名验证
196
184
  const signatureData =
197
- this.uploaderService.checkValidateAndReturnSignatureData(
198
- userId,
199
- body as any,
200
- );
185
+ this.uploaderService.checkValidateAndReturnSignatureData(userId, body);
201
186
 
202
187
  const vendor =
203
188
  body.vendor ?? (this.appConfig.defaultVendor as FileBucketVendor);
@@ -8,3 +8,24 @@ export {
8
8
  PageErrorBoundary,
9
9
  ComponentErrorBoundary,
10
10
  } from './error-boundary';
11
+
12
+ export {
13
+ Skeleton,
14
+ CardSkeleton,
15
+ ListItemSkeleton,
16
+ ListSkeleton,
17
+ TableSkeleton,
18
+ FormSkeleton,
19
+ DetailSkeleton,
20
+ AvatarSkeleton,
21
+ PageSkeleton,
22
+ } from './skeletons';
23
+
24
+ export {
25
+ AsyncBoundary,
26
+ CardBoundary,
27
+ ListBoundary,
28
+ PageBoundary,
29
+ withSuspense,
30
+ withAsyncBoundary,
31
+ } from './suspense-utils';