{"record":{"id":"b3d162f58eef9927","repo":"yikart/AiToEarn","slug":"materialnotfound","errorCode":"MaterialNotFound","errorMessage":"MaterialNotFound","messagePattern":"MaterialNotFound","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/offline-qr/douyin-offline-qr.service.ts","lineNumber":84,"sourceCode":"      status: PublishStatus.WaitingForUserAction,\n      userAction: {\n        shareId: dataOption.shareId,\n        schemeUrl: dataOption.schema,\n        shortLink: dataOption.shortLink,\n        expiresAt: new Date(dataOption.expiresAt),\n      },\n    }\n  }\n\n  private async validateMaterial(materialGroupId: string, materialId: string): Promise<void> {\n    const materialGroup = await this.materialGroupRepo.getInfo(materialGroupId)\n    if (!materialGroup) {\n      throw new AppException(ResponseCode.MaterialGroupNotFound)\n    }\n\n    const material = await this.materialRepo.getInfo(materialId)\n    if (!material || material.groupId !== materialGroupId) {\n      throw new AppException(ResponseCode.MaterialNotFound)\n    }\n  }\n\n  private async prepareContent(content: PublishContentInput): Promise<PublishContentInput> {\n    const prepared = await this.mediaService.preparePublishContentMedia({\n      userId: '',\n      content,\n      mediaRules: DOUYIN_METADATA.mediaRules,\n    })\n    if (prepared.issues.length) {\n      const locale = getLocale()\n      throw new AppException(ResponseCode.ChannelPublishValidationFailed, {\n        platform: AccountType.Douyin,\n        accountId: '',\n        issues: prepared.issues.map(issue => formatPublishValidationIssue(issue, locale)),\n      })\n    }\n    return prepared.content","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/offline-qr/douyin-offline-qr.service.ts#L66-L102","documentation":"MaterialNotFound is thrown by DouyinOfflineQrService.validateMaterial when the material id cannot be loaded (materialRepo.getInfo returns null) or when the material exists but belongs to a different group (material.groupId !== materialGroupId). It guards that the referenced media actually lives inside the declared material group before offline-QR publish proceeds.","triggerScenarios":"createPublish with a materialId that was deleted, is malformed, or whose parent groupId differs from the supplied materialGroupId — e.g. moving a material to another group then publishing with the old group id, or mixing ids across groups/users.","commonSituations":"User moved the material after selecting it in the UI; ids copied from another group or workspace; race where the material is deleted concurrently with publish; client sends stale ids after a reorganization of the material library.","solutions":["Refresh the material list from the API and publish with a materialId whose groupId matches the given materialGroupId.","Ensure the UI re-reads the material's current groupId before submitting, rather than caching it at selection time.","If the material was moved intentionally, update materialGroupId to the material's current group.","Check for accidental id transposition or cross-tenant id usage."],"exampleFix":"// before\nawait offlineQr.createPublish({ materialGroupId: groupA._id, materialId: materialInGroupB._id }) // groupId mismatch -> throws\n// after\nconst material = await materialService.getInfo(materialInGroupB._id)\nawait offlineQr.createPublish({ materialGroupId: material.groupId, materialId: material._id })","handlingStrategy":"validation","validationCode":"const [group, material] = await Promise.all([\n  materialGroupRepo.getInfo(materialGroupId),\n  materialRepo.getInfo(materialId),\n])\nif (!group) throw new Error('group missing')\nif (!material || material.groupId !== materialGroupId) throw new Error('material missing or not in group')","typeGuard":"const isInGroup = (m: { groupId: string } | null, groupId: string): m is { groupId: string } =>\n  !!m && m.groupId === groupId","tryCatchPattern":"try {\n  await offlineQrService.createPublish(dto)\n} catch (err) {\n  if (err instanceof AppException && err.code === ResponseCode.MaterialNotFound) {\n    // refresh material list and re-select a material within the chosen group\n  } else throw err\n}","preventionTips":["After moving a material to another group, refresh materialGroupId in any pending publish draft","Have the UI submit the material's current groupId read at submit time, not selection time","Handle concurrent deletion gracefully by refreshing the library before publish","Validate material.groupId === materialGroupId client-side before calling the API"],"tags":["douyin","material","not-found","validation"],"backgroundTag":"resource-not-found","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}