NNTmux /
newznab-tmux
| 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
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
'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
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
'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
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
|
|||||
| 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
|
|||||
| 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
'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
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
'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
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 |