fix: 修复多个服务的 TypeScript 编译错误
- admin-service: 添加 kafkajs 依赖,修复 SystemConfigEntity null vs undefined 类型 - authorization-service: 修复 selfPlantingCount 属性名,修复 AuthorizationRole factory 参数 - identity-service: 修复测试文件 accountSequence 类型(number -> string) - admin-web: 在 authSlice 中添加 refreshToken 支持 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
943fd9efe9
commit
f20643599e
|
|
@ -24,6 +24,7 @@
|
|||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"kafkajs": "^2.2.4",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
|
|
@ -7116,6 +7117,15 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kafkajs": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz",
|
||||
"integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"kafkajs": "^2.2.4",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
|
|
@ -51,7 +52,6 @@
|
|||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
|
|
@ -61,6 +61,7 @@
|
|||
"@types/passport-jwt": "^3.0.13",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@types/unzipper": "^0.10.11",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.42.0",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export const CONFIG_KEYS = {
|
|||
interface SystemConfigResponseDto {
|
||||
key: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
description: string | null;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export interface SystemConfigEntity {
|
|||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
description: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
updatedBy?: string;
|
||||
updatedBy: string | null;
|
||||
}
|
||||
|
||||
export interface ISystemConfigRepository {
|
||||
|
|
|
|||
|
|
@ -2775,8 +2775,8 @@ export class AuthorizationApplicationService {
|
|||
|
||||
// 1. 获取用户认种数据
|
||||
const teamStats = await this.statsRepository.findByAccountSequence(accountSequence)
|
||||
const hasPlanted = (teamStats?.personalPlantingCount || 0) > 0
|
||||
const plantedCount = teamStats?.personalPlantingCount || 0
|
||||
const hasPlanted = (teamStats?.selfPlantingCount || 0) > 0
|
||||
const plantedCount = teamStats?.selfPlantingCount || 0
|
||||
|
||||
// 2. 获取用户已有的授权
|
||||
const authorizations = await this.authorizationRepository.findByAccountSequence(accountSequence)
|
||||
|
|
@ -2812,8 +2812,8 @@ export class AuthorizationApplicationService {
|
|||
|
||||
// 1. 验证用户已认种
|
||||
const teamStats = await this.statsRepository.findByAccountSequence(command.accountSequence)
|
||||
const personalPlantingCount = teamStats?.personalPlantingCount || 0
|
||||
if (personalPlantingCount <= 0) {
|
||||
const selfPlantingCount = teamStats?.selfPlantingCount || 0
|
||||
if (selfPlantingCount <= 0) {
|
||||
throw new ApplicationError('申请授权需要先完成认种')
|
||||
}
|
||||
|
||||
|
|
@ -2891,7 +2891,7 @@ export class AuthorizationApplicationService {
|
|||
authorization.clearDomainEvents()
|
||||
|
||||
return {
|
||||
applicationId: authorization.id,
|
||||
applicationId: authorization.authorizationId.value,
|
||||
status: 'APPROVED',
|
||||
type: SelfApplyAuthorizationType.COMMUNITY,
|
||||
appliedAt: new Date(),
|
||||
|
|
@ -2907,20 +2907,19 @@ export class AuthorizationApplicationService {
|
|||
command: SelfApplyAuthorizationCommand,
|
||||
): Promise<SelfApplyAuthorizationResponseDto> {
|
||||
// 检查是否已有该市的授权市公司
|
||||
const existing = await this.authorizationRepository.findByRegionCodeAndRoleType(
|
||||
RegionCode.create(command.cityCode!),
|
||||
const existingList = await this.authorizationRepository.findActiveByRoleTypeAndRegion(
|
||||
RoleType.AUTH_CITY_COMPANY,
|
||||
RegionCode.create(command.cityCode!),
|
||||
)
|
||||
if (existing && existing.status !== AuthorizationStatus.REVOKED) {
|
||||
if (existingList.length > 0) {
|
||||
throw new ApplicationError('该市已有授权市公司')
|
||||
}
|
||||
|
||||
// 创建授权市公司授权
|
||||
const userId = UserId.create(command.userId, command.accountSequence)
|
||||
const regionCode = RegionCode.create(command.cityCode!)
|
||||
const authorization = AuthorizationRole.createAuthCityCompany({
|
||||
userId,
|
||||
regionCode,
|
||||
cityCode: command.cityCode!,
|
||||
cityName: command.cityName!,
|
||||
})
|
||||
|
||||
|
|
@ -2936,7 +2935,7 @@ export class AuthorizationApplicationService {
|
|||
authorization.clearDomainEvents()
|
||||
|
||||
return {
|
||||
applicationId: authorization.id,
|
||||
applicationId: authorization.authorizationId.value,
|
||||
status: 'APPROVED',
|
||||
type: SelfApplyAuthorizationType.CITY_TEAM,
|
||||
appliedAt: new Date(),
|
||||
|
|
@ -2952,20 +2951,19 @@ export class AuthorizationApplicationService {
|
|||
command: SelfApplyAuthorizationCommand,
|
||||
): Promise<SelfApplyAuthorizationResponseDto> {
|
||||
// 检查是否已有该省的授权省公司
|
||||
const existing = await this.authorizationRepository.findByRegionCodeAndRoleType(
|
||||
RegionCode.create(command.provinceCode!),
|
||||
const existingList = await this.authorizationRepository.findActiveByRoleTypeAndRegion(
|
||||
RoleType.AUTH_PROVINCE_COMPANY,
|
||||
RegionCode.create(command.provinceCode!),
|
||||
)
|
||||
if (existing && existing.status !== AuthorizationStatus.REVOKED) {
|
||||
if (existingList.length > 0) {
|
||||
throw new ApplicationError('该省已有授权省公司')
|
||||
}
|
||||
|
||||
// 创建授权省公司授权
|
||||
const userId = UserId.create(command.userId, command.accountSequence)
|
||||
const regionCode = RegionCode.create(command.provinceCode!)
|
||||
const authorization = AuthorizationRole.createAuthProvinceCompany({
|
||||
userId,
|
||||
regionCode,
|
||||
provinceCode: command.provinceCode!,
|
||||
provinceName: command.provinceName!,
|
||||
})
|
||||
|
||||
|
|
@ -2981,7 +2979,7 @@ export class AuthorizationApplicationService {
|
|||
authorization.clearDomainEvents()
|
||||
|
||||
return {
|
||||
applicationId: authorization.id,
|
||||
applicationId: authorization.authorizationId.value,
|
||||
status: 'APPROVED',
|
||||
type: SelfApplyAuthorizationType.PROVINCE_TEAM,
|
||||
appliedAt: new Date(),
|
||||
|
|
|
|||
|
|
@ -7,12 +7,16 @@ import { AccountSequence, ReferralCode, UserId, AccountStatus, KYCStatus, Device
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
import { ValidateReferralCodeQuery, GetReferralStatsQuery, GenerateReferralLinkCommand } from '@/application/commands';
|
||||
import { ApplicationError } from '@/shared/exceptions/domain.exception';
|
||||
import { AccountSequenceGeneratorService, UserValidatorService, WalletGeneratorService } from '@/domain/services';
|
||||
import { AccountSequenceGeneratorService, UserValidatorService } from '@/domain/services';
|
||||
import { TokenService } from './token.service';
|
||||
import { RedisService } from '@/infrastructure/redis/redis.service';
|
||||
import { SmsService } from '@/infrastructure/external/sms/sms.service';
|
||||
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
||||
import { MpcWalletService } from '@/infrastructure/external/mpc';
|
||||
import { BlockchainClientService } from '@/infrastructure/external/blockchain/blockchain-client.service';
|
||||
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
|
||||
import { BlockchainWalletHandler } from '@/application/event-handlers/blockchain-wallet.handler';
|
||||
import { MpcKeygenCompletedHandler } from '@/application/event-handlers/mpc-keygen-completed.handler';
|
||||
|
||||
describe('UserApplicationService - Referral APIs', () => {
|
||||
let service: UserApplicationService;
|
||||
|
|
@ -21,12 +25,12 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
// Helper function to create a test account using UserAccount.reconstruct
|
||||
const createMockAccount = (params: {
|
||||
userId?: string;
|
||||
accountSequence?: number;
|
||||
accountSequence?: string;
|
||||
referralCode?: string;
|
||||
nickname?: string;
|
||||
avatarUrl?: string | null;
|
||||
isActive?: boolean;
|
||||
inviterSequence?: number | null;
|
||||
inviterSequence?: string | null;
|
||||
registeredAt?: Date;
|
||||
} = {}): UserAccount => {
|
||||
const devices = [
|
||||
|
|
@ -35,7 +39,7 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
|
||||
return UserAccount.reconstruct({
|
||||
userId: params.userId || '123456789',
|
||||
accountSequence: params.accountSequence || 1,
|
||||
accountSequence: params.accountSequence || 'D2412190001',
|
||||
devices,
|
||||
phoneNumber: '13800138000',
|
||||
nickname: params.nickname || '用户1',
|
||||
|
|
@ -90,15 +94,15 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
};
|
||||
|
||||
const mockAccountSequenceGeneratorService = {
|
||||
getNext: jest.fn().mockResolvedValue(AccountSequence.create(1)),
|
||||
getNext: jest.fn().mockResolvedValue(AccountSequence.create('D2412190001')),
|
||||
};
|
||||
|
||||
const mockUserValidatorService = {
|
||||
validateUniquePhone: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWalletGeneratorService = {
|
||||
generateWallets: jest.fn(),
|
||||
const mockBlockchainClientService = {
|
||||
getBalance: jest.fn(),
|
||||
};
|
||||
|
||||
const mockTokenService = {
|
||||
|
|
@ -127,6 +131,18 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
generateMpcWallet: jest.fn(),
|
||||
};
|
||||
|
||||
const mockPrismaService = {
|
||||
userAccount: { findUnique: jest.fn() },
|
||||
};
|
||||
|
||||
const mockBlockchainWalletHandler = {
|
||||
handle: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMpcKeygenCompletedHandler = {
|
||||
handle: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
UserApplicationService,
|
||||
|
|
@ -151,8 +167,8 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
useValue: mockUserValidatorService,
|
||||
},
|
||||
{
|
||||
provide: WalletGeneratorService,
|
||||
useValue: mockWalletGeneratorService,
|
||||
provide: BlockchainClientService,
|
||||
useValue: mockBlockchainClientService,
|
||||
},
|
||||
{
|
||||
provide: TokenService,
|
||||
|
|
@ -174,6 +190,18 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
provide: MpcWalletService,
|
||||
useValue: mockMpcWalletService,
|
||||
},
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: mockPrismaService,
|
||||
},
|
||||
{
|
||||
provide: BlockchainWalletHandler,
|
||||
useValue: mockBlockchainWalletHandler,
|
||||
},
|
||||
{
|
||||
provide: MpcKeygenCompletedHandler,
|
||||
useValue: mockMpcKeygenCompletedHandler,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
|
@ -189,7 +217,7 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
it('should return current user info with referral code and link', async () => {
|
||||
const mockAccount = createMockAccount({
|
||||
userId: '123456789',
|
||||
accountSequence: 1,
|
||||
accountSequence: 'D2412190001',
|
||||
nickname: '测试用户',
|
||||
referralCode: 'ABC123',
|
||||
});
|
||||
|
|
@ -200,7 +228,7 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
|
||||
expect(result).toEqual({
|
||||
userId: '123456789',
|
||||
accountSequence: 1,
|
||||
accountSequence: 'D2412190001',
|
||||
phoneNumber: '138****8000', // masked
|
||||
nickname: '测试用户',
|
||||
avatarUrl: null,
|
||||
|
|
@ -228,7 +256,7 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
describe('validateReferralCode', () => {
|
||||
it('should return valid=true for existing active referral code', async () => {
|
||||
const mockInviter = createMockAccount({
|
||||
accountSequence: 100,
|
||||
accountSequence: 'D2412190100',
|
||||
nickname: '邀请人',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
referralCode: 'INVTE1',
|
||||
|
|
@ -245,7 +273,7 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
valid: true,
|
||||
referralCode: 'INVTE1',
|
||||
inviterInfo: {
|
||||
accountSequence: 100,
|
||||
accountSequence: 'D2412190100',
|
||||
nickname: '邀请人',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
},
|
||||
|
|
@ -406,33 +434,33 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
it('should return referral stats with direct and indirect invites', async () => {
|
||||
const mockAccount = createMockAccount({
|
||||
userId: '123456789',
|
||||
accountSequence: 1,
|
||||
accountSequence: 'D2412190001',
|
||||
referralCode: 'ABC123',
|
||||
});
|
||||
|
||||
// Direct invites (invited by user 1)
|
||||
const directInvite1 = createMockAccount({
|
||||
userId: '200000001',
|
||||
accountSequence: 2,
|
||||
accountSequence: 'D2412190002',
|
||||
nickname: '直接邀请1',
|
||||
inviterSequence: 1,
|
||||
inviterSequence: 'D2412190001',
|
||||
registeredAt: new Date(),
|
||||
});
|
||||
|
||||
const directInvite2 = createMockAccount({
|
||||
userId: '200000002',
|
||||
accountSequence: 3,
|
||||
accountSequence: 'D2412190003',
|
||||
nickname: '直接邀请2',
|
||||
inviterSequence: 1,
|
||||
inviterSequence: 'D2412190001',
|
||||
registeredAt: new Date(),
|
||||
});
|
||||
|
||||
// Indirect invite (invited by user 2, who was invited by user 1)
|
||||
const indirectInvite1 = createMockAccount({
|
||||
userId: '300000001',
|
||||
accountSequence: 4,
|
||||
accountSequence: 'D2412190004',
|
||||
nickname: '间接邀请1',
|
||||
inviterSequence: 2,
|
||||
inviterSequence: 'D2412190002',
|
||||
registeredAt: new Date(),
|
||||
});
|
||||
|
||||
|
|
@ -456,7 +484,7 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
thisMonthInvites: expect.any(Number),
|
||||
recentInvites: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
accountSequence: expect.any(Number),
|
||||
accountSequence: expect.any(String),
|
||||
nickname: expect.any(String),
|
||||
level: expect.any(Number), // 1 for direct, 2 for indirect
|
||||
}),
|
||||
|
|
@ -469,7 +497,7 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
it('should return empty stats when no invites', async () => {
|
||||
const mockAccount = createMockAccount({
|
||||
userId: '123456789',
|
||||
accountSequence: 1,
|
||||
accountSequence: 'D2412190001',
|
||||
referralCode: 'ABC123',
|
||||
});
|
||||
|
||||
|
|
@ -495,29 +523,29 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
it('should correctly calculate time-based stats', async () => {
|
||||
const mockAccount = createMockAccount({
|
||||
userId: '123456789',
|
||||
accountSequence: 1,
|
||||
accountSequence: 'D2412190001',
|
||||
referralCode: 'ABC123',
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
const todayInvite = createMockAccount({
|
||||
accountSequence: 2,
|
||||
accountSequence: 'D2412190002',
|
||||
nickname: '今日邀请',
|
||||
inviterSequence: 1,
|
||||
inviterSequence: 'D2412190001',
|
||||
registeredAt: now,
|
||||
});
|
||||
|
||||
const yesterdayInvite = createMockAccount({
|
||||
accountSequence: 3,
|
||||
accountSequence: 'D2412190003',
|
||||
nickname: '昨日邀请',
|
||||
inviterSequence: 1,
|
||||
inviterSequence: 'D2412190001',
|
||||
registeredAt: new Date(now.getTime() - 24 * 60 * 60 * 1000), // yesterday
|
||||
});
|
||||
|
||||
const lastMonthInvite = createMockAccount({
|
||||
accountSequence: 4,
|
||||
accountSequence: 'D2412190004',
|
||||
nickname: '上月邀请',
|
||||
inviterSequence: 1,
|
||||
inviterSequence: 'D2412190001',
|
||||
registeredAt: new Date(now.getFullYear(), now.getMonth() - 1, 15),
|
||||
});
|
||||
|
||||
|
|
@ -546,22 +574,22 @@ describe('UserApplicationService - Referral APIs', () => {
|
|||
it('should sort recent invites by registration date (newest first)', async () => {
|
||||
const mockAccount = createMockAccount({
|
||||
userId: '123456789',
|
||||
accountSequence: 1,
|
||||
accountSequence: 'D2412190001',
|
||||
referralCode: 'ABC123',
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
const oldInvite = createMockAccount({
|
||||
accountSequence: 2,
|
||||
accountSequence: 'D2412190002',
|
||||
nickname: '旧邀请',
|
||||
inviterSequence: 1,
|
||||
inviterSequence: 'D2412190001',
|
||||
registeredAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
|
||||
});
|
||||
|
||||
const newInvite = createMockAccount({
|
||||
accountSequence: 3,
|
||||
accountSequence: 'D2412190003',
|
||||
nickname: '新邀请',
|
||||
inviterSequence: 1,
|
||||
inviterSequence: 'D2412190001',
|
||||
registeredAt: now,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -65,10 +65,17 @@ describe('Identity Service E2E Tests', () => {
|
|||
|
||||
// 初始化账户序列号生成器
|
||||
await prisma.accountSequenceGenerator.deleteMany();
|
||||
const today = new Date();
|
||||
const year = String(today.getFullYear()).slice(-2);
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(today.getDate()).padStart(2, '0');
|
||||
const dateKey = `${year}${month}${day}`;
|
||||
|
||||
await prisma.accountSequenceGenerator.create({
|
||||
data: {
|
||||
id: 1,
|
||||
currentSequence: BigInt(100000),
|
||||
dateKey: dateKey,
|
||||
currentSequence: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { User } from '@/types/user.types';
|
|||
interface AuthState {
|
||||
user: User | null;
|
||||
token: string | null;
|
||||
refreshToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
permissions: string[];
|
||||
loading: boolean;
|
||||
|
|
@ -12,6 +13,7 @@ interface AuthState {
|
|||
const initialState: AuthState = {
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
permissions: [],
|
||||
loading: true,
|
||||
|
|
@ -23,10 +25,11 @@ const authSlice = createSlice({
|
|||
reducers: {
|
||||
setCredentials: (
|
||||
state,
|
||||
action: PayloadAction<{ user: User; token: string; permissions?: string[] }>
|
||||
action: PayloadAction<{ user: User; token: string; refreshToken?: string; permissions?: string[] }>
|
||||
) => {
|
||||
state.user = action.payload.user;
|
||||
state.token = action.payload.token;
|
||||
state.refreshToken = action.payload.refreshToken || null;
|
||||
state.permissions = action.payload.permissions || [];
|
||||
state.isAuthenticated = true;
|
||||
state.loading = false;
|
||||
|
|
@ -34,6 +37,7 @@ const authSlice = createSlice({
|
|||
logout: (state) => {
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
state.refreshToken = null;
|
||||
state.permissions = [];
|
||||
state.isAuthenticated = false;
|
||||
state.loading = false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue