{"record":{"id":"b14831ebbfcbf53d","repo":"yikart/AiToEarn","slug":"responsecode-channelauthselectionrequired","errorCode":"ResponseCode.ChannelAuthSelectionRequired","errorMessage":"ChannelAuthSelectionRequired","messagePattern":"ChannelAuthSelectionRequired","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"warning","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts","lineNumber":327,"sourceCode":"    allowReassign?: boolean\n  }): Promise<{ accountIds: string[], accounts: ConnectedSelectableAccount[] }> {\n    const result = await this.saveSelectedAccountsForUser(input)\n    await Promise.all(result.accounts.map(account => this.emitAccountConnected(input.userId, account.accountId, account.platform, input.source)))\n    return result\n  }\n\n  @Transactional()\n  private async saveSelectedAccountsForUser(input: {\n    userId: string\n    platform: AccountType\n    selectableAccounts: PlatformSelectableAccount[]\n    selectedAccounts: SelectedAccountIdentity[]\n    groupId?: string\n    source: string\n    allowReassign?: boolean\n  }): Promise<{ accountIds: string[], accounts: ConnectedSelectableAccount[] }> {\n    if (input.selectedAccounts.length === 0) {\n      throw new AppException(ResponseCode.ChannelAuthSelectionRequired)\n    }\n\n    const keyOf = (account: SelectedAccountIdentity) => `${account.platformUid}\\u0000${account.account ?? ''}`\n    const selectedAccounts = Array.from(\n      new Map(input.selectedAccounts.map(account => [keyOf(account), account])).values(),\n    )\n    const selectableAccounts = new Map(\n      input.selectableAccounts.map(account => [keyOf(account), account]),\n    )\n    const unknownAccount = selectedAccounts.find(account => !selectableAccounts.has(keyOf(account)))\n    if (unknownAccount) {\n      throw new AppException(ResponseCode.ChannelAuthSelectedAccountUnavailable)\n    }\n\n    const groupId = await this.resolveGroupId(input.userId, input.groupId)\n    const accountIds: string[] = []\n    const accounts: ConnectedSelectableAccount[] = []\n","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/auth/auth.service.ts#L309-L345","documentation":"Thrown in AuthService.saveSelectedAccountsForUser when the submitted selectedAccounts array is empty. Persisting the user's chosen second-level accounts requires at least one account identity ({ platformUid, account? }); an empty submission means nothing was selected.","triggerScenarios":"saveSelectedAccountsForUser (via connectSelectableAccounts / connectSelectedAccountsForUser, e.g. POST /accounts/auth/selections) with body.accounts = [] — an empty selection form, a frontend bug sending no accounts, or an API caller omitting the accounts list.","commonSituations":"User submits the account-selection page without ticking any checkbox; frontend not mapping the checkbox values into the request body; DTO accepting an empty array because the schema only validates item shape, not min length; scripted calls to the endpoint with an empty payload.","solutions":["Enforce at least one selection in the UI (disable submit until a checkbox is ticked) and validate client-side before POSTing.","Reject empty arrays at the DTO boundary with a Zod min-length constraint so callers get a clear 400-style message.","Check the frontend form serialization — ensure the accounts field is populated from the selection checkboxes.","If the user legitimately wants to select none, cancel the flow instead of submitting an empty selection."],"exampleFix":"// before: DTO allows empty array\naccounts: z.array(selectedAccountSchema)\n\n// after: require at least one selection\naccounts: z.array(selectedAccountSchema).min(1)","handlingStrategy":"validation","validationCode":"function canSubmitSelection(selected: SelectedAccountIdentity[]): boolean {\n  return Array.isArray(selected) && selected.length > 0\n}\nif (!canSubmitSelection(collectedSelections)) {\n  alert('Select at least one account')\n  return\n}","typeGuard":"function hasSelections(v: unknown): v is SelectedAccountIdentity[] {\n  return Array.isArray(v) && v.length > 0 && v.every(i => typeof (i as any).platformUid === 'string')\n}","tryCatchPattern":"try {\n  await submitSelections(selected)\n}\ncatch (e) {\n  if (getErrorCode(e) === ResponseCode.ChannelAuthSelectionRequired) {\n    showError('Please select at least one account before continuing')\n  }\n}","preventionTips":["Require at least one checkbox before enabling submit","Enforce z.array(...).min(1) in the selection DTO","Verify form serialization maps checkboxes to the accounts field","Offer an explicit cancel action instead of empty submits"],"tags":["validation","selection","empty-payload","dto"],"backgroundTag":"empty-selection-payload","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}