Files

3.8 KiB

Metadata Linting

User Perspective

The linting feature automatically checks video metadata and descriptions for quality issues. Lint results appear as badges on the video list and as a collapsible section in the video editor header.

Lint Page (/linting)

Shows all open (unresolved) lint results across the team. Filter by severity or rule code. Results can be individually resolved (marking them as acknowledged). Navigating to the linting page automatically triggers a background heal that corrects any stale lintStatus values on video records.

Lint Status on Videos

Videos show one of three statuses:

  • OK — no open lint issues
  • WARNING — at least one warning, no errors
  • ERROR — at least one error

The "Lint Issues" tab on the Videos page shows all videos with WARNING or ERROR status.

Resolving Issues

Resolving a lint result marks it as resolvedAt = now(). It will reappear on the next lint run if the underlying issue is not fixed. Bulk-resolve is supported.

Developer Perspective

Rules

All rules implement the LintRule interface: { code: string, severity: LintSeverity, check(video): LintIssue | null }.

LintSeverity has three values: ERROR, WARNING, and INFO. No current rule uses INFO — it is reserved for future informational hints. Important: computeStatus() only checks for ERROR and WARNING when computing Video.lintStatus. A video with only INFO results will have lintStatus: OK. This is intentional — INFO is non-actionable and should not surface as a problem on the video list.

Rule Code Severity What it checks
TITLE_WEAK WARNING Title < 20 chars or contains generic words (video, test, untitled, new video, upload)
TITLE_TOO_LONG WARNING Title > 100 characters
DESC_MISSING_CTA WARNING Description lacks CTA keywords (subscribe, abonnieren, follow, like, comment, cta)
DESC_MISSING_CHAPTERS WARNING Description has fewer than 2 timestamp patterns (\d{1,2}:\d{2})
DESC_EMPTY_PLACEHOLDER ERROR Description contains unresolved {placeholder} patterns
DESC_DUPLICATE_HASHTAG WARNING Description has duplicate hashtags (case-insensitive)
DESC_REQUIRED_LINK_MISSING ERROR Description missing a required link defined in template.rules.requiredLinks
DESC_OUTDATED_SPONSOR_COPY ERROR A CAMPAIGN block references an expired or inactive campaign
REMOTE_CONFLICT ERROR video.remoteConflict is true — YouTube-side metadata changed since last sync

Lint Execution Flow

  1. LintProcessor receives { videoId } from the lint queue
  2. Calls LintingService.lintVideo(videoId)
  3. Fetches video with config, template, existing lint results, and team's disabledLintRules
  4. Filters rules: removes any whose code is in disabledLintRules
  5. Runs each active rule's check() method
  6. Wraps in a transaction: deletes all unresolved results, creates new ones, updates Video.lintStatus

Disabling Rules

Team admins can disable specific rules in Settings. When a rule is disabled:

  1. Existing unresolved LintResult rows for that rule are deleted
  2. Video.lintStatus is recomputed for all affected videos
  3. Future lint runs skip that rule

Stale lintStatus

Video.lintStatus is a denormalized cache. It can become stale if LintResult rows are deleted outside of lintVideo(). The heal endpoint POST /lint/team/recompute-status corrects all stale statuses in one query. This endpoint is called automatically when the linting page loads.

Adding a New Lint Rule

  1. Create backend/src/modules/linting/rules/your-rule.rule.ts implementing LintRule
  2. Import and instantiate it in LintingService.rules[] array
  3. The rule will run automatically on all subsequent lint jobs
  • 03 - Linting API
  • 01 - Video Management
  • 13 - Team Settings