Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
47.06% covered (danger)
47.06%
8 / 17
40.00% covered (danger)
40.00%
4 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
CachedExtensionFile
47.06% covered (danger)
47.06%
8 / 17
40.00% covered (danger)
40.00%
4 / 10
33.37
0.00% covered (danger)
0.00%
0 / 1
 scopeBySource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 scopeByBrowser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 scopeReleases
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 scopeArtifacts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 scopeActive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 scopeExpired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 findByGithubId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isCached
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 hasExpired
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 markAsExpired
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Http\Models;
4
5use Illuminate\Database\Eloquent\Builder;
6
7/**
8 * Cached Extension File Model
9 *
10 * Stores metadata about GitHub extension files cached in S3.
11 * Tracks both release assets (public) and artifacts (require auth).
12 *
13 * @property string $_id MongoDB ObjectId
14 * @property string $github_id GitHub asset/artifact ID
15 * @property string $source Source type: 'release' or 'artifact'
16 * @property string $browser Browser type: 'chrome' or 'edge'
17 * @property string $version Version string (e.g., '1.0.0' or '1.0.0-staging')
18 * @property int|null $build Build number (for releases only)
19 * @property string $file_name Original file name
20 * @property string $github_url Original GitHub URL
21 * @property string $s3_path Path in S3 bucket
22 * @property string $s3_url Public S3 URL
23 * @property int $size_bytes File size in bytes
24 * @property string $content_hash SHA-256 hash of file content
25 * @property \Carbon\Carbon $published_at When the file was published on GitHub
26 * @property \Carbon\Carbon|null $expires_at When the artifact expires (artifacts only)
27 * @property bool $is_expired Whether the cached file is expired
28 * @property \Carbon\Carbon $cached_at When the file was cached in S3
29 * @property \Carbon\Carbon $created_at
30 * @property \Carbon\Carbon $updated_at
31 */
32class CachedExtensionFile extends Moloquent
33{
34    /**
35     * The connection name for the model.
36     *
37     * @var string
38     */
39    protected $connection = 'mongodb';
40
41    /**
42     * The collection associated with the model.
43     *
44     * @var string
45     */
46    protected $collection = 'cached_extension_files';
47
48    /**
49     * The attributes that are mass assignable.
50     *
51     * @var array<string>
52     */
53    protected $fillable = [
54        'github_id',
55        'source',
56        'browser',
57        'version',
58        'build',
59        'file_name',
60        'github_url',
61        's3_path',
62        's3_url',
63        'size_bytes',
64        'content_hash',
65        'published_at',
66        'expires_at',
67        'is_expired',
68        'cached_at',
69    ];
70
71    /**
72     * The attributes that should be cast.
73     *
74     * @var array<string, string>
75     */
76    protected $casts = [
77        'build' => 'integer',
78        'size_bytes' => 'integer',
79        'is_expired' => 'boolean',
80        'published_at' => 'datetime',
81        'expires_at' => 'datetime',
82        'cached_at' => 'datetime',
83    ];
84
85    /**
86     * Source type constants.
87     */
88    public const SOURCE_RELEASE = 'release';
89
90    public const SOURCE_ARTIFACT = 'artifact';
91
92    /**
93     * Browser type constants.
94     */
95    public const BROWSER_CHROME = 'chrome';
96
97    public const BROWSER_EDGE = 'edge';
98
99    /**
100     * Scope to filter by source type.
101     *
102     * @param  string  $source  'release' or 'artifact'
103     */
104    public function scopeBySource(Builder $query, string $source): Builder
105    {
106        return $query->where('source', $source);
107    }
108
109    /**
110     * Scope to filter by browser.
111     *
112     * @param  string  $browser  'chrome' or 'edge'
113     */
114    public function scopeByBrowser(Builder $query, string $browser): Builder
115    {
116        return $query->where('browser', $browser);
117    }
118
119    /**
120     * Scope to filter release files.
121     */
122    public function scopeReleases(Builder $query): Builder
123    {
124        return $query->where('source', self::SOURCE_RELEASE);
125    }
126
127    /**
128     * Scope to filter artifact files.
129     */
130    public function scopeArtifacts(Builder $query): Builder
131    {
132        return $query->where('source', self::SOURCE_ARTIFACT);
133    }
134
135    /**
136     * Scope to filter non-expired files.
137     */
138    public function scopeActive(Builder $query): Builder
139    {
140        return $query->where('is_expired', false);
141    }
142
143    /**
144     * Scope to filter expired files.
145     */
146    public function scopeExpired(Builder $query): Builder
147    {
148        return $query->where('is_expired', true);
149    }
150
151    /**
152     * Find a cached file by its GitHub ID and source.
153     *
154     * @param  string  $githubId  The GitHub asset/artifact ID
155     * @param  string  $source  'release' or 'artifact'
156     * @return static|null
157     */
158    public static function findByGithubId(string $githubId, string $source): ?self
159    {
160        return static::where('github_id', $githubId)
161            ->where('source', $source)
162            ->first();
163    }
164
165    /**
166     * Check if a file is cached and active.
167     *
168     * @param  string  $githubId  The GitHub asset/artifact ID
169     * @param  string  $source  'release' or 'artifact'
170     */
171    public static function isCached(string $githubId, string $source): bool
172    {
173        return static::where('github_id', $githubId)
174            ->where('source', $source)
175            ->where('is_expired', false)
176            ->exists();
177    }
178
179    /**
180     * Check if this cached file has expired.
181     */
182    public function hasExpired(): bool
183    {
184        if ($this->source === self::SOURCE_RELEASE) {
185            return false; // Releases don't expire
186        }
187
188        return $this->expires_at !== null && $this->expires_at->isPast();
189    }
190
191    /**
192     * Mark this cached file as expired.
193     */
194    public function markAsExpired(): bool
195    {
196        return $this->update(['is_expired' => true]);
197    }
198}