{"record":{"id":"0296e26e78d2d35f","repo":"yikart/AiToEarn","slug":"asset-is-not-a-video","errorCode":null,"errorMessage":"Asset is not a video","messagePattern":"Asset is not a video","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-backend/libs/assets/src/http/assets-http.controller-base.ts","lineNumber":136,"sourceCode":"  }\n\n  @ApiDoc({\n    summary: 'Get Video Thumbnail',\n    description: 'Get or extract thumbnail from a video by URL. If thumbnail already exists in metadata.cover, returns it directly. Otherwise extracts a new thumbnail.',\n    query: GetThumbnailQueryDto.schema,\n    response: ThumbnailResultVo,\n  })\n  @Get('/thumbnail')\n  async getThumbnail(\n    @GetToken() token: TokenInfo,\n    @Query() query: GetThumbnailQueryDto,\n    @Res({ passthrough: true }) res: Response,\n  ) {\n    const path = this.assetsService.parsePathFromUrl(query.url)\n    const asset = await this.assetsService.getOrCreateAssetByPath(path, token.id, this.userType)\n\n    if (!asset.mimeType?.startsWith('video/')) {\n      throw new BadRequestException('Asset is not a video')\n    }\n\n    const existingCover = (asset.metadata as { cover?: string } | undefined)?.cover\n    if (existingCover) {\n      const thumbnailUrl = this.assetsService.buildUrl(existingCover)\n      if (query.redirect) {\n        res.redirect(302, thumbnailUrl)\n        return\n      }\n      return ThumbnailResultVo.create({ thumbnailUrl })\n    }\n\n    const result = await this.videoMetadataService.extractAndSaveThumbnail(\n      asset,\n      token.id,\n      this.userType,\n      { timeInSeconds: query.timeInSeconds },\n    )","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/libs/assets/src/http/assets-http.controller-base.ts#L118-L154","documentation":"BadRequestException('Asset is not a video') is thrown by the thumbnail endpoint when the asset resolved from query.url has a mimeType that is not video/*. Thumbnails/covers are generated only for video assets, so images, PDFs, or assets with unknown mime type are rejected.","triggerScenarios":"GET thumbnail endpoint with query.url pointing to an image or non-media file; the asset's mimeType is null/undefined (unknown type); passing a URL whose path resolves to a non-video asset for the given user.","commonSituations":"Frontend blindly requests thumbnails for every attachment including images; asset was uploaded without a recognizable extension so mimeType detection fell back to a non-video type; URL points at the wrong file after a path refactor.","solutions":["Only request thumbnails when the asset mimeType starts with 'video/'","Ensure uploads preserve correct file extensions so mime.lookup yields video/*","Check the asset's mimeType via the asset detail endpoint before calling this endpoint","If a cover is needed for images, use the image URL directly instead of this endpoint"],"exampleFix":"// before\nawait api.get(`/assets/thumbnail?url=${encodeURIComponent(anyAsset.url)}`)\n// after\nif (asset.mimeType?.startsWith('video/')) {\n  await api.get(`/assets/thumbnail?url=${encodeURIComponent(asset.url)}`)\n}","handlingStrategy":"type-guard","validationCode":"const asset = await getAssetByUrl(url)\nif (!asset.mimeType?.startsWith('video/')) throw new Error('thumbnail only available for video assets')","typeGuard":"function isVideoAsset(a: { mimeType?: string | null }): boolean {\n  return typeof a.mimeType === 'string' && a.mimeType.startsWith('video/')\n}","tryCatchPattern":"try {\n  await api.get(`/assets/thumbnail?url=${encodeURIComponent(url)}`)\n} catch (e) {\n  if (e.response?.status === 400 && e.response.data?.message === 'Asset is not a video') {\n    // fall back to showing the original asset inline\n  } else throw e\n}","preventionTips":["Check asset.mimeType before requesting thumbnails","Preserve file extensions on upload so mime detection works","Only use the thumbnail endpoint for videos; use direct URL for images","Handle unknown mimeType as non-video defensively"],"tags":["thumbnail","video","mime-type","validation"],"backgroundTag":"unsupported-media-type","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}