{"record":{"id":"338e0d37dc737c91","repo":"yikart/AiToEarn","slug":"materialgroupnotfound-338e0d","errorCode":"MaterialGroupNotFound","errorMessage":"MaterialGroupNotFound","messagePattern":"MaterialGroupNotFound","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":79,"sourceCode":"      queued: false,\n    })\n\n    return {\n      recordId: record.id,\n      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,","sourceCodeStart":61,"sourceCodeEnd":97,"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#L61-L97","documentation":"MaterialGroupNotFound is thrown by DouyinOfflineQrService.validateMaterial when materialGroupRepo.getInfo(materialGroupId) returns null, i.e. no material group exists for the given id. validateMaterial runs during createPublish, so publishing an offline QR with an unknown materialGroupId is rejected before any media is prepared.","triggerScenarios":"createPublish with a materialGroupId that was deleted, belongs to another user, was never created, or is a malformed/nonexistent id; also when ids are swapped (passing materialId where group id is expected).","commonSituations":"Client cached a material group id that another session deleted; hardcoded ids from tests/staging used against a different database; copy-paste between materialId and materialGroupId fields; multitenancy mismatch (id from a different workspace).","solutions":["Fetch the user's material groups via the material-group list API and use a returned _id in createPublish.","Check that materialGroupId and materialId are not swapped in the request payload.","If the group was deleted, recreate it or pick another group; purge stale ids from client-side cache.","Verify the request targets the same environment/database where the group was created (staging vs production)."],"exampleFix":"// before\nawait offlineQr.createPublish({ materialGroupId: '64b000000000000000000000', ... }) // group deleted -> throws\n// after\nconst groups = await materialGroupService.listMine()\nawait offlineQr.createPublish({ materialGroupId: groups[0]._id, materialId: validMaterialId, ... })","handlingStrategy":"validation","validationCode":"const group = await materialGroupRepo.getInfo(materialGroupId)\nif (!group) throw new Error(`material group ${materialGroupId} does not exist`)\n// only then call createPublish","typeGuard":"const isExistingGroup = async (id: string) => (await materialGroupRepo.getInfo(id)) != null","tryCatchPattern":"try {\n  await offlineQrService.createPublish(dto)\n} catch (err) {\n  if (err instanceof AppException && err.code === ResponseCode.MaterialGroupNotFound) {\n    // prompt user to pick a valid material group\n  } else throw err\n}","preventionTips":["Load material group ids from the list API rather than caching them indefinitely","Invalidate client-side group caches after any delete/rename operation","Never swap materialGroupId and materialId fields in request payloads","Ensure environments (staging vs prod) are not mixing ids"],"tags":["douyin","material-group","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"}