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.content

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Refresh the material list from the API and publish with a materialId whose groupId matches the given materialGroupId.
  2. Ensure the UI re-reads the material's current groupId before submitting, rather than caching it at selection time.
  3. If the material was moved intentionally, update materialGroupId to the material's current group.
  4. 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

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.