Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
Article
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
11 / 11
15
100.00% covered (success)
100.00%
1 / 1
 casts
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 isValid
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isApproved
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPending
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRejected
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 approve
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 reject
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 canBePublished
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 articlePublication
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 feed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 booted
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Models;
4
5use App\Events\ArticleApproved;
6use App\Events\NewArticleFetched;
7use Database\Factories\ArticleFactory;
8use Illuminate\Database\Eloquent\Factories\HasFactory;
9use Illuminate\Database\Eloquent\Model;
10use Illuminate\Database\Eloquent\Relations\BelongsTo;
11use Illuminate\Database\Eloquent\Relations\HasOne;
12use Illuminate\Support\Carbon;
13
14/**
15 * @method static firstOrCreate(array<string, mixed> $array)
16 * @method static where(string $string, string $url)
17 * @method static create(array<string, mixed> $array)
18 * @property integer $id
19 * @property int $feed_id
20 * @property Feed $feed
21 * @property string $url
22 * @property bool|null $is_valid
23 * @property Carbon|null $validated_at
24 * @property Carbon $created_at
25 * @property Carbon $updated_at
26 * @property ArticlePublication $articlePublication
27 */
28class Article extends Model
29{
30    /** @use HasFactory<ArticleFactory> */
31    use HasFactory;
32
33    protected $fillable = [
34        'feed_id',
35        'url',
36        'title',
37        'description',
38        'is_valid',
39        'is_duplicate',
40        'approval_status',
41        'approved_at',
42        'approved_by',
43        'fetched_at',
44        'validated_at',
45    ];
46
47    /**
48     * @return array<string, string>
49     */
50    public function casts(): array
51    {
52        return [
53            'is_valid' => 'boolean',
54            'is_duplicate' => 'boolean',
55            'approval_status' => 'string',
56            'approved_at' => 'datetime',
57            'fetched_at' => 'datetime',
58            'validated_at' => 'datetime',
59            'created_at' => 'datetime',
60            'updated_at' => 'datetime',
61        ];
62    }
63
64    public function isValid(): bool
65    {
66        if (is_null($this->validated_at)) {
67            return false;
68        }
69
70        if (is_null($this->is_valid)) {
71            return false;
72        }
73
74        return $this->is_valid;
75    }
76
77    public function isApproved(): bool
78    {
79        return $this->approval_status === 'approved';
80    }
81
82    public function isPending(): bool
83    {
84        return $this->approval_status === 'pending';
85    }
86
87    public function isRejected(): bool
88    {
89        return $this->approval_status === 'rejected';
90    }
91
92    public function approve(string $approvedBy = null): void
93    {
94        $this->update([
95            'approval_status' => 'approved',
96            'approved_at' => now(),
97            'approved_by' => $approvedBy,
98        ]);
99
100        // Fire event to trigger publishing
101        event(new ArticleApproved($this));
102    }
103
104    public function reject(string $rejectedBy = null): void
105    {
106        $this->update([
107            'approval_status' => 'rejected',
108            'approved_at' => now(),
109            'approved_by' => $rejectedBy,
110        ]);
111    }
112
113    public function canBePublished(): bool
114    {
115        if (!$this->isValid()) {
116            return false;
117        }
118
119        // If approval system is disabled, auto-approve valid articles
120        if (!\App\Models\Setting::isPublishingApprovalsEnabled()) {
121            return true;
122        }
123
124        // If approval system is enabled, only approved articles can be published
125        return $this->isApproved();
126    }
127
128    /**
129     * @return HasOne<ArticlePublication, $this>
130     */
131    public function articlePublication(): HasOne
132    {
133        return $this->hasOne(ArticlePublication::class);
134    }
135
136    /**
137     * @return BelongsTo<Feed, $this>
138     */
139    public function feed(): BelongsTo
140    {
141        return $this->belongsTo(Feed::class);
142    }
143
144    protected static function booted(): void
145    {
146        static::created(function ($article) {
147            event(new NewArticleFetched($article));
148        });
149    }
150}