Passed
Push — master ( ee4c3d...022e93 )
by Darko
09:16
created

UserActivityStat::getDownloadsPerDay()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 23
rs 9.7666
cc 3
nc 3
nop 1
1
<?php
2
3
namespace App\Models;
4
5
use Carbon\Carbon;
6
use Illuminate\Database\Eloquent\Factories\HasFactory;
7
use Illuminate\Database\Eloquent\Model;
8
9
class UserActivityStat extends Model
10
{
11
    use HasFactory;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Database\Eloquent\Factories\HasFactory requires the property $factoryClass which is not provided by App\Models\UserActivityStat.
Loading history...
12
13
    protected $guarded = [];
14
15
    protected $casts = [
16
        'stat_date' => 'date',
17
    ];
18
19
    /**
20
     * Collect and store user activity stats for a specific date
21
     * This aggregates data from user_downloads and user_requests tables
22
     */
23
    public static function collectDailyStats(?string $date = null): void
24
    {
25
        $statDate = $date ? Carbon::parse($date)->format('Y-m-d') : Carbon::yesterday()->format('Y-m-d');
26
27
        // Count downloads for the date
28
        $downloadsCount = UserDownload::query()
29
            ->whereRaw('DATE(timestamp) = ?', [$statDate])
30
            ->count();
31
32
        // Count API hits for the date
33
        $apiHitsCount = UserRequest::query()
34
            ->whereRaw('DATE(timestamp) = ?', [$statDate])
35
            ->count();
36
37
        // Store or update the stats
38
        self::updateOrCreate(
39
            ['stat_date' => $statDate],
40
            [
41
                'downloads_count' => $downloadsCount,
42
                'api_hits_count' => $apiHitsCount,
43
            ]
44
        );
45
    }
46
47
    /**
48
     * Get download stats for the last N days
49
     */
50
    public static function getDownloadsPerDay(int $days = 30): array
51
    {
52
        $startDate = Carbon::now()->subDays($days - 1)->format('Y-m-d');
53
54
        $stats = self::query()
55
            ->select('stat_date', 'downloads_count')
56
            ->where('stat_date', '>=', $startDate)
57
            ->orderBy('stat_date', 'asc')
0 ignored issues
show
Bug introduced by
'stat_date' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderBy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

57
            ->orderBy(/** @scrutinizer ignore-type */ 'stat_date', 'asc')
Loading history...
58
            ->get()
59
            ->keyBy('stat_date');
60
61
        // Fill in missing days with zero counts
62
        $result = [];
63
        for ($i = $days - 1; $i >= 0; $i--) {
64
            $date = Carbon::now()->subDays($i)->format('Y-m-d');
65
            $stat = $stats->get($date);
66
            $result[] = [
67
                'date' => Carbon::parse($date)->format('M d'),
68
                'count' => $stat ? $stat->downloads_count : 0,
69
            ];
70
        }
71
72
        return $result;
73
    }
74
75
    /**
76
     * Get API hits stats for the last N days
77
     */
78
    public static function getApiHitsPerDay(int $days = 30): array
79
    {
80
        $startDate = Carbon::now()->subDays($days - 1)->format('Y-m-d');
81
82
        $stats = self::query()
83
            ->select('stat_date', 'api_hits_count')
84
            ->where('stat_date', '>=', $startDate)
85
            ->orderBy('stat_date', 'asc')
0 ignored issues
show
Bug introduced by
'stat_date' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderBy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

85
            ->orderBy(/** @scrutinizer ignore-type */ 'stat_date', 'asc')
Loading history...
86
            ->get()
87
            ->keyBy('stat_date');
88
89
        // Fill in missing days with zero counts
90
        $result = [];
91
        for ($i = $days - 1; $i >= 0; $i--) {
92
            $date = Carbon::now()->subDays($i)->format('Y-m-d');
93
            $stat = $stats->get($date);
94
            $result[] = [
95
                'date' => Carbon::parse($date)->format('M d'),
96
                'count' => $stat ? $stat->api_hits_count : 0,
97
            ];
98
        }
99
100
        return $result;
101
    }
102
103
    /**
104
     * Get total downloads for the last N days
105
     */
106
    public static function getTotalDownloads(int $days = 7): int
107
    {
108
        return self::query()
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::query()->wh...>sum('downloads_count') could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
109
            ->where('stat_date', '>=', Carbon::now()->subDays($days)->format('Y-m-d'))
110
            ->sum('downloads_count');
111
    }
112
113
    /**
114
     * Get total API hits for the last N days
115
     */
116
    public static function getTotalApiHits(int $days = 7): int
117
    {
118
        return self::query()
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::query()->wh...->sum('api_hits_count') could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
119
            ->where('stat_date', '>=', Carbon::now()->subDays($days)->format('Y-m-d'))
120
            ->sum('api_hits_count');
121
    }
122
123
    /**
124
     * Cleanup old stats (keep last N days)
125
     */
126
    public static function cleanupOldStats(int $keepDays = 90): int
127
    {
128
        $cutoffDate = Carbon::now()->subDays($keepDays)->format('Y-m-d');
129
130
        return self::query()
131
            ->where('stat_date', '<', $cutoffDate)
132
            ->delete();
133
    }
134
135
    /**
136
     * Collect and store hourly user activity stats for a specific hour
137
     * This aggregates data from user_downloads and user_requests tables
138
     */
139
    public static function collectHourlyStats(?string $hour = null): void
140
    {
141
        $statHour = $hour ? Carbon::parse($hour)->format('Y-m-d H:00:00') : Carbon::now()->subHour()->startOfHour()->format('Y-m-d H:00:00');
142
143
        // Count downloads for the hour
144
        $downloadsCount = UserDownload::query()
145
            ->where('timestamp', '>=', $statHour)
146
            ->where('timestamp', '<', Carbon::parse($statHour)->addHour()->format('Y-m-d H:00:00'))
147
            ->count();
148
149
        // Count API hits for the hour
150
        $apiHitsCount = UserRequest::query()
151
            ->where('timestamp', '>=', $statHour)
152
            ->where('timestamp', '<', Carbon::parse($statHour)->addHour()->format('Y-m-d H:00:00'))
153
            ->count();
154
155
        // Store in hourly stats table
156
        \DB::table('user_activity_stats_hourly')->updateOrInsert(
157
            ['stat_hour' => $statHour],
158
            [
159
                'downloads_count' => $downloadsCount,
160
                'api_hits_count' => $apiHitsCount,
161
                'updated_at' => now(),
162
                'created_at' => \DB::raw('COALESCE(created_at, NOW())'),
163
            ]
164
        );
165
    }
166
167
    /**
168
     * Get download stats per hour for the last N hours
169
     */
170
    public static function getDownloadsPerHour(int $hours = 168): array
171
    {
172
        $startHour = Carbon::now()->subHours($hours - 1)->startOfHour()->format('Y-m-d H:00:00');
173
174
        $stats = \DB::table('user_activity_stats_hourly')
175
            ->select('stat_hour', 'downloads_count')
176
            ->where('stat_hour', '>=', $startHour)
177
            ->orderBy('stat_hour', 'asc')
0 ignored issues
show
Bug introduced by
'stat_hour' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderBy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

177
            ->orderBy(/** @scrutinizer ignore-type */ 'stat_hour', 'asc')
Loading history...
178
            ->get()
179
            ->keyBy('stat_hour');
180
181
        // Fill in missing hours with zero counts
182
        $result = [];
183
        for ($i = $hours - 1; $i >= 0; $i--) {
184
            $time = Carbon::now()->subHours($i)->startOfHour();
185
            $hourKey = $time->format('Y-m-d H:00:00');
186
            $stat = $stats->get($hourKey);
187
188
            // Format label based on how recent the hour is
189
            $now = Carbon::now();
190
            if ($time->isToday()) {
191
                $label = $time->format('H:i');
192
            } elseif ($time->isYesterday()) {
193
                $label = 'Yesterday '.$time->format('H:i');
194
            } elseif ($time->diffInDays($now) < 7) {
195
                $label = $time->format('D H:i');
196
            } else {
197
                $label = $time->format('M d H:i');
198
            }
199
200
            $result[] = [
201
                'time' => $label,
202
                'count' => $stat ? $stat->downloads_count : 0,
203
            ];
204
        }
205
206
        return $result;
207
    }
208
209
    /**
210
     * Get API hits stats per hour for the last N hours
211
     */
212
    public static function getApiHitsPerHour(int $hours = 168): array
213
    {
214
        $startHour = Carbon::now()->subHours($hours - 1)->startOfHour()->format('Y-m-d H:00:00');
215
216
        $stats = \DB::table('user_activity_stats_hourly')
217
            ->select('stat_hour', 'api_hits_count')
218
            ->where('stat_hour', '>=', $startHour)
219
            ->orderBy('stat_hour', 'asc')
0 ignored issues
show
Bug introduced by
'stat_hour' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderBy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

219
            ->orderBy(/** @scrutinizer ignore-type */ 'stat_hour', 'asc')
Loading history...
220
            ->get()
221
            ->keyBy('stat_hour');
222
223
        // Fill in missing hours with zero counts
224
        $result = [];
225
        for ($i = $hours - 1; $i >= 0; $i--) {
226
            $time = Carbon::now()->subHours($i)->startOfHour();
227
            $hourKey = $time->format('Y-m-d H:00:00');
228
            $stat = $stats->get($hourKey);
229
230
            // Format label based on how recent the hour is
231
            $now = Carbon::now();
232
            if ($time->isToday()) {
233
                $label = $time->format('H:i');
234
            } elseif ($time->isYesterday()) {
235
                $label = 'Yesterday '.$time->format('H:i');
236
            } elseif ($time->diffInDays($now) < 7) {
237
                $label = $time->format('D H:i');
238
            } else {
239
                $label = $time->format('M d H:i');
240
            }
241
242
            $result[] = [
243
                'time' => $label,
244
                'count' => $stat ? $stat->api_hits_count : 0,
245
            ];
246
        }
247
248
        return $result;
249
    }
250
251
    /**
252
     * Cleanup old hourly stats (keep last N days)
253
     */
254
    public static function cleanupOldHourlyStats(int $keepDays = 30): int
255
    {
256
        $cutoffHour = Carbon::now()->subDays($keepDays)->startOfHour()->format('Y-m-d H:00:00');
257
258
        return \DB::table('user_activity_stats_hourly')
259
            ->where('stat_hour', '<', $cutoffHour)
260
            ->delete();
261
    }
262
}
263