refactor(identity): remove province/city/address fields
- Remove provinceCode, cityCode, address from UserAccount aggregate - Remove ProvinceCode, CityCode value objects - Remove UserLocationUpdatedEvent domain event - Update Prisma schema to drop province/city/address columns - Update repository, mapper, handlers, services and DTOs - Clean up tests and factory files Province/city should belong to adoption-service as transaction data, not identity-service user data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fbec0b9112
commit
2705812826
|
|
@ -30,7 +30,13 @@
|
||||||
"Bash(cd:*)",
|
"Bash(cd:*)",
|
||||||
"Bash(curl:*)",
|
"Bash(curl:*)",
|
||||||
"Bash(git revert:*)",
|
"Bash(git revert:*)",
|
||||||
"Bash(del \"c:\\Users\\dong\\Desktop\\rwadurian\\backend\\services\\identity-service\\src\\infrastructure\\persistence\\entities\\user-device.entity.ts\")"
|
"Bash(del \"c:\\Users\\dong\\Desktop\\rwadurian\\backend\\services\\identity-service\\src\\infrastructure\\persistence\\entities\\user-device.entity.ts\")",
|
||||||
|
"Bash(cmd /c \"cd /d c:\\Users\\dong\\Desktop\\rwadurian\\backend\\services\\identity-service && npm run build\")",
|
||||||
|
"Bash(cmd /c \"cd /d c:\\Users\\dong\\Desktop\\rwadurian\\backend\\services\\identity-service && npx nest build\")",
|
||||||
|
"Bash(cmd /c \"cd /d c:\\Users\\dong\\Desktop\\rwadurian && git add -A && git status\")",
|
||||||
|
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" status)",
|
||||||
|
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" add -A)",
|
||||||
|
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" commit -m \"$(cat <<''EOF''\nrefactor(identity): remove province/city/address fields\n\n- Remove provinceCode, cityCode, address from UserAccount aggregate\n- Remove ProvinceCode, CityCode value objects\n- Remove UserLocationUpdatedEvent domain event\n- Update Prisma schema to drop province/city/address columns\n- Update repository, mapper, handlers, services and DTOs\n- Clean up tests and factory files\n\nProvince/city should belong to adoption-service as transaction data,\nnot identity-service user data.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
|
|
@ -10,39 +10,34 @@ datasource db {
|
||||||
model UserAccount {
|
model UserAccount {
|
||||||
userId BigInt @id @default(autoincrement()) @map("user_id")
|
userId BigInt @id @default(autoincrement()) @map("user_id")
|
||||||
accountSequence BigInt @unique @map("account_sequence")
|
accountSequence BigInt @unique @map("account_sequence")
|
||||||
|
|
||||||
phoneNumber String? @unique @map("phone_number") @db.VarChar(20)
|
phoneNumber String? @unique @map("phone_number") @db.VarChar(20)
|
||||||
nickname String @db.VarChar(100)
|
nickname String @db.VarChar(100)
|
||||||
avatarUrl String? @map("avatar_url") @db.Text
|
avatarUrl String? @map("avatar_url") @db.Text
|
||||||
|
|
||||||
inviterSequence BigInt? @map("inviter_sequence")
|
inviterSequence BigInt? @map("inviter_sequence")
|
||||||
referralCode String @unique @map("referral_code") @db.VarChar(10)
|
referralCode String @unique @map("referral_code") @db.VarChar(10)
|
||||||
|
|
||||||
provinceCode String @map("province_code") @db.VarChar(10)
|
|
||||||
cityCode String @map("city_code") @db.VarChar(10)
|
|
||||||
address String? @db.VarChar(500)
|
|
||||||
|
|
||||||
kycStatus String @default("NOT_VERIFIED") @map("kyc_status") @db.VarChar(20)
|
kycStatus String @default("NOT_VERIFIED") @map("kyc_status") @db.VarChar(20)
|
||||||
realName String? @map("real_name") @db.VarChar(100)
|
realName String? @map("real_name") @db.VarChar(100)
|
||||||
idCardNumber String? @map("id_card_number") @db.VarChar(20)
|
idCardNumber String? @map("id_card_number") @db.VarChar(20)
|
||||||
idCardFrontUrl String? @map("id_card_front_url") @db.VarChar(500)
|
idCardFrontUrl String? @map("id_card_front_url") @db.VarChar(500)
|
||||||
idCardBackUrl String? @map("id_card_back_url") @db.VarChar(500)
|
idCardBackUrl String? @map("id_card_back_url") @db.VarChar(500)
|
||||||
kycVerifiedAt DateTime? @map("kyc_verified_at")
|
kycVerifiedAt DateTime? @map("kyc_verified_at")
|
||||||
|
|
||||||
status String @default("ACTIVE") @db.VarChar(20)
|
status String @default("ACTIVE") @db.VarChar(20)
|
||||||
|
|
||||||
registeredAt DateTime @default(now()) @map("registered_at")
|
registeredAt DateTime @default(now()) @map("registered_at")
|
||||||
lastLoginAt DateTime? @map("last_login_at")
|
lastLoginAt DateTime? @map("last_login_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
devices UserDevice[]
|
devices UserDevice[]
|
||||||
walletAddresses WalletAddress[]
|
walletAddresses WalletAddress[]
|
||||||
|
|
||||||
@@index([phoneNumber], name: "idx_phone")
|
@@index([phoneNumber], name: "idx_phone")
|
||||||
@@index([accountSequence], name: "idx_sequence")
|
@@index([accountSequence], name: "idx_sequence")
|
||||||
@@index([referralCode], name: "idx_referral_code")
|
@@index([referralCode], name: "idx_referral_code")
|
||||||
@@index([inviterSequence], name: "idx_inviter")
|
@@index([inviterSequence], name: "idx_inviter")
|
||||||
@@index([provinceCode, cityCode], name: "idx_province_city")
|
|
||||||
@@index([kycStatus], name: "idx_kyc_status")
|
@@index([kycStatus], name: "idx_kyc_status")
|
||||||
@@index([status], name: "idx_status")
|
@@index([status], name: "idx_status")
|
||||||
@@map("user_accounts")
|
@@map("user_accounts")
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export class UserAccountController {
|
||||||
return this.userService.register(
|
return this.userService.register(
|
||||||
new RegisterCommand(
|
new RegisterCommand(
|
||||||
dto.phoneNumber, dto.smsCode, dto.deviceId,
|
dto.phoneNumber, dto.smsCode, dto.deviceId,
|
||||||
dto.provinceCode, dto.cityCode, dto.deviceName, dto.inviterReferralCode,
|
dto.deviceName, dto.inviterReferralCode,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,16 +47,6 @@ export class RegisterDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
provinceCode: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
cityCode: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,6 @@ export class UserProfileDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
province: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
city: string;
|
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
|
||||||
address: string | null;
|
|
||||||
|
|
||||||
@ApiProperty({ type: [WalletAddressDto] })
|
@ApiProperty({ type: [WalletAddressDto] })
|
||||||
walletAddresses: WalletAddressDto[];
|
walletAddresses: WalletAddressDto[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { AutoCreateAccountCommand } from './auto-create-account.command';
|
||||||
import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface';
|
import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface';
|
||||||
import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate';
|
import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate';
|
||||||
import { AccountSequenceGeneratorService, UserValidatorService } from '@/domain/services';
|
import { AccountSequenceGeneratorService, UserValidatorService } from '@/domain/services';
|
||||||
import { ReferralCode, AccountSequence, ProvinceCode, CityCode } from '@/domain/value-objects';
|
import { ReferralCode, AccountSequence } from '@/domain/value-objects';
|
||||||
import { TokenService } from '@/application/services/token.service';
|
import { TokenService } from '@/application/services/token.service';
|
||||||
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
||||||
import { ApplicationError } from '@/shared/exceptions/domain.exception';
|
import { ApplicationError } from '@/shared/exceptions/domain.exception';
|
||||||
|
|
@ -63,8 +63,6 @@ export class AutoCreateAccountHandler {
|
||||||
deviceName: deviceNameStr,
|
deviceName: deviceNameStr,
|
||||||
deviceInfo: command.deviceName, // 100% 保持原样存储
|
deviceInfo: command.deviceName, // 100% 保持原样存储
|
||||||
inviterSequence,
|
inviterSequence,
|
||||||
province: ProvinceCode.create('DEFAULT'),
|
|
||||||
city: CityCode.create('DEFAULT'),
|
|
||||||
nickname: identity.username,
|
nickname: identity.username,
|
||||||
avatarSvg: identity.avatarSvg,
|
avatarSvg: identity.avatarSvg,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,6 @@ export class RegisterCommand {
|
||||||
public readonly phoneNumber: string,
|
public readonly phoneNumber: string,
|
||||||
public readonly smsCode: string,
|
public readonly smsCode: string,
|
||||||
public readonly deviceId: string,
|
public readonly deviceId: string,
|
||||||
public readonly provinceCode: string,
|
|
||||||
public readonly cityCode: string,
|
|
||||||
public readonly deviceName?: string,
|
public readonly deviceName?: string,
|
||||||
public readonly inviterReferralCode?: string,
|
public readonly inviterReferralCode?: string,
|
||||||
) {}
|
) {}
|
||||||
|
|
@ -75,7 +73,6 @@ export class UpdateProfileCommand {
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly nickname?: string,
|
public readonly nickname?: string,
|
||||||
public readonly avatarUrl?: string,
|
public readonly avatarUrl?: string,
|
||||||
public readonly address?: string,
|
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,9 +216,6 @@ export interface UserProfileDTO {
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
province: string;
|
|
||||||
city: string;
|
|
||||||
address: string | null;
|
|
||||||
walletAddresses: Array<{ chainType: string; address: string }>;
|
walletAddresses: Array<{ chainType: string; address: string }>;
|
||||||
kycStatus: string;
|
kycStatus: string;
|
||||||
kycInfo: { realName: string; idCardNumber: string } | null;
|
kycInfo: { realName: string; idCardNumber: string } | null;
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,6 @@ export class GetMyProfileHandler {
|
||||||
nickname: account.nickname,
|
nickname: account.nickname,
|
||||||
avatarUrl: account.avatarUrl,
|
avatarUrl: account.avatarUrl,
|
||||||
referralCode: account.referralCode.value,
|
referralCode: account.referralCode.value,
|
||||||
province: account.province.value,
|
|
||||||
city: account.city.value,
|
|
||||||
address: account.addressDetail,
|
|
||||||
walletAddresses: account.getAllWalletAddresses().map((wa) => ({
|
walletAddresses: account.getAllWalletAddresses().map((wa) => ({
|
||||||
chainType: wa.chainType,
|
chainType: wa.chainType,
|
||||||
address: wa.address,
|
address: wa.address,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { UserApplicationService } from './user-application.service';
|
||||||
import { USER_ACCOUNT_REPOSITORY, UserAccountRepository, ReferralLinkData, CreateReferralLinkParams } from '@/domain/repositories/user-account.repository.interface';
|
import { USER_ACCOUNT_REPOSITORY, UserAccountRepository, ReferralLinkData, CreateReferralLinkParams } from '@/domain/repositories/user-account.repository.interface';
|
||||||
import { MPC_KEY_SHARE_REPOSITORY, MpcKeyShareRepository } from '@/domain/repositories/mpc-key-share.repository.interface';
|
import { MPC_KEY_SHARE_REPOSITORY, MpcKeyShareRepository } from '@/domain/repositories/mpc-key-share.repository.interface';
|
||||||
import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate';
|
import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate';
|
||||||
import { AccountSequence, ReferralCode, UserId, ProvinceCode, CityCode, AccountStatus, KYCStatus, DeviceInfo } from '@/domain/value-objects';
|
import { AccountSequence, ReferralCode, UserId, AccountStatus, KYCStatus, DeviceInfo } from '@/domain/value-objects';
|
||||||
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';
|
||||||
|
|
@ -42,9 +42,6 @@ describe('UserApplicationService - Referral APIs', () => {
|
||||||
avatarUrl: params.avatarUrl ?? null,
|
avatarUrl: params.avatarUrl ?? null,
|
||||||
inviterSequence: params.inviterSequence ?? null,
|
inviterSequence: params.inviterSequence ?? null,
|
||||||
referralCode: params.referralCode || 'ABC123',
|
referralCode: params.referralCode || 'ABC123',
|
||||||
province: '110000',
|
|
||||||
city: '110100',
|
|
||||||
address: null,
|
|
||||||
walletAddresses: [],
|
walletAddresses: [],
|
||||||
kycInfo: null,
|
kycInfo: null,
|
||||||
kycStatus: KYCStatus.NOT_VERIFIED,
|
kycStatus: KYCStatus.NOT_VERIFIED,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
AccountSequenceGeneratorService, UserValidatorService,
|
AccountSequenceGeneratorService, UserValidatorService,
|
||||||
} from '@/domain/services';
|
} from '@/domain/services';
|
||||||
import {
|
import {
|
||||||
UserId, PhoneNumber, ReferralCode, AccountSequence, ProvinceCode, CityCode,
|
UserId, PhoneNumber, ReferralCode, AccountSequence,
|
||||||
ChainType, KYCInfo,
|
ChainType, KYCInfo,
|
||||||
} from '@/domain/value-objects';
|
} from '@/domain/value-objects';
|
||||||
import { TokenService } from './token.service';
|
import { TokenService } from './token.service';
|
||||||
|
|
@ -106,8 +106,6 @@ export class UserApplicationService {
|
||||||
deviceName: deviceNameStr,
|
deviceName: deviceNameStr,
|
||||||
deviceInfo: command.deviceName, // 100% 保持原样存储
|
deviceInfo: command.deviceName, // 100% 保持原样存储
|
||||||
inviterSequence,
|
inviterSequence,
|
||||||
province: ProvinceCode.create('DEFAULT'),
|
|
||||||
city: CityCode.create('DEFAULT'),
|
|
||||||
nickname: identity.username,
|
nickname: identity.username,
|
||||||
avatarSvg: identity.avatarSvg,
|
avatarSvg: identity.avatarSvg,
|
||||||
});
|
});
|
||||||
|
|
@ -298,8 +296,6 @@ export class UserApplicationService {
|
||||||
initialDeviceId: command.deviceId,
|
initialDeviceId: command.deviceId,
|
||||||
deviceName: command.deviceName,
|
deviceName: command.deviceName,
|
||||||
inviterSequence,
|
inviterSequence,
|
||||||
province: ProvinceCode.create(command.provinceCode),
|
|
||||||
city: CityCode.create(command.cityCode),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.userRepository.save(account);
|
await this.userRepository.save(account);
|
||||||
|
|
@ -466,9 +462,6 @@ export class UserApplicationService {
|
||||||
nickname: account.nickname,
|
nickname: account.nickname,
|
||||||
avatarUrl: account.avatarUrl,
|
avatarUrl: account.avatarUrl,
|
||||||
referralCode: account.referralCode.value,
|
referralCode: account.referralCode.value,
|
||||||
province: account.province.value,
|
|
||||||
city: account.city.value,
|
|
||||||
address: account.addressDetail,
|
|
||||||
walletAddresses: account.getAllWalletAddresses().map((wa) => ({
|
walletAddresses: account.getAllWalletAddresses().map((wa) => ({
|
||||||
chainType: wa.chainType,
|
chainType: wa.chainType,
|
||||||
address: wa.address,
|
address: wa.address,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { DomainError } from '@/shared/exceptions/domain.exception';
|
import { DomainError } from '@/shared/exceptions/domain.exception';
|
||||||
import {
|
import {
|
||||||
UserId, AccountSequence, PhoneNumber, ReferralCode, ProvinceCode, CityCode,
|
UserId, AccountSequence, PhoneNumber, ReferralCode,
|
||||||
DeviceInfo, ChainType, KYCInfo, KYCStatus, AccountStatus,
|
DeviceInfo, ChainType, KYCInfo, KYCStatus, AccountStatus,
|
||||||
} from '@/domain/value-objects';
|
} from '@/domain/value-objects';
|
||||||
import { WalletAddress } from '@/domain/entities/wallet-address.entity';
|
import { WalletAddress } from '@/domain/entities/wallet-address.entity';
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
DeviceAddedEvent, DeviceRemovedEvent, PhoneNumberBoundEvent,
|
DeviceAddedEvent, DeviceRemovedEvent, PhoneNumberBoundEvent,
|
||||||
WalletAddressBoundEvent, MultipleWalletAddressesBoundEvent,
|
WalletAddressBoundEvent, MultipleWalletAddressesBoundEvent,
|
||||||
KYCSubmittedEvent, KYCVerifiedEvent, KYCRejectedEvent,
|
KYCSubmittedEvent, KYCVerifiedEvent, KYCRejectedEvent,
|
||||||
UserLocationUpdatedEvent, UserAccountFrozenEvent, UserAccountDeactivatedEvent,
|
UserAccountFrozenEvent, UserAccountDeactivatedEvent,
|
||||||
} from '@/domain/events';
|
} from '@/domain/events';
|
||||||
|
|
||||||
export class UserAccount {
|
export class UserAccount {
|
||||||
|
|
@ -21,9 +21,6 @@ export class UserAccount {
|
||||||
private _avatarUrl: string | null;
|
private _avatarUrl: string | null;
|
||||||
private readonly _inviterSequence: AccountSequence | null;
|
private readonly _inviterSequence: AccountSequence | null;
|
||||||
private readonly _referralCode: ReferralCode;
|
private readonly _referralCode: ReferralCode;
|
||||||
private _province: ProvinceCode;
|
|
||||||
private _city: CityCode;
|
|
||||||
private _address: string | null;
|
|
||||||
private _walletAddresses: Map<ChainType, WalletAddress>;
|
private _walletAddresses: Map<ChainType, WalletAddress>;
|
||||||
private _kycInfo: KYCInfo | null;
|
private _kycInfo: KYCInfo | null;
|
||||||
private _kycStatus: KYCStatus;
|
private _kycStatus: KYCStatus;
|
||||||
|
|
@ -41,9 +38,6 @@ export class UserAccount {
|
||||||
get avatarUrl(): string | null { return this._avatarUrl; }
|
get avatarUrl(): string | null { return this._avatarUrl; }
|
||||||
get inviterSequence(): AccountSequence | null { return this._inviterSequence; }
|
get inviterSequence(): AccountSequence | null { return this._inviterSequence; }
|
||||||
get referralCode(): ReferralCode { return this._referralCode; }
|
get referralCode(): ReferralCode { return this._referralCode; }
|
||||||
get province(): ProvinceCode { return this._province; }
|
|
||||||
get city(): CityCode { return this._city; }
|
|
||||||
get addressDetail(): string | null { return this._address; }
|
|
||||||
get kycInfo(): KYCInfo | null { return this._kycInfo; }
|
get kycInfo(): KYCInfo | null { return this._kycInfo; }
|
||||||
get kycStatus(): KYCStatus { return this._kycStatus; }
|
get kycStatus(): KYCStatus { return this._kycStatus; }
|
||||||
get status(): AccountStatus { return this._status; }
|
get status(): AccountStatus { return this._status; }
|
||||||
|
|
@ -58,7 +52,6 @@ export class UserAccount {
|
||||||
userId: UserId, accountSequence: AccountSequence, devices: Map<string, DeviceInfo>,
|
userId: UserId, accountSequence: AccountSequence, devices: Map<string, DeviceInfo>,
|
||||||
phoneNumber: PhoneNumber | null, nickname: string, avatarUrl: string | null,
|
phoneNumber: PhoneNumber | null, nickname: string, avatarUrl: string | null,
|
||||||
inviterSequence: AccountSequence | null, referralCode: ReferralCode,
|
inviterSequence: AccountSequence | null, referralCode: ReferralCode,
|
||||||
province: ProvinceCode, city: CityCode, address: string | null,
|
|
||||||
walletAddresses: Map<ChainType, WalletAddress>, kycInfo: KYCInfo | null,
|
walletAddresses: Map<ChainType, WalletAddress>, kycInfo: KYCInfo | null,
|
||||||
kycStatus: KYCStatus, status: AccountStatus, registeredAt: Date,
|
kycStatus: KYCStatus, status: AccountStatus, registeredAt: Date,
|
||||||
lastLoginAt: Date | null, updatedAt: Date,
|
lastLoginAt: Date | null, updatedAt: Date,
|
||||||
|
|
@ -71,9 +64,6 @@ export class UserAccount {
|
||||||
this._avatarUrl = avatarUrl;
|
this._avatarUrl = avatarUrl;
|
||||||
this._inviterSequence = inviterSequence;
|
this._inviterSequence = inviterSequence;
|
||||||
this._referralCode = referralCode;
|
this._referralCode = referralCode;
|
||||||
this._province = province;
|
|
||||||
this._city = city;
|
|
||||||
this._address = address;
|
|
||||||
this._walletAddresses = walletAddresses;
|
this._walletAddresses = walletAddresses;
|
||||||
this._kycInfo = kycInfo;
|
this._kycInfo = kycInfo;
|
||||||
this._kycStatus = kycStatus;
|
this._kycStatus = kycStatus;
|
||||||
|
|
@ -89,8 +79,6 @@ export class UserAccount {
|
||||||
deviceName?: string;
|
deviceName?: string;
|
||||||
deviceInfo?: Record<string, unknown>; // 完整的设备信息 JSON
|
deviceInfo?: Record<string, unknown>; // 完整的设备信息 JSON
|
||||||
inviterSequence: AccountSequence | null;
|
inviterSequence: AccountSequence | null;
|
||||||
province: ProvinceCode;
|
|
||||||
city: CityCode;
|
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
avatarSvg?: string;
|
avatarSvg?: string;
|
||||||
}): UserAccount {
|
}): UserAccount {
|
||||||
|
|
@ -107,7 +95,7 @@ export class UserAccount {
|
||||||
const account = new UserAccount(
|
const account = new UserAccount(
|
||||||
UserId.create(0), params.accountSequence, devices, null,
|
UserId.create(0), params.accountSequence, devices, null,
|
||||||
nickname, avatarUrl, params.inviterSequence,
|
nickname, avatarUrl, params.inviterSequence,
|
||||||
ReferralCode.generate(), params.province, params.city, null,
|
ReferralCode.generate(),
|
||||||
new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE,
|
new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE,
|
||||||
new Date(), null, new Date(),
|
new Date(), null, new Date(),
|
||||||
);
|
);
|
||||||
|
|
@ -117,8 +105,6 @@ export class UserAccount {
|
||||||
accountSequence: params.accountSequence.value,
|
accountSequence: params.accountSequence.value,
|
||||||
initialDeviceId: params.initialDeviceId,
|
initialDeviceId: params.initialDeviceId,
|
||||||
inviterSequence: params.inviterSequence?.value || null,
|
inviterSequence: params.inviterSequence?.value || null,
|
||||||
province: params.province.value,
|
|
||||||
city: params.city.value,
|
|
||||||
registeredAt: account._registeredAt,
|
registeredAt: account._registeredAt,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -132,8 +118,6 @@ export class UserAccount {
|
||||||
deviceName?: string;
|
deviceName?: string;
|
||||||
deviceInfo?: Record<string, unknown>; // 完整的设备信息 JSON
|
deviceInfo?: Record<string, unknown>; // 完整的设备信息 JSON
|
||||||
inviterSequence: AccountSequence | null;
|
inviterSequence: AccountSequence | null;
|
||||||
province: ProvinceCode;
|
|
||||||
city: CityCode;
|
|
||||||
}): UserAccount {
|
}): UserAccount {
|
||||||
const devices = new Map<string, DeviceInfo>();
|
const devices = new Map<string, DeviceInfo>();
|
||||||
devices.set(params.initialDeviceId, new DeviceInfo(
|
devices.set(params.initialDeviceId, new DeviceInfo(
|
||||||
|
|
@ -145,7 +129,7 @@ export class UserAccount {
|
||||||
const account = new UserAccount(
|
const account = new UserAccount(
|
||||||
UserId.create(0), params.accountSequence, devices, params.phoneNumber,
|
UserId.create(0), params.accountSequence, devices, params.phoneNumber,
|
||||||
`用户${params.accountSequence.value}`, null, params.inviterSequence,
|
`用户${params.accountSequence.value}`, null, params.inviterSequence,
|
||||||
ReferralCode.generate(), params.province, params.city, null,
|
ReferralCode.generate(),
|
||||||
new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE,
|
new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE,
|
||||||
new Date(), null, new Date(),
|
new Date(), null, new Date(),
|
||||||
);
|
);
|
||||||
|
|
@ -156,8 +140,6 @@ export class UserAccount {
|
||||||
phoneNumber: params.phoneNumber.value,
|
phoneNumber: params.phoneNumber.value,
|
||||||
initialDeviceId: params.initialDeviceId,
|
initialDeviceId: params.initialDeviceId,
|
||||||
inviterSequence: params.inviterSequence?.value || null,
|
inviterSequence: params.inviterSequence?.value || null,
|
||||||
province: params.province.value,
|
|
||||||
city: params.city.value,
|
|
||||||
registeredAt: account._registeredAt,
|
registeredAt: account._registeredAt,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -168,7 +150,6 @@ export class UserAccount {
|
||||||
userId: string; accountSequence: number; devices: DeviceInfo[];
|
userId: string; accountSequence: number; devices: DeviceInfo[];
|
||||||
phoneNumber: string | null; nickname: string; avatarUrl: string | null;
|
phoneNumber: string | null; nickname: string; avatarUrl: string | null;
|
||||||
inviterSequence: number | null; referralCode: string;
|
inviterSequence: number | null; referralCode: string;
|
||||||
province: string; city: string; address: string | null;
|
|
||||||
walletAddresses: WalletAddress[]; kycInfo: KYCInfo | null;
|
walletAddresses: WalletAddress[]; kycInfo: KYCInfo | null;
|
||||||
kycStatus: KYCStatus; status: AccountStatus;
|
kycStatus: KYCStatus; status: AccountStatus;
|
||||||
registeredAt: Date; lastLoginAt: Date | null; updatedAt: Date;
|
registeredAt: Date; lastLoginAt: Date | null; updatedAt: Date;
|
||||||
|
|
@ -188,9 +169,6 @@ export class UserAccount {
|
||||||
params.avatarUrl,
|
params.avatarUrl,
|
||||||
params.inviterSequence ? AccountSequence.create(params.inviterSequence) : null,
|
params.inviterSequence ? AccountSequence.create(params.inviterSequence) : null,
|
||||||
ReferralCode.create(params.referralCode),
|
ReferralCode.create(params.referralCode),
|
||||||
ProvinceCode.create(params.province),
|
|
||||||
CityCode.create(params.city),
|
|
||||||
params.address,
|
|
||||||
walletMap,
|
walletMap,
|
||||||
params.kycInfo,
|
params.kycInfo,
|
||||||
params.kycStatus,
|
params.kycStatus,
|
||||||
|
|
@ -243,24 +221,13 @@ export class UserAccount {
|
||||||
return Array.from(this._devices.values());
|
return Array.from(this._devices.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProfile(params: { nickname?: string; avatarUrl?: string; address?: string }): void {
|
updateProfile(params: { nickname?: string; avatarUrl?: string }): void {
|
||||||
this.ensureActive();
|
this.ensureActive();
|
||||||
if (params.nickname) this._nickname = params.nickname;
|
if (params.nickname) this._nickname = params.nickname;
|
||||||
if (params.avatarUrl !== undefined) this._avatarUrl = params.avatarUrl;
|
if (params.avatarUrl !== undefined) this._avatarUrl = params.avatarUrl;
|
||||||
if (params.address !== undefined) this._address = params.address;
|
|
||||||
this._updatedAt = new Date();
|
this._updatedAt = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLocation(province: ProvinceCode, city: CityCode): void {
|
|
||||||
this.ensureActive();
|
|
||||||
this._province = province;
|
|
||||||
this._city = city;
|
|
||||||
this._updatedAt = new Date();
|
|
||||||
this.addDomainEvent(new UserLocationUpdatedEvent({
|
|
||||||
userId: this.userId.toString(), province: province.value, city: city.value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
bindPhoneNumber(phoneNumber: PhoneNumber): void {
|
bindPhoneNumber(phoneNumber: PhoneNumber): void {
|
||||||
this.ensureActive();
|
this.ensureActive();
|
||||||
if (this._phoneNumber) throw new DomainError('已绑定手机号,不可重复绑定');
|
if (this._phoneNumber) throw new DomainError('已绑定手机号,不可重复绑定');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UserAccount } from './user-account.aggregate';
|
import { UserAccount } from './user-account.aggregate';
|
||||||
import { AccountSequence, PhoneNumber, ProvinceCode, CityCode } from '@/domain/value-objects';
|
import { AccountSequence, PhoneNumber } from '@/domain/value-objects';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserAccountFactory {
|
export class UserAccountFactory {
|
||||||
|
|
@ -9,8 +9,6 @@ export class UserAccountFactory {
|
||||||
initialDeviceId: string;
|
initialDeviceId: string;
|
||||||
deviceName?: string;
|
deviceName?: string;
|
||||||
inviterSequence: AccountSequence | null;
|
inviterSequence: AccountSequence | null;
|
||||||
province: ProvinceCode;
|
|
||||||
city: CityCode;
|
|
||||||
}): UserAccount {
|
}): UserAccount {
|
||||||
return UserAccount.createAutomatic(params);
|
return UserAccount.createAutomatic(params);
|
||||||
}
|
}
|
||||||
|
|
@ -21,8 +19,6 @@ export class UserAccountFactory {
|
||||||
initialDeviceId: string;
|
initialDeviceId: string;
|
||||||
deviceName?: string;
|
deviceName?: string;
|
||||||
inviterSequence: AccountSequence | null;
|
inviterSequence: AccountSequence | null;
|
||||||
province: ProvinceCode;
|
|
||||||
city: CityCode;
|
|
||||||
}): UserAccount {
|
}): UserAccount {
|
||||||
return UserAccount.create(params);
|
return UserAccount.create(params);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { UserAccount } from './user-account.aggregate';
|
import { UserAccount } from './user-account.aggregate';
|
||||||
import { AccountSequence, ProvinceCode, CityCode } from '@/domain/value-objects';
|
import { AccountSequence } from '@/domain/value-objects';
|
||||||
import { DomainError } from '@/shared/exceptions/domain.exception';
|
import { DomainError } from '@/shared/exceptions/domain.exception';
|
||||||
|
|
||||||
describe('UserAccount', () => {
|
describe('UserAccount', () => {
|
||||||
|
|
@ -9,8 +9,6 @@ describe('UserAccount', () => {
|
||||||
initialDeviceId: 'device-001',
|
initialDeviceId: 'device-001',
|
||||||
deviceName: 'Test Device',
|
deviceName: 'Test Device',
|
||||||
inviterSequence: null,
|
inviterSequence: null,
|
||||||
province: ProvinceCode.create('110000'),
|
|
||||||
city: CityCode.create('110100'),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -43,7 +41,7 @@ describe('UserAccount', () => {
|
||||||
account.addDevice('device-003');
|
account.addDevice('device-003');
|
||||||
account.addDevice('device-004');
|
account.addDevice('device-004');
|
||||||
account.addDevice('device-005');
|
account.addDevice('device-005');
|
||||||
|
|
||||||
expect(() => account.addDevice('device-006')).toThrow(DomainError);
|
expect(() => account.addDevice('device-006')).toThrow(DomainError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ export class UserAccountAutoCreatedEvent extends DomainEvent {
|
||||||
accountSequence: number;
|
accountSequence: number;
|
||||||
initialDeviceId: string;
|
initialDeviceId: string;
|
||||||
inviterSequence: number | null;
|
inviterSequence: number | null;
|
||||||
province: string;
|
|
||||||
city: string;
|
|
||||||
registeredAt: Date;
|
registeredAt: Date;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
@ -38,8 +36,6 @@ export class UserAccountCreatedEvent extends DomainEvent {
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
initialDeviceId: string;
|
initialDeviceId: string;
|
||||||
inviterSequence: number | null;
|
inviterSequence: number | null;
|
||||||
province: string;
|
|
||||||
city: string;
|
|
||||||
registeredAt: Date;
|
registeredAt: Date;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
@ -143,16 +139,6 @@ export class KYCRejectedEvent extends DomainEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserLocationUpdatedEvent extends DomainEvent {
|
|
||||||
constructor(public readonly payload: { userId: string; province: string; city: string }) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
get eventType(): string {
|
|
||||||
return 'UserLocationUpdated';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserAccountFrozenEvent extends DomainEvent {
|
export class UserAccountFrozenEvent extends DomainEvent {
|
||||||
constructor(public readonly payload: { userId: string; reason: string }) {
|
constructor(public readonly payload: { userId: string; reason: string }) {
|
||||||
super();
|
super();
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export interface UserAccountRepository {
|
||||||
getMaxAccountSequence(): Promise<AccountSequence | null>;
|
getMaxAccountSequence(): Promise<AccountSequence | null>;
|
||||||
getNextAccountSequence(): Promise<AccountSequence>;
|
getNextAccountSequence(): Promise<AccountSequence>;
|
||||||
findUsers(
|
findUsers(
|
||||||
filters?: { status?: AccountStatus; kycStatus?: KYCStatus; province?: string; city?: string; keyword?: string },
|
filters?: { status?: AccountStatus; kycStatus?: KYCStatus; keyword?: string },
|
||||||
pagination?: Pagination,
|
pagination?: Pagination,
|
||||||
): Promise<UserAccount[]>;
|
): Promise<UserAccount[]>;
|
||||||
countUsers(filters?: { status?: AccountStatus; kycStatus?: KYCStatus }): Promise<number>;
|
countUsers(filters?: { status?: AccountStatus; kycStatus?: KYCStatus }): Promise<number>;
|
||||||
|
|
|
||||||
|
|
@ -97,23 +97,6 @@ export class ReferralCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ ProvinceCode & CityCode ============
|
|
||||||
export class ProvinceCode {
|
|
||||||
constructor(public readonly value: string) {}
|
|
||||||
|
|
||||||
static create(value: string): ProvinceCode {
|
|
||||||
return new ProvinceCode(value || 'DEFAULT');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CityCode {
|
|
||||||
constructor(public readonly value: string) {}
|
|
||||||
|
|
||||||
static create(value: string): CityCode {
|
|
||||||
return new CityCode(value || 'DEFAULT');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ Mnemonic ============
|
// ============ Mnemonic ============
|
||||||
export class Mnemonic {
|
export class Mnemonic {
|
||||||
constructor(public readonly value: string) {
|
constructor(public readonly value: string) {
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ export interface UserAccountEntity {
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
inviterSequence: bigint | null;
|
inviterSequence: bigint | null;
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
provinceCode: string;
|
|
||||||
cityCode: string;
|
|
||||||
address: string | null;
|
|
||||||
kycStatus: string;
|
kycStatus: string;
|
||||||
realName: string | null;
|
realName: string | null;
|
||||||
idCardNumber: string | null;
|
idCardNumber: string | null;
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,6 @@ export class UserAccountMapper {
|
||||||
avatarUrl: entity.avatarUrl,
|
avatarUrl: entity.avatarUrl,
|
||||||
inviterSequence: entity.inviterSequence ? Number(entity.inviterSequence) : null,
|
inviterSequence: entity.inviterSequence ? Number(entity.inviterSequence) : null,
|
||||||
referralCode: entity.referralCode,
|
referralCode: entity.referralCode,
|
||||||
province: entity.provinceCode,
|
|
||||||
city: entity.cityCode,
|
|
||||||
address: entity.address,
|
|
||||||
walletAddresses: wallets,
|
walletAddresses: wallets,
|
||||||
kycInfo,
|
kycInfo,
|
||||||
kycStatus: entity.kycStatus as KYCStatus,
|
kycStatus: entity.kycStatus as KYCStatus,
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,6 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
avatarUrl: account.avatarUrl,
|
avatarUrl: account.avatarUrl,
|
||||||
inviterSequence: account.inviterSequence ? BigInt(account.inviterSequence.value) : null,
|
inviterSequence: account.inviterSequence ? BigInt(account.inviterSequence.value) : null,
|
||||||
referralCode: account.referralCode.value,
|
referralCode: account.referralCode.value,
|
||||||
provinceCode: account.province.value,
|
|
||||||
cityCode: account.city.value,
|
|
||||||
address: account.addressDetail,
|
|
||||||
kycStatus: account.kycStatus,
|
kycStatus: account.kycStatus,
|
||||||
realName: account.kycInfo?.realName || null,
|
realName: account.kycInfo?.realName || null,
|
||||||
idCardNumber: account.kycInfo?.idCardNumber || null,
|
idCardNumber: account.kycInfo?.idCardNumber || null,
|
||||||
|
|
@ -56,9 +53,6 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
phoneNumber: account.phoneNumber?.value || null,
|
phoneNumber: account.phoneNumber?.value || null,
|
||||||
nickname: account.nickname,
|
nickname: account.nickname,
|
||||||
avatarUrl: account.avatarUrl,
|
avatarUrl: account.avatarUrl,
|
||||||
provinceCode: account.province.value,
|
|
||||||
cityCode: account.city.value,
|
|
||||||
address: account.addressDetail,
|
|
||||||
kycStatus: account.kycStatus,
|
kycStatus: account.kycStatus,
|
||||||
realName: account.kycInfo?.realName || null,
|
realName: account.kycInfo?.realName || null,
|
||||||
idCardNumber: account.kycInfo?.idCardNumber || null,
|
idCardNumber: account.kycInfo?.idCardNumber || null,
|
||||||
|
|
@ -184,14 +178,12 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
async findUsers(
|
async findUsers(
|
||||||
filters?: { status?: AccountStatus; kycStatus?: KYCStatus; province?: string; city?: string; keyword?: string },
|
filters?: { status?: AccountStatus; kycStatus?: KYCStatus; keyword?: string },
|
||||||
pagination?: Pagination,
|
pagination?: Pagination,
|
||||||
): Promise<UserAccount[]> {
|
): Promise<UserAccount[]> {
|
||||||
const where: any = {};
|
const where: any = {};
|
||||||
if (filters?.status) where.status = filters.status;
|
if (filters?.status) where.status = filters.status;
|
||||||
if (filters?.kycStatus) where.kycStatus = filters.kycStatus;
|
if (filters?.kycStatus) where.kycStatus = filters.kycStatus;
|
||||||
if (filters?.province) where.provinceCode = filters.province;
|
|
||||||
if (filters?.city) where.cityCode = filters.city;
|
|
||||||
if (filters?.keyword) {
|
if (filters?.keyword) {
|
||||||
where.OR = [
|
where.OR = [
|
||||||
{ nickname: { contains: filters.keyword } },
|
{ nickname: { contains: filters.keyword } },
|
||||||
|
|
@ -262,9 +254,6 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
inviterSequence: data.inviterSequence ? Number(data.inviterSequence) : null,
|
inviterSequence: data.inviterSequence ? Number(data.inviterSequence) : null,
|
||||||
referralCode: data.referralCode,
|
referralCode: data.referralCode,
|
||||||
province: data.provinceCode,
|
|
||||||
city: data.cityCode,
|
|
||||||
address: data.address,
|
|
||||||
walletAddresses: wallets,
|
walletAddresses: wallets,
|
||||||
kycInfo,
|
kycInfo,
|
||||||
kycStatus: data.kycStatus as KYCStatus,
|
kycStatus: data.kycStatus as KYCStatus,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue