feat(pending-actions): display accountSequence alongside userId
- Add accountSequence field to PendingActionResponseDto - Add helper methods to fetch accountSequence from UserAccount - Update queryActions and getAction to include accountSequence - Update admin-web table and detail view to show both fields - accountSequence displayed prominently, userId shown as secondary info 🤖 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
13dd42d2be
commit
cbbef170e8
|
|
@ -557,7 +557,23 @@
|
|||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(android\\): add 5-minute polling timeout mechanism for keygen/sign\n\nImplements Electron''s checkAndTriggerKeygen\\(\\) polling fallback:\n- Adds polling every 2 seconds with 5-minute timeout\n- Triggers keygen/sign via synthetic session_started event on in_progress status\n- Handles gRPC stream disconnection when app goes to background\n- Shows timeout error in UI via existing error mechanism\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(go list:*)",
|
||||
"Bash(adb install:*)",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(tss\\): add real-time round progress from msg.Type\\(\\) parsing\n\nExtract current round number from tss-lib message type string using\nregex pattern `Round\\(\\\\d+\\)`. This enables real-time progress updates\n\\(1/4, 2/4... for keygen, 1/9, 2/9... for signing\\) instead of only\nshowing completion status.\n\nChanges across all three platforms:\n- tss-wasm/main.go: Add extractRoundFromMessageType\\(\\) and call\n OnProgress with parsed round on each outgoing message\n- service-party-android/tsslib/tsslib.go: Same implementation for\n Android gomobile binding\n- service-party-app/tss-party/main.go: Same implementation for\n Electron subprocess, with isKeygen parameter to distinguish\n keygen \\(4 rounds\\) vs signing \\(9 rounds\\)\n\nSafe fallback: Returns 0 if parsing fails, which doesn''t affect\nprotocol execution - only UI display.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(tss\\): add real-time round progress from msg.Type\\(\\) parsing\n\nExtract current round number from tss-lib message type string using\nregex pattern `Round\\(\\\\d+\\)`. This enables real-time progress updates\n\\(1/4, 2/4... for keygen, 1/9, 2/9... for signing\\) instead of only\nshowing completion status.\n\nChanges across all three platforms:\n- tss-wasm/main.go: Add extractRoundFromMessageType\\(\\) and call\n OnProgress with parsed round on each outgoing message\n- service-party-android/tsslib/tsslib.go: Same implementation for\n Android gomobile binding\n- service-party-app/tss-party/main.go: Same implementation for\n Electron subprocess, with isKeygen parameter to distinguish\n keygen \\(4 rounds\\) vs signing \\(9 rounds\\)\n\nSafe fallback: Returns 0 if parsing fails, which doesn''t affect\nprotocol execution - only UI display.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(node --input-type=module -e:*)",
|
||||
"Bash(npx solc:*)",
|
||||
"Bash(node /c/Users/dong/Desktop/rwadurian/contracts/deploy.mjs:*)",
|
||||
"Bash(npm init:*)",
|
||||
"Bash(node deploy.mjs:*)",
|
||||
"Bash(npx solcjs@0.8.19:*)",
|
||||
"Bash(node compile.mjs:*)",
|
||||
"Bash(node verify-sig.mjs:*)",
|
||||
"Bash(node deploy-ethers.mjs:*)",
|
||||
"Bash(node transfer-all.mjs:*)",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(android\\): add share export and import functionality\n\nAdd ability to backup wallet shares to files and restore from backups:\n\n- Add ShareBackup data class in Models.kt for backup format\n- Add exportShareBackup\\(\\) and importShareBackup\\(\\) in TssRepository\n- Add export/import state and methods in MainViewModel\n- Add file picker integration in MainActivity using ActivityResultContracts\n- Add import FAB button in WalletsScreen\n- Export saves as .tss-backup file with address and timestamp in filename\n- Import validates backup format and checks for duplicate wallets\n\nThe backup file contains all necessary data to restore a wallet share:\nsessionId, publicKey, encryptedShare, threshold, partyIndex, address.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(dir /s /b \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\identity-service\\\\src\\\\api\\\\controllers\\\\*.ts\")",
|
||||
"Bash(dir /s /b \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\identity-service\\\\src\\\\infrastructure\\\\persistence\\\\repositories\\\\*.ts\")",
|
||||
"Bash(head:*)",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(pending-actions\\): add user pending actions system\n\nAdd a fully optional pending actions system that allows admins to configure\nspecific tasks that users must complete after login.\n\nBackend \\(identity-service\\):\n- Add UserPendingAction model to Prisma schema\n- Add migration for user_pending_actions table\n- Add PendingActionService with full CRUD operations\n- Add user-facing API \\(GET list, POST complete\\)\n- Add admin API \\(CRUD, batch create\\)\n\nAdmin Web:\n- Add pending actions management page\n- Support single/batch create, edit, cancel, delete\n- View action details including completion time\n- Filter by userId, actionCode, status\n\nFlutter Mobile App:\n- Add PendingActionService and PendingActionCheckService\n- Add PendingActionsPage for forced task execution\n- Integrate into splash_page login flow\n- Users must complete all pending tasks in priority order\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(npm run type-check:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ export class PendingActionResponseDto {
|
|||
@ApiProperty({ example: '12345', description: '用户ID' })
|
||||
userId: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'D25122700022', description: '用户账号序列号' })
|
||||
accountSequence?: string;
|
||||
|
||||
@ApiProperty({ example: 'ADOPTION_WIZARD', description: '操作代码' })
|
||||
actionCode: string;
|
||||
|
||||
|
|
|
|||
|
|
@ -224,8 +224,13 @@ export class PendingActionService {
|
|||
this.prisma.userPendingAction.count({ where }),
|
||||
]);
|
||||
|
||||
// 批量获取 accountSequence
|
||||
const accountSequenceMap = await this.getAccountSequenceMap(items.map((item) => item.userId));
|
||||
|
||||
return {
|
||||
items: items.map((action) => this.toResponseDto(action)),
|
||||
items: items.map((action) =>
|
||||
this.toResponseDto(action, accountSequenceMap.get(action.userId.toString())),
|
||||
),
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
|
|
@ -244,7 +249,8 @@ export class PendingActionService {
|
|||
throw new ApplicationError('操作不存在');
|
||||
}
|
||||
|
||||
return this.toResponseDto(action);
|
||||
const accountSequence = await this.getAccountSequence(action.userId);
|
||||
return this.toResponseDto(action, accountSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -367,10 +373,11 @@ export class PendingActionService {
|
|||
});
|
||||
}
|
||||
|
||||
private toResponseDto(action: any): PendingActionResponseDto {
|
||||
private toResponseDto(action: any, accountSequence?: string): PendingActionResponseDto {
|
||||
return {
|
||||
id: action.id.toString(),
|
||||
userId: action.userId.toString(),
|
||||
accountSequence,
|
||||
actionCode: action.actionCode,
|
||||
actionParams: action.actionParams as Record<string, any> | null,
|
||||
status: action.status,
|
||||
|
|
@ -381,4 +388,36 @@ export class PendingActionService {
|
|||
createdBy: action.createdBy,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取用户的 accountSequence
|
||||
*/
|
||||
private async getAccountSequenceMap(userIds: bigint[]): Promise<Map<string, string>> {
|
||||
if (userIds.length === 0) return new Map();
|
||||
|
||||
const uniqueUserIds = [...new Set(userIds.map((id) => id.toString()))];
|
||||
const users = await this.prisma.userAccount.findMany({
|
||||
where: { userId: { in: uniqueUserIds.map((id) => BigInt(id)) } },
|
||||
select: { userId: true, accountSequence: true },
|
||||
});
|
||||
|
||||
const map = new Map<string, string>();
|
||||
for (const user of users) {
|
||||
if (user.accountSequence) {
|
||||
map.set(user.userId.toString(), user.accountSequence);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个用户的 accountSequence
|
||||
*/
|
||||
private async getAccountSequence(userId: bigint): Promise<string | undefined> {
|
||||
const user = await this.prisma.userAccount.findUnique({
|
||||
where: { userId },
|
||||
select: { accountSequence: true },
|
||||
});
|
||||
return user?.accountSequence ?? undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ export default function PendingActionsPage() {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户ID</th>
|
||||
<th>用户</th>
|
||||
<th>操作类型</th>
|
||||
<th>状态</th>
|
||||
<th>优先级</th>
|
||||
|
|
@ -489,7 +489,16 @@ export default function PendingActionsPage() {
|
|||
return (
|
||||
<tr key={action.id}>
|
||||
<td>{action.id}</td>
|
||||
<td>{action.userId}</td>
|
||||
<td>
|
||||
{action.accountSequence && (
|
||||
<div className={styles.pendingActions__accountSequence}>
|
||||
{action.accountSequence}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.pendingActions__userId}>
|
||||
ID: {action.userId}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className={styles.pendingActions__codeTag}>
|
||||
{getActionCodeLabel(action.actionCode)}
|
||||
|
|
@ -826,8 +835,11 @@ export default function PendingActionsPage() {
|
|||
<span>{viewingAction.id}</span>
|
||||
</div>
|
||||
<div className={styles.pendingActions__detailRow}>
|
||||
<span className={styles.pendingActions__detailLabel}>用户ID:</span>
|
||||
<span>{viewingAction.userId}</span>
|
||||
<span className={styles.pendingActions__detailLabel}>用户:</span>
|
||||
<span>
|
||||
{viewingAction.accountSequence && `${viewingAction.accountSequence} / `}
|
||||
ID: {viewingAction.userId}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.pendingActions__detailRow}>
|
||||
<span className={styles.pendingActions__detailLabel}>操作类型:</span>
|
||||
|
|
|
|||
|
|
@ -136,6 +136,17 @@
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__accountSequence {
|
||||
font-weight: 600;
|
||||
color: $primary-color;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&__userId {
|
||||
font-size: 12px;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export type ActionCode = (typeof ACTION_CODES)[keyof typeof ACTION_CODES] | stri
|
|||
export interface PendingAction {
|
||||
id: string;
|
||||
userId: string;
|
||||
accountSequence?: string; // 用户账号序列号 (如 D25122700022)
|
||||
actionCode: string;
|
||||
actionParams: Record<string, unknown> | null;
|
||||
status: PendingActionStatus;
|
||||
|
|
|
|||
Loading…
Reference in New Issue