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