{"record":{"id":"dc161fb42acd4ec3","repo":"yikart/AiToEarn","slug":"userid-accountid-query","errorCode":null,"errorMessage":"userId, accountId和query是必须的","messagePattern":"userId, accountId和query是必须的","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/twitter/twitter.controller.ts","lineNumber":337,"sourceCode":"  }\n\n  /**\n   * 搜索推文\n   */\n  @Get('tweets/search')\n  @ApiOperation({ summary: '搜索推文' })\n  @ApiQuery({ name: 'accountId', required: true, description: 'Twitter账号ID' })\n  @ApiQuery({ name: 'query', required: true, description: '搜索关键词' })\n  @ApiQuery({ name: 'maxResults', required: false, description: '最大结果数', type: 'number' })\n  async searchTweets(\n    @GetToken() systemToken: TokenInfo,\n    @Query('accountId') accountId: string,\n    @Query('query') query: string,\n    @Query('maxResults') maxResults?: number,\n  ) {\n    const userId = systemToken.id;\n    if (!userId || !accountId || !query) {\n      throw new BadRequestException('userId, accountId和query是必须的');\n    }\n\n    const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);\n    return this.twitterService.searchTweets(accessToken, userId, accountId, query, maxResults);\n  }\n\n  /**\n   * 对推文点赞、取消点赞\n   */\n  @Post('tweets/rate')\n  @ApiOperation({ summary: '对推文点赞、取消点赞' })\n  @ApiBody({\n    schema: {\n      type: 'object',\n      properties: {\n        accountId: { type: 'string', description: '账号ID' },\n        tweetId: { type: 'string', description: '推文ID' },\n        rating: { type: 'string', description: '点赞 like、取消点赞  unlike' }","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/twitter/twitter.controller.ts#L319-L355","documentation":"Thrown by the searchTweets endpoint when the query string is missing 'accountId' or 'query' (userId comes from the system token). The guard runs before obtaining the account's access token and executing the Twitter recent-search call.","triggerScenarios":"GET to searchTweets without ?query=... or ?accountId=..., empty-string values, or an invalid system token so userId is falsy. maxResults is optional and does not trigger this error.","commonSituations":"Search box submitted empty; accountId not yet selected in a multi-account UI; special characters in query dropped during URL construction leaving an empty value.","solutions":["Provide a non-empty query string and accountId as query parameters.","URL-encode the query (e.g. encodeURIComponent) so it survives URL construction.","Block the client-side submit when the search input is empty.","Verify authentication so systemToken.id is non-empty."],"exampleFix":"// before\nconst url = `/twitter/search?accountId=${accountId}&query=${query}`;\n// after\nconst url = `/twitter/search?accountId=${encodeURIComponent(accountId)}&query=${encodeURIComponent(query)}`;","handlingStrategy":"validation","validationCode":"const q = searchInput.trim();\nif (!q || !accountId) return; // don't call the API\nawait api.get('/twitter/search', { params: { query: q, accountId, maxResults } });","typeGuard":"function canSearch(p: unknown): p is { query: string; accountId: string } {\n  const o = p as any;\n  return typeof o?.query === 'string' && o.query.trim().length > 0 && typeof o?.accountId === 'string' && o.accountId.length > 0;\n}","tryCatchPattern":"try {\n  const results = await api.get('/twitter/search', { params: { query, accountId } });\n} catch (e) {\n  if (e.response?.status === 400) setSearchError('请输入搜索内容并选择账号');\n}","preventionTips":["Trim and check the search input before submitting.","Require an account selection before enabling search.","Use the axios params option (it URL-encodes automatically).","Treat any 400 with '必须' in the message as a client-side param bug."],"tags":["nestjs","validation","bad-request","missing-parameter","search"],"backgroundTag":"missing-required-parameter","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}