yikart/AiToEarn · error · AppException
MaterialNotFound
MaterialNotFound
Error message
MaterialNotFound
What it means
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.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/offline-qr/douyin-offline-qr.service.ts:84
status: PublishStatus.WaitingForUserAction,
userAction: {
shareId: dataOption.shareId,
schemeUrl: dataOption.schema,
shortLink: dataOption.shortLink,
expiresAt: new Date(dataOption.expiresAt),
},
}
}
private async validateMaterial(materialGroupId: string, materialId: string): Promise<void> {
const materialGroup = await this.materialGroupRepo.getInfo(materialGroupId)
if (!materialGroup) {
throw new AppException(ResponseCode.MaterialGroupNotFound)
}
const material = await this.materialRepo.getInfo(materialId)
if (!material || material.groupId !== materialGroupId) {
throw new AppException(ResponseCode.MaterialNotFound)
}
}
private async prepareContent(content: PublishContentInput): Promise<PublishContentInput> {
const prepared = await this.mediaService.preparePublishContentMedia({
userId: '',
content,
mediaRules: DOUYIN_METADATA.mediaRules,
})
if (prepared.issues.length) {
const locale = getLocale()
throw new AppException(ResponseCode.ChannelPublishValidationFailed, {
platform: AccountType.Douyin,
accountId: '',
issues: prepared.issues.map(issue => formatPublishValidationIssue(issue, locale)),
})
}
return prepared.contentView on GitHub (pinned to d3aa8bea5b)
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.
Example fix
// before
await offlineQr.createPublish({ materialGroupId: groupA._id, materialId: materialInGroupB._id }) // groupId mismatch -> throws
// after
const material = await materialService.getInfo(materialInGroupB._id)
await offlineQr.createPublish({ materialGroupId: material.groupId, materialId: material._id }) Defensive patterns
Strategy: validation
Validate before calling
const [group, material] = await Promise.all([
materialGroupRepo.getInfo(materialGroupId),
materialRepo.getInfo(materialId),
])
if (!group) throw new Error('group missing')
if (!material || material.groupId !== materialGroupId) throw new Error('material missing or not in group') Type guard
const isInGroup = (m: { groupId: string } | null, groupId: string): m is { groupId: string } =>
!!m && m.groupId === groupId Try / catch
try {
await offlineQrService.createPublish(dto)
} catch (err) {
if (err instanceof AppException && err.code === ResponseCode.MaterialNotFound) {
// refresh material list and re-select a material within the chosen group
} else throw err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/b3d162f58eef9927.
Report an issue: GitHub.