*/ use HasFactory; protected $fillable = [ 'feed_id', 'platform_channel_id', 'article_id', 'approval_status', 'publish_status', 'publish_attempts', 'next_attempt_at', 'validated_at', ]; protected $casts = [ 'approval_status' => ApprovalStatusEnum::class, 'publish_status' => PublishStatusEnum::class, 'publish_attempts' => 'integer', 'next_attempt_at' => 'datetime', 'validated_at' => 'datetime', ]; /** * @return BelongsTo */ public function route(): BelongsTo { return $this->belongsTo(Route::class, 'feed_id', 'feed_id') ->where('platform_channel_id', $this->platform_channel_id); } /** * @return BelongsTo */ public function article(): BelongsTo { return $this->belongsTo(Article::class); } /** * @return BelongsTo */ public function feed(): BelongsTo { return $this->belongsTo(Feed::class); } /** * @return BelongsTo */ public function platformChannel(): BelongsTo { return $this->belongsTo(PlatformChannel::class); } public function isPending(): bool { return $this->approval_status === ApprovalStatusEnum::PENDING; } public function isApproved(): bool { return $this->approval_status === ApprovalStatusEnum::APPROVED; } public function isRejected(): bool { return $this->approval_status === ApprovalStatusEnum::REJECTED; } public function approve(): void { if ($this->isApproved()) { return; } $this->update(['approval_status' => ApprovalStatusEnum::APPROVED]); event(new RouteArticleApproved($this)); } public function reject(): void { $this->update(['approval_status' => ApprovalStatusEnum::REJECTED]); } private const RETRY_BACKOFF_MINUTES = [5, 30, 120, 360]; public const MAX_PUBLISH_ATTEMPTS = 4; public function recordPublishAttemptFailed(): void { $attempts = $this->publish_attempts + 1; $backoff = self::RETRY_BACKOFF_MINUTES; $this->update([ 'publish_attempts' => $attempts, 'next_attempt_at' => now()->addMinutes($backoff[$attempts - 1] ?? end($backoff)), ]); } public function clearPublishAttempts(): void { $this->update(['publish_attempts' => 0, 'next_attempt_at' => null]); } public function hasExhaustedPublishAttempts(): bool { return $this->publish_attempts >= self::MAX_PUBLISH_ATTEMPTS; } /** * @param Builder $query * @return Builder */ public function scopeDueForPublishing(Builder $query): Builder { return $query->where('publish_attempts', '<', self::MAX_PUBLISH_ATTEMPTS) ->where(function (Builder $query) { $query->whereNull('next_attempt_at') ->orWhere('next_attempt_at', '<=', now()); }); } }